impel-cli 0.20.13 → 0.20.15
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/README.md +16 -0
- package/RELEASE_NOTES.md +25 -0
- package/docs/native-agent-host-capability-matrix.md +14 -6
- package/package.json +4 -1
- package/scripts/analyze-native-codex.mjs +426 -0
- package/scripts/profile-native-codex.mjs +575 -0
- package/src/agents.js +230 -19
- package/src/apps.js +5 -3
- package/src/cli.js +1 -0
- package/src/commands/agents.js +4 -0
- package/src/commands/launch.js +124 -6
- package/src/nativeAgentTransport.js +58 -6
package/src/agents.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
redactSecretText,
|
|
17
17
|
} from "./config.js";
|
|
18
18
|
import {
|
|
19
|
+
IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES,
|
|
19
20
|
IMPEL_NATIVE_AGENT_MCP_TARGET,
|
|
20
21
|
impelNativeAgentMcpInvocation,
|
|
21
22
|
} from "./selfInvocation.js";
|
|
@@ -51,7 +52,7 @@ export const NATIVE_AGENT_RUN_TOOL = "run_native_agent";
|
|
|
51
52
|
export const NATIVE_AGENT_RESUME_TOOL = "resume_native_agent_run";
|
|
52
53
|
export const NATIVE_AGENT_RECOVER_TOOL = "recover_native_agent_runs";
|
|
53
54
|
export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
|
|
54
|
-
export const MANAGED_AGENT_MANIFEST_VERSION =
|
|
55
|
+
export const MANAGED_AGENT_MANIFEST_VERSION = 10;
|
|
55
56
|
|
|
56
57
|
const NATIVE_AGENT_TOOL_NAMES = [
|
|
57
58
|
NATIVE_AGENT_RUN_TOOL,
|
|
@@ -631,8 +632,12 @@ function claudeAdapterInstructions(tenantId, agent) {
|
|
|
631
632
|
].join(" ");
|
|
632
633
|
}
|
|
633
634
|
|
|
635
|
+
export function nativeToolNamespace(serverName = MANAGED_AGENT_MCP_SERVER) {
|
|
636
|
+
return `mcp__${serverName}`;
|
|
637
|
+
}
|
|
638
|
+
|
|
634
639
|
function nativeToolName(toolName) {
|
|
635
|
-
return
|
|
640
|
+
return `${nativeToolNamespace()}__${toolName}`;
|
|
636
641
|
}
|
|
637
642
|
|
|
638
643
|
function codexAdapterInstructions(tenantId, agent) {
|
|
@@ -732,23 +737,46 @@ function renderClaudeAgent({ tenantId, agent, name, invocation, recoveryOnly = f
|
|
|
732
737
|
return lines.join("\n");
|
|
733
738
|
}
|
|
734
739
|
|
|
735
|
-
function
|
|
740
|
+
function codexInvocationEnvironment(invocation, { durableProfile = false } = {}) {
|
|
741
|
+
const entries = Object.entries(invocation.env || {});
|
|
742
|
+
if (!durableProfile) return entries;
|
|
743
|
+
const transient = new Set(IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES);
|
|
744
|
+
return entries.filter(([key]) => !transient.has(key));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function renderCodexConfiguration({
|
|
748
|
+
tenantId,
|
|
749
|
+
agent,
|
|
750
|
+
name,
|
|
751
|
+
invocation,
|
|
752
|
+
recoveryOnly = false,
|
|
753
|
+
durableProfile = false,
|
|
754
|
+
directCodeMode = true,
|
|
755
|
+
}) {
|
|
736
756
|
const description = recoveryOnly
|
|
737
757
|
? `Recovery-only custom agent for pending runs from retired Impel binding ${agent.agentId} in tenant ${tenantId}.`
|
|
738
758
|
: customAgentDescription(tenantId, agent);
|
|
739
759
|
const toolNames = recoveryOnly ? NATIVE_AGENT_RECOVERY_TOOL_NAMES : nativeAgentToolNames(agent);
|
|
740
760
|
const eager = eagerNativeAgentTransportEnabled();
|
|
741
|
-
const envEntries =
|
|
761
|
+
const envEntries = codexInvocationEnvironment(invocation, { durableProfile })
|
|
742
762
|
.map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`)
|
|
743
763
|
.join(", ");
|
|
744
764
|
const lines = [
|
|
745
|
-
|
|
746
|
-
|
|
765
|
+
...(!durableProfile ? [
|
|
766
|
+
`name = ${JSON.stringify(name)}`,
|
|
767
|
+
`description = ${JSON.stringify(description)}`,
|
|
768
|
+
] : []),
|
|
747
769
|
'model = "gpt-5.6-luna"',
|
|
748
770
|
'model_reasoning_effort = "low"',
|
|
749
771
|
'sandbox_mode = "read-only"',
|
|
750
772
|
`developer_instructions = ${JSON.stringify(recoveryOnly ? retiredAdapterInstructions(tenantId, agent) : codexAdapterInstructions(tenantId, agent))}`,
|
|
751
773
|
"",
|
|
774
|
+
...(directCodeMode ? [
|
|
775
|
+
"[features.code_mode]",
|
|
776
|
+
"enabled = true",
|
|
777
|
+
`direct_only_tool_namespaces = [${JSON.stringify(nativeToolNamespace())}]`,
|
|
778
|
+
"",
|
|
779
|
+
] : []),
|
|
752
780
|
`[mcp_servers.${MANAGED_AGENT_MCP_SERVER}]`,
|
|
753
781
|
`command = ${JSON.stringify(invocation.command)}`,
|
|
754
782
|
`args = [${invocation.args.map((argument) => JSON.stringify(argument)).join(", ")}]`,
|
|
@@ -764,6 +792,14 @@ function renderCodexAgent({ tenantId, agent, name, invocation, recoveryOnly = fa
|
|
|
764
792
|
return lines.join("\n");
|
|
765
793
|
}
|
|
766
794
|
|
|
795
|
+
function renderCodexAgent(options) {
|
|
796
|
+
return renderCodexConfiguration(options);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function renderCodexProfile(options) {
|
|
800
|
+
return renderCodexConfiguration({ ...options, durableProfile: true });
|
|
801
|
+
}
|
|
802
|
+
|
|
767
803
|
function boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
768
804
|
policyFingerprint = nativeAgentPolicyFingerprint(agent),
|
|
769
805
|
mode = usesDirectAnswer(agent) ? "answer" : "durable",
|
|
@@ -794,7 +830,7 @@ function boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
|
794
830
|
};
|
|
795
831
|
}
|
|
796
832
|
|
|
797
|
-
export function renderManagedAgents(client, tenantId, agents, invocation = null) {
|
|
833
|
+
export function renderManagedAgents(client, tenantId, agents, invocation = null, { directCodeMode = true } = {}) {
|
|
798
834
|
if (client !== "claude" && client !== "codex") throw new Error(`unknown agent client ${client}`);
|
|
799
835
|
const normalizedTenant = normalizeTenantId(tenantId);
|
|
800
836
|
const fileStems = generatedAgentFileStems(normalizedTenant, agents);
|
|
@@ -811,20 +847,34 @@ export function renderManagedAgents(client, tenantId, agents, invocation = null)
|
|
|
811
847
|
const boundInvocation = boundNativeAgentInvocation(normalizedTenant, agent, invocation);
|
|
812
848
|
const contents = client === "claude"
|
|
813
849
|
? renderClaudeAgent({ tenantId: normalizedTenant, agent, name, invocation: boundInvocation })
|
|
814
|
-
: renderCodexAgent({ tenantId: normalizedTenant, agent, name, invocation: boundInvocation });
|
|
850
|
+
: renderCodexAgent({ tenantId: normalizedTenant, agent, name, invocation: boundInvocation, directCodeMode });
|
|
851
|
+
const profileName = client === "codex" ? fileStem : null;
|
|
815
852
|
return {
|
|
816
853
|
agentId: agent.agentId,
|
|
854
|
+
title: agent.title,
|
|
855
|
+
sideEffects: agent.sideEffects,
|
|
817
856
|
scopeParam: agent.scopeParam,
|
|
818
857
|
policyFingerprint: nativeAgentPolicyFingerprint(agent),
|
|
819
858
|
retired: false,
|
|
820
859
|
name,
|
|
821
860
|
fileName: `${fileStem}${extension}`,
|
|
822
861
|
contents,
|
|
862
|
+
...(profileName ? {
|
|
863
|
+
profileName,
|
|
864
|
+
profileFileName: `${profileName}.config.toml`,
|
|
865
|
+
profileContents: renderCodexProfile({
|
|
866
|
+
tenantId: normalizedTenant,
|
|
867
|
+
agent,
|
|
868
|
+
name,
|
|
869
|
+
invocation: boundInvocation,
|
|
870
|
+
directCodeMode,
|
|
871
|
+
}),
|
|
872
|
+
} : {}),
|
|
823
873
|
};
|
|
824
874
|
});
|
|
825
875
|
}
|
|
826
876
|
|
|
827
|
-
function renderRetiredManagedAgents(client, tenantId, bindings, active, invocation = null) {
|
|
877
|
+
function renderRetiredManagedAgents(client, tenantId, bindings, active, invocation = null, { directCodeMode = true } = {}) {
|
|
828
878
|
const usedNames = new Set(active.map(({ name }) => name));
|
|
829
879
|
const usedFiles = new Set(active.map(({ fileName }) => fileName));
|
|
830
880
|
return bindings.map((binding) => {
|
|
@@ -865,15 +915,30 @@ function renderRetiredManagedAgents(client, tenantId, bindings, active, invocati
|
|
|
865
915
|
});
|
|
866
916
|
const contents = client === "claude"
|
|
867
917
|
? renderClaudeAgent({ tenantId, agent, name, invocation: boundInvocation, recoveryOnly: true })
|
|
868
|
-
: renderCodexAgent({ tenantId, agent, name, invocation: boundInvocation, recoveryOnly: true });
|
|
918
|
+
: renderCodexAgent({ tenantId, agent, name, invocation: boundInvocation, recoveryOnly: true, directCodeMode });
|
|
919
|
+
const profileName = client === "codex" ? path.basename(fileName, extension) : null;
|
|
869
920
|
return {
|
|
870
921
|
agentId: binding.agentId,
|
|
922
|
+
title: agent.title,
|
|
923
|
+
sideEffects: agent.sideEffects,
|
|
871
924
|
scopeParam: binding.scopeParam,
|
|
872
925
|
policyFingerprint: binding.policyFingerprint,
|
|
873
926
|
retired: true,
|
|
874
927
|
name,
|
|
875
928
|
fileName,
|
|
876
929
|
contents,
|
|
930
|
+
...(profileName ? {
|
|
931
|
+
profileName,
|
|
932
|
+
profileFileName: `${profileName}.config.toml`,
|
|
933
|
+
profileContents: renderCodexProfile({
|
|
934
|
+
tenantId,
|
|
935
|
+
agent,
|
|
936
|
+
name,
|
|
937
|
+
invocation: boundInvocation,
|
|
938
|
+
recoveryOnly: true,
|
|
939
|
+
directCodeMode,
|
|
940
|
+
}),
|
|
941
|
+
} : {}),
|
|
877
942
|
};
|
|
878
943
|
});
|
|
879
944
|
}
|
|
@@ -903,16 +968,112 @@ function readManifest(manifestPath) {
|
|
|
903
968
|
}
|
|
904
969
|
}
|
|
905
970
|
|
|
971
|
+
function managedManifestPath(root) {
|
|
972
|
+
return path.join(root, "agents", MANAGED_AGENT_DIRECTORY, MANAGED_AGENT_MANIFEST);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function contentDigest(contents) {
|
|
976
|
+
return crypto.createHash("sha256").update(contents, "utf8").digest("hex");
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function managedArtifactIsCurrent(root, relativePath, expectedDigest) {
|
|
980
|
+
if (typeof relativePath !== "string" || !relativePath || path.isAbsolute(relativePath)) return false;
|
|
981
|
+
const segments = relativePath.split("/");
|
|
982
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
|
|
983
|
+
if (!/^[a-f0-9]{64}$/u.test(expectedDigest || "")) return false;
|
|
984
|
+
const artifactPath = path.join(root, ...segments);
|
|
985
|
+
try {
|
|
986
|
+
if (fs.lstatSync(artifactPath).isSymbolicLink()) return false;
|
|
987
|
+
return contentDigest(fs.readFileSync(artifactPath, "utf8")) === expectedDigest;
|
|
988
|
+
} catch {
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function managedArtifactExists(root, relativePath, expectedDigest) {
|
|
994
|
+
if (typeof relativePath !== "string" || !relativePath || path.isAbsolute(relativePath)) return false;
|
|
995
|
+
const segments = relativePath.split("/");
|
|
996
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) return false;
|
|
997
|
+
if (!/^[a-f0-9]{64}$/u.test(expectedDigest || "")) return false;
|
|
998
|
+
try {
|
|
999
|
+
const stat = fs.lstatSync(path.join(root, ...segments));
|
|
1000
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
1001
|
+
} catch {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function readManagedAgentManifest(root) {
|
|
1007
|
+
return readManifest(managedManifestPath(root));
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
export function resolveManagedCodexAgentProfile(root, tenantId, selector) {
|
|
1011
|
+
const normalizedTenant = normalizeTenantId(tenantId);
|
|
1012
|
+
const normalizedSelector = boundedString(selector, "agent selector", { max: 512 });
|
|
1013
|
+
const manifest = readManagedAgentManifest(root);
|
|
1014
|
+
if (!manifest
|
|
1015
|
+
|| manifest.version !== MANAGED_AGENT_MANIFEST_VERSION
|
|
1016
|
+
|| manifest.client !== "codex"
|
|
1017
|
+
|| manifest.tenantId !== normalizedTenant
|
|
1018
|
+
|| !Array.isArray(manifest.agents)) {
|
|
1019
|
+
throw new Error("the managed Codex agent catalog is missing or stale; run `impel agents sync codex`");
|
|
1020
|
+
}
|
|
1021
|
+
const records = manifest.agents.filter((record) => record
|
|
1022
|
+
&& typeof record === "object"
|
|
1023
|
+
&& typeof record.profileName === "string"
|
|
1024
|
+
&& typeof record.profileFileName === "string");
|
|
1025
|
+
const active = records.filter((record) => !record.retired);
|
|
1026
|
+
let matches = active.filter((record) => record.agentId === normalizedSelector);
|
|
1027
|
+
if (matches.length === 0) matches = active.filter((record) => record.title === normalizedSelector);
|
|
1028
|
+
if (matches.length === 0) matches = active.filter((record) => record.profileName === normalizedSelector);
|
|
1029
|
+
if (matches.length === 0 && records.some((record) => record.retired && [
|
|
1030
|
+
record.agentId,
|
|
1031
|
+
record.title,
|
|
1032
|
+
record.profileName,
|
|
1033
|
+
].includes(normalizedSelector))) {
|
|
1034
|
+
throw new Error(`managed Codex agent ${JSON.stringify(normalizedSelector)} is retired and cannot start new work`);
|
|
1035
|
+
}
|
|
1036
|
+
if (matches.length !== 1) {
|
|
1037
|
+
const reason = matches.length > 1 ? "is ambiguous" : "was not found";
|
|
1038
|
+
throw new Error(`managed Codex agent ${JSON.stringify(normalizedSelector)} ${reason} for tenant ${JSON.stringify(normalizedTenant)}`);
|
|
1039
|
+
}
|
|
1040
|
+
const [record] = matches;
|
|
1041
|
+
if (path.basename(record.profileFileName) !== record.profileFileName
|
|
1042
|
+
|| record.profileFileName !== `${record.profileName}.config.toml`) {
|
|
1043
|
+
throw new Error("the managed Codex agent profile mapping is invalid; run `impel agents sync codex`");
|
|
1044
|
+
}
|
|
1045
|
+
const relativePath = record.profileFileName;
|
|
1046
|
+
if (!managedArtifactIsCurrent(root, relativePath, manifest.contentDigests?.[relativePath])) {
|
|
1047
|
+
throw new Error("the managed Codex agent profile failed its integrity check; run `impel agents sync codex`");
|
|
1048
|
+
}
|
|
1049
|
+
return { ...record, path: path.join(root, relativePath) };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
906
1052
|
function profileIsFresh(profile, tenantId, now, ttlMs) {
|
|
907
|
-
const manifest =
|
|
1053
|
+
const manifest = readManagedAgentManifest(profile.root);
|
|
908
1054
|
if (!manifest || manifest.version !== MANAGED_AGENT_MANIFEST_VERSION || manifest.tenantId !== tenantId) return false;
|
|
1055
|
+
if (!Array.isArray(manifest.files) || !manifest.contentDigests
|
|
1056
|
+
|| typeof manifest.contentDigests !== "object"
|
|
1057
|
+
|| Array.isArray(manifest.contentDigests)) return false;
|
|
1058
|
+
const expectedDirectProfiles = profile.client === "codex" && profile.directProfiles !== false;
|
|
1059
|
+
const expectedDirectCodeMode = profile.client === "codex" && profile.directCodeMode !== false;
|
|
1060
|
+
if (manifest.directProfiles !== expectedDirectProfiles
|
|
1061
|
+
|| manifest.directCodeMode !== expectedDirectCodeMode) return false;
|
|
909
1062
|
const syncedAt = Date.parse(manifest.syncedAt || "");
|
|
910
1063
|
if (!Number.isFinite(syncedAt) || now - syncedAt >= ttlMs) return false;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1064
|
+
const artifacts = [
|
|
1065
|
+
...manifest.files.map((fileName) => `agents/${fileName}`),
|
|
1066
|
+
...(Array.isArray(manifest.profiles) ? manifest.profiles : []),
|
|
1067
|
+
];
|
|
1068
|
+
// Renderer changes are invalidated by the manifest version/capability fields
|
|
1069
|
+
// above. Keep ordinary launch freshness O(number of files) metadata-only;
|
|
1070
|
+
// the explicitly selected direct profile gets a full SHA-256 verification
|
|
1071
|
+
// in resolveManagedCodexAgentProfile before Codex starts.
|
|
1072
|
+
return artifacts.every((relativePath) => managedArtifactExists(
|
|
1073
|
+
profile.root,
|
|
1074
|
+
relativePath,
|
|
1075
|
+
manifest.contentDigests?.[relativePath],
|
|
1076
|
+
));
|
|
916
1077
|
}
|
|
917
1078
|
|
|
918
1079
|
export function syncAgentProfile({
|
|
@@ -921,6 +1082,8 @@ export function syncAgentProfile({
|
|
|
921
1082
|
label,
|
|
922
1083
|
tenantId,
|
|
923
1084
|
agents,
|
|
1085
|
+
directProfiles = client === "codex",
|
|
1086
|
+
directCodeMode = client === "codex",
|
|
924
1087
|
now = Date.now(),
|
|
925
1088
|
nativeAgentRunsRoot = path.join(CONFIG_DIR, "native-agent-runs"),
|
|
926
1089
|
}) {
|
|
@@ -934,7 +1097,7 @@ export function syncAgentProfile({
|
|
|
934
1097
|
privateDirectory(managedDir);
|
|
935
1098
|
const manifestPath = path.join(managedDir, MANAGED_AGENT_MANIFEST);
|
|
936
1099
|
const prior = readManifest(manifestPath);
|
|
937
|
-
const active = renderManagedAgents(client, tenantId, agents);
|
|
1100
|
+
const active = renderManagedAgents(client, tenantId, agents, null, { directCodeMode });
|
|
938
1101
|
const activeBindings = new Set(active.map((agent) =>
|
|
939
1102
|
`${agent.agentId}\0${agent.scopeParam}\0${agent.policyFingerprint}`
|
|
940
1103
|
));
|
|
@@ -942,16 +1105,22 @@ export function syncAgentProfile({
|
|
|
942
1105
|
.filter((binding) => !activeBindings.has(
|
|
943
1106
|
`${binding.agentId}\0${binding.scopeParam}\0${binding.policyFingerprint}`,
|
|
944
1107
|
));
|
|
945
|
-
const retired = renderRetiredManagedAgents(client, tenantId, retiredBindings, active);
|
|
1108
|
+
const retired = renderRetiredManagedAgents(client, tenantId, retiredBindings, active, null, { directCodeMode });
|
|
946
1109
|
const rendered = [...active, ...retired];
|
|
947
1110
|
if (new Set(rendered.map(({ name }) => name)).size !== rendered.length
|
|
948
|
-
|| new Set(rendered.map(({ fileName }) => fileName)).size !== rendered.length
|
|
1111
|
+
|| new Set(rendered.map(({ fileName }) => fileName)).size !== rendered.length
|
|
1112
|
+
|| (client === "codex" && directProfiles
|
|
1113
|
+
&& new Set(rendered.map(({ profileFileName }) => profileFileName)).size !== rendered.length)) {
|
|
949
1114
|
throw new Error("generated native-agent destinations are not unique");
|
|
950
1115
|
}
|
|
951
1116
|
const priorFiles = new Set(prior?.files || []);
|
|
1117
|
+
const priorProfiles = new Set(prior?.profiles || []);
|
|
952
1118
|
const priorUsesDiscoveryRoot = Number.isInteger(prior?.version)
|
|
953
1119
|
&& prior.version >= 2
|
|
954
1120
|
&& prior.version <= MANAGED_AGENT_MANIFEST_VERSION;
|
|
1121
|
+
const priorOwnsCodexProfiles = priorUsesDiscoveryRoot
|
|
1122
|
+
&& prior?.client === "codex"
|
|
1123
|
+
&& prior?.tenantId === tenantId;
|
|
955
1124
|
|
|
956
1125
|
// Native clients discover standalone definitions directly under `agents/`.
|
|
957
1126
|
// Preflight every destination before writing so an unmanaged file with the
|
|
@@ -966,11 +1135,26 @@ export function syncAgentProfile({
|
|
|
966
1135
|
throw new Error(`refusing to overwrite symlinked native-agent file ${destination}`);
|
|
967
1136
|
}
|
|
968
1137
|
}
|
|
1138
|
+
if (client === "codex" && directProfiles) {
|
|
1139
|
+
const profileDestination = path.join(root, agent.profileFileName);
|
|
1140
|
+
if (fs.existsSync(profileDestination)) {
|
|
1141
|
+
if (fs.lstatSync(profileDestination).isSymbolicLink()) {
|
|
1142
|
+
throw new Error(`refusing to overwrite symlinked Codex profile ${profileDestination}`);
|
|
1143
|
+
}
|
|
1144
|
+
if (!priorOwnsCodexProfiles || !priorProfiles.has(agent.profileFileName)) {
|
|
1145
|
+
throw new Error(`refusing to overwrite unmanaged Codex profile ${profileDestination}`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
969
1149
|
}
|
|
970
1150
|
for (const agent of rendered) {
|
|
971
1151
|
atomicPrivateWrite(path.join(agentsDir, agent.fileName), agent.contents);
|
|
1152
|
+
if (client === "codex" && directProfiles) atomicPrivateWrite(path.join(root, agent.profileFileName), agent.profileContents);
|
|
972
1153
|
}
|
|
973
1154
|
const currentFiles = new Set(rendered.map((agent) => agent.fileName));
|
|
1155
|
+
const currentProfiles = new Set(client === "codex" && directProfiles
|
|
1156
|
+
? rendered.map((agent) => agent.profileFileName)
|
|
1157
|
+
: []);
|
|
974
1158
|
for (const stale of prior?.files || []) {
|
|
975
1159
|
if (
|
|
976
1160
|
typeof stale === "string"
|
|
@@ -981,6 +1165,16 @@ export function syncAgentProfile({
|
|
|
981
1165
|
fs.rmSync(path.join(priorUsesDiscoveryRoot ? agentsDir : managedDir, stale), { force: true });
|
|
982
1166
|
}
|
|
983
1167
|
}
|
|
1168
|
+
for (const stale of priorOwnsCodexProfiles ? prior.profiles || [] : []) {
|
|
1169
|
+
if (
|
|
1170
|
+
typeof stale === "string"
|
|
1171
|
+
&& path.basename(stale) === stale
|
|
1172
|
+
&& stale.endsWith(".config.toml")
|
|
1173
|
+
&& !currentProfiles.has(stale)
|
|
1174
|
+
) {
|
|
1175
|
+
fs.rmSync(path.join(root, stale), { force: true });
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
984
1178
|
if (!priorUsesDiscoveryRoot) {
|
|
985
1179
|
for (const legacy of prior?.files || []) {
|
|
986
1180
|
if (
|
|
@@ -992,18 +1186,34 @@ export function syncAgentProfile({
|
|
|
992
1186
|
}
|
|
993
1187
|
}
|
|
994
1188
|
}
|
|
1189
|
+
const contentDigests = Object.fromEntries(rendered.flatMap((agent) => [
|
|
1190
|
+
[`agents/${agent.fileName}`, contentDigest(agent.contents)],
|
|
1191
|
+
...(client === "codex" && directProfiles
|
|
1192
|
+
? [[agent.profileFileName, contentDigest(agent.profileContents)]]
|
|
1193
|
+
: []),
|
|
1194
|
+
]).sort(([left], [right]) => left.localeCompare(right)));
|
|
995
1195
|
atomicPrivateWrite(manifestPath, `${JSON.stringify({
|
|
996
1196
|
version: MANAGED_AGENT_MANIFEST_VERSION,
|
|
997
1197
|
tenantId,
|
|
998
1198
|
client,
|
|
1199
|
+
directProfiles: client === "codex" && directProfiles,
|
|
1200
|
+
directCodeMode: client === "codex" && directCodeMode,
|
|
999
1201
|
syncedAt: new Date(now).toISOString(),
|
|
1000
1202
|
files: [...currentFiles].sort(),
|
|
1203
|
+
profiles: [...currentProfiles].sort(),
|
|
1204
|
+
contentDigests,
|
|
1001
1205
|
agents: rendered.map((agent) => ({
|
|
1002
1206
|
agentId: agent.agentId,
|
|
1207
|
+
title: agent.title,
|
|
1208
|
+
sideEffects: agent.sideEffects,
|
|
1003
1209
|
scopeParam: agent.scopeParam,
|
|
1004
1210
|
policyFingerprint: agent.policyFingerprint,
|
|
1005
1211
|
retired: agent.retired,
|
|
1006
1212
|
name: agent.name,
|
|
1213
|
+
...(client === "codex" && directProfiles ? {
|
|
1214
|
+
profileName: agent.profileName,
|
|
1215
|
+
profileFileName: agent.profileFileName,
|
|
1216
|
+
} : {}),
|
|
1007
1217
|
})),
|
|
1008
1218
|
}, null, 2)}\n`);
|
|
1009
1219
|
return {
|
|
@@ -1014,6 +1224,7 @@ export function syncAgentProfile({
|
|
|
1014
1224
|
count: active.length,
|
|
1015
1225
|
recoveryCount: retired.length,
|
|
1016
1226
|
files: [...currentFiles],
|
|
1227
|
+
profiles: [...currentProfiles],
|
|
1017
1228
|
};
|
|
1018
1229
|
}
|
|
1019
1230
|
|
package/src/apps.js
CHANGED
|
@@ -108,8 +108,8 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
108
108
|
// The Microsoft Store manifest identifies this exact reviewed build.
|
|
109
109
|
// Keep one Windows package version so the manifest contract and local
|
|
110
110
|
// AppX validation cannot silently drift apart again.
|
|
111
|
-
packageVersion: "26.
|
|
112
|
-
codexVersion: "0.147.0-alpha.
|
|
111
|
+
packageVersion: "26.803.5235.0",
|
|
112
|
+
codexVersion: "0.147.0-alpha.6.5",
|
|
113
113
|
publisherId: "2p2nqsd0c76g0",
|
|
114
114
|
executable: "app\\ChatGPT.exe",
|
|
115
115
|
updateManifestUrl: "https://persistent.oaistatic.com/codex-app-prod/windows-store-update.json",
|
|
@@ -279,7 +279,9 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
|
|
|
279
279
|
// transport and remove model-controlled upstream start/read polling.
|
|
280
280
|
// 29: add marker-aware custom-agent verbatim relay guidance to ChatGPT and
|
|
281
281
|
// expose direct-answer native agents through their one-shot local MCP binding.
|
|
282
|
-
|
|
282
|
+
// 30: generate integrity-tracked top-level Codex profiles and direct code-mode
|
|
283
|
+
// namespaces for fixed native-agent bindings.
|
|
284
|
+
export const CURRENT_CONFIG_VERSION = 30;
|
|
283
285
|
|
|
284
286
|
// Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
|
|
285
287
|
// helper rebranding, and signing. A vendored bundle is rebuilt only when this
|
package/src/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ Get started:
|
|
|
35
35
|
Work:
|
|
36
36
|
impel claude [args...] Launch Claude Code with an isolated Impel profile
|
|
37
37
|
impel codex [args...] Launch Codex with an isolated Impel profile
|
|
38
|
+
impel codex --agent <id|exact-title> ... Run one fixed tenant agent without a parent hop
|
|
38
39
|
impel remote handoff|dispatch|handback Move or control provider-native sessions remotely
|
|
39
40
|
impel remote status|viewer|proxy Inspect, control, or connect to a remote run
|
|
40
41
|
impel tenant list List accessible organizations
|
package/src/commands/agents.js
CHANGED
|
@@ -38,6 +38,10 @@ export function managedAgentProfiles(client, options = {}) {
|
|
|
38
38
|
environment,
|
|
39
39
|
homeDir,
|
|
40
40
|
}),
|
|
41
|
+
...(client === "codex" ? {
|
|
42
|
+
directProfiles: profile.label !== "Codex CLI (native profile)",
|
|
43
|
+
directCodeMode: profile.label !== "Codex CLI (native profile)",
|
|
44
|
+
} : {}),
|
|
41
45
|
}));
|
|
42
46
|
}
|
|
43
47
|
|
package/src/commands/launch.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ensureImpelClaudeProfile,
|
|
7
7
|
ensureImpelCodexProfile,
|
|
8
8
|
} from "../cliProfiles.js";
|
|
9
|
-
import { syncAgentProfilesSafe } from "../agents.js";
|
|
9
|
+
import { resolveManagedCodexAgentProfile, syncAgentProfilesSafe } from "../agents.js";
|
|
10
10
|
import {
|
|
11
11
|
crossAppModelsEnabled,
|
|
12
12
|
loadConfig,
|
|
@@ -77,7 +77,7 @@ const IMPEL_CODEX_RUNTIME_OVERRIDES = [
|
|
|
77
77
|
"features.code_mode_host=false",
|
|
78
78
|
];
|
|
79
79
|
|
|
80
|
-
export function impelLaunchArguments(tool, argv) {
|
|
80
|
+
export function impelLaunchArguments(tool, argv, { codexAgentProfile = null } = {}) {
|
|
81
81
|
if (!RUNTIME_BRAND.features.agents) return [...argv];
|
|
82
82
|
if (tool === "claude") {
|
|
83
83
|
return ["--append-system-prompt", IMPEL_CLAUDE_PARENT_DELEGATION_INSTRUCTIONS, ...argv];
|
|
@@ -86,8 +86,10 @@ export function impelLaunchArguments(tool, argv) {
|
|
|
86
86
|
// `-c` is a Codex global option, so it must precede subcommands such as
|
|
87
87
|
// `exec`, `resume`, and `mcp`. JSON strings are valid TOML basic strings.
|
|
88
88
|
return [
|
|
89
|
-
"
|
|
90
|
-
|
|
89
|
+
...(codexAgentProfile ? ["--profile", codexAgentProfile] : [
|
|
90
|
+
"-c",
|
|
91
|
+
`developer_instructions=${JSON.stringify(IMPEL_CODEX_PARENT_DELEGATION_INSTRUCTIONS)}`,
|
|
92
|
+
]),
|
|
91
93
|
...IMPEL_CODEX_RUNTIME_OVERRIDES.flatMap((override) => ["-c", override]),
|
|
92
94
|
...argv,
|
|
93
95
|
];
|
|
@@ -95,6 +97,91 @@ export function impelLaunchArguments(tool, argv) {
|
|
|
95
97
|
throw new Error(`unsupported CLI launcher: ${tool}`);
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
const CODEX_AGENT_LOCKED_OPTIONS = new Set([
|
|
101
|
+
"-c",
|
|
102
|
+
"--config",
|
|
103
|
+
"-m",
|
|
104
|
+
"--model",
|
|
105
|
+
"-p",
|
|
106
|
+
"--profile",
|
|
107
|
+
"-s",
|
|
108
|
+
"--sandbox",
|
|
109
|
+
"-a",
|
|
110
|
+
"--ask-for-approval",
|
|
111
|
+
"--enable",
|
|
112
|
+
"--disable",
|
|
113
|
+
"--full-auto",
|
|
114
|
+
"--approve-for-me",
|
|
115
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
116
|
+
"--dangerously-bypass-hook-trust",
|
|
117
|
+
"--oss",
|
|
118
|
+
"--local-provider",
|
|
119
|
+
"--search",
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
function codexAgentLockedOption(argument) {
|
|
123
|
+
if (CODEX_AGENT_LOCKED_OPTIONS.has(argument)) return argument;
|
|
124
|
+
const withEquals = [...CODEX_AGENT_LOCKED_OPTIONS].find((option) =>
|
|
125
|
+
argument.startsWith(`${option}=`)
|
|
126
|
+
);
|
|
127
|
+
if (withEquals) return withEquals;
|
|
128
|
+
return ["-c", "-m", "-p", "-s", "-a"].find((option) =>
|
|
129
|
+
argument.length > option.length && argument.startsWith(option)
|
|
130
|
+
) || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function parseCodexAgentLaunchArguments(argv) {
|
|
134
|
+
const passthrough = [];
|
|
135
|
+
let selector = null;
|
|
136
|
+
let literal = false;
|
|
137
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
138
|
+
const argument = argv[index];
|
|
139
|
+
if (literal) {
|
|
140
|
+
passthrough.push(argument);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (argument === "--") {
|
|
144
|
+
literal = true;
|
|
145
|
+
passthrough.push(argument);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (argument === "--agent" || argument.startsWith("--agent=")) {
|
|
149
|
+
if (selector !== null) throw new Error("`--agent` may be specified only once");
|
|
150
|
+
const value = argument === "--agent" ? argv[index += 1] : argument.slice("--agent=".length);
|
|
151
|
+
if (typeof value !== "string" || !value.trim() || value.startsWith("-")) {
|
|
152
|
+
throw new Error("`--agent` requires an exact agent ID or title");
|
|
153
|
+
}
|
|
154
|
+
selector = value.trim();
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
passthrough.push(argument);
|
|
158
|
+
}
|
|
159
|
+
if (selector !== null) {
|
|
160
|
+
for (const argument of passthrough) {
|
|
161
|
+
if (argument === "--") break;
|
|
162
|
+
const locked = codexAgentLockedOption(argument);
|
|
163
|
+
if (locked) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`${locked} cannot be used with \`--agent\`; the managed profile locks model, instructions, tools, approvals, and sandbox policy`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { selector, argv: passthrough };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function codexJsonOutput(argv) {
|
|
174
|
+
return argv.some((argument) => argument === "--json" || argument.startsWith("--json="));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function agentSyncLogger(argv) {
|
|
178
|
+
if (!codexJsonOutput(argv)) return console;
|
|
179
|
+
return {
|
|
180
|
+
log: (...args) => console.error(...args),
|
|
181
|
+
warn: (...args) => console.error(...args),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
98
185
|
function deleteEnvironmentKeys(environment, keys) {
|
|
99
186
|
for (const key of keys) delete environment[key];
|
|
100
187
|
}
|
|
@@ -174,6 +261,17 @@ function runNativeCli(tool, argv, environment) {
|
|
|
174
261
|
}
|
|
175
262
|
|
|
176
263
|
export async function cmdLaunch(tool, argv) {
|
|
264
|
+
let nativeArgv = [...argv];
|
|
265
|
+
let codexAgentSelector = null;
|
|
266
|
+
if (tool === "codex") {
|
|
267
|
+
try {
|
|
268
|
+
({ selector: codexAgentSelector, argv: nativeArgv } = parseCodexAgentLaunchArguments(argv));
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(`impel codex: ${error.message}`);
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
177
275
|
const config = loadConfig();
|
|
178
276
|
if (!config?.pat) {
|
|
179
277
|
console.error(`impel ${tool}: not authenticated. Run \`impel auth\` first.`);
|
|
@@ -199,7 +297,7 @@ export async function cmdLaunch(tool, argv) {
|
|
|
199
297
|
return;
|
|
200
298
|
}
|
|
201
299
|
const gatewayCredential = tenantCredential(config.pat, tenantId);
|
|
202
|
-
if (!localInformationRequest(
|
|
300
|
+
if (!localInformationRequest(nativeArgv)) {
|
|
203
301
|
try {
|
|
204
302
|
await assertLiveProviderReadiness({
|
|
205
303
|
config,
|
|
@@ -252,13 +350,33 @@ export async function cmdLaunch(tool, argv) {
|
|
|
252
350
|
credential: gatewayCredential,
|
|
253
351
|
tenantId,
|
|
254
352
|
staleOnly: true,
|
|
353
|
+
logger: agentSyncLogger(nativeArgv),
|
|
255
354
|
});
|
|
256
355
|
}
|
|
257
356
|
|
|
357
|
+
let codexAgentProfile = null;
|
|
358
|
+
if (codexAgentSelector !== null) {
|
|
359
|
+
try {
|
|
360
|
+
codexAgentProfile = resolveManagedCodexAgentProfile(
|
|
361
|
+
agentProfile.root,
|
|
362
|
+
tenantId,
|
|
363
|
+
codexAgentSelector,
|
|
364
|
+
).profileName;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error(`impel codex: ${redactSecretText(error?.message || error)}`);
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
258
372
|
// Both vendor CLIs shell out to `git` for plugin/marketplace operations; on
|
|
259
373
|
// a fresh Windows machine the installed git (typically the Impel-managed
|
|
260
374
|
// MinGit) sits off the inherited PATH, so repair it for the child session.
|
|
261
375
|
const { env: launchEnvironment } = withGitEnvironment(environment, { baseEnvironment: environment });
|
|
262
|
-
const exitCode = await runNativeCli(
|
|
376
|
+
const exitCode = await runNativeCli(
|
|
377
|
+
tool,
|
|
378
|
+
impelLaunchArguments(tool, nativeArgv, { codexAgentProfile }),
|
|
379
|
+
launchEnvironment,
|
|
380
|
+
);
|
|
263
381
|
if (exitCode !== 0) process.exitCode = exitCode;
|
|
264
382
|
}
|