impel-cli 0.20.42 → 0.20.44
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 +10 -0
- package/package.json +1 -1
- package/scripts/profile-native-codex.mjs +604 -57
- package/src/agents.js +217 -21
- package/src/cli.js +1 -0
- package/src/commands/agents.js +1 -0
- package/src/commands/launch.js +31 -7
- package/src/managedProfileVersion.js +1 -1
- package/src/nativeAgentTransport.js +9 -2
- package/src/selfInvocation.js +20 -1
package/src/agents.js
CHANGED
|
@@ -21,8 +21,10 @@ import {
|
|
|
21
21
|
IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV,
|
|
22
22
|
IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES,
|
|
23
23
|
IMPEL_NATIVE_AGENT_MCP_TARGET,
|
|
24
|
+
IMPEL_NATIVE_BENCHMARK_ENV,
|
|
24
25
|
impelCliInvocation,
|
|
25
26
|
impelNativeAgentMcpInvocation,
|
|
27
|
+
nativeBenchmarkHeaderValue,
|
|
26
28
|
} from "./selfInvocation.js";
|
|
27
29
|
import {
|
|
28
30
|
adapterAnswerFaithfulCompletionGuidance,
|
|
@@ -86,8 +88,11 @@ function eagerNativeAgentTransportEnabled() {
|
|
|
86
88
|
return process.env.IMPEL_NATIVE_EAGER_TRANSPORT !== "0";
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
function enabledProfileFlag(environment, name) {
|
|
90
|
-
|
|
91
|
+
function enabledProfileFlag(environment, name, { defaultEnabled = false } = {}) {
|
|
92
|
+
const configured = String(environment?.[name] || "").toLowerCase();
|
|
93
|
+
if (["1", "true"].includes(configured)) return true;
|
|
94
|
+
if (["0", "false"].includes(configured)) return false;
|
|
95
|
+
return defaultEnabled;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
export function nativeParentDirectEnabled(environment = process.env) {
|
|
@@ -95,7 +100,11 @@ export function nativeParentDirectEnabled(environment = process.env) {
|
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
export function nativeSlashCommandsEnabled(environment = process.env) {
|
|
98
|
-
|
|
103
|
+
// This surface is additive: the relay agent remains installed. Keep the
|
|
104
|
+
// explicit false literals as a symmetric rollback for managed profiles.
|
|
105
|
+
return enabledProfileFlag(environment, IMPEL_NATIVE_SLASH_COMMANDS_ENV, {
|
|
106
|
+
defaultEnabled: true,
|
|
107
|
+
});
|
|
99
108
|
}
|
|
100
109
|
const MAX_RETIRED_AGENT_BINDINGS = 50;
|
|
101
110
|
const MAX_NATIVE_AGENT_STATE_BYTES = 512 * 1024;
|
|
@@ -670,6 +679,116 @@ function nativeToolName(toolName) {
|
|
|
670
679
|
return `${nativeToolNamespace()}__${toolName}`;
|
|
671
680
|
}
|
|
672
681
|
|
|
682
|
+
const CLAUDE_PARENT_DIRECT_TOOL_PERMISSIONS = Object.freeze([
|
|
683
|
+
NATIVE_AGENT_ANSWER_TOOL,
|
|
684
|
+
NATIVE_AGENT_RESUME_TOOL,
|
|
685
|
+
].map((tool) => nativeToolName(tool)));
|
|
686
|
+
|
|
687
|
+
function parentDirectPermissionTools(renderedAgents) {
|
|
688
|
+
const trusted = new Set(CLAUDE_PARENT_DIRECT_TOOL_PERMISSIONS);
|
|
689
|
+
const tools = [...new Set(renderedAgents.flatMap((agent) =>
|
|
690
|
+
agent.parentLaunchDefinition?.tools || []
|
|
691
|
+
))];
|
|
692
|
+
if (!tools.every((tool) => trusted.has(tool))) {
|
|
693
|
+
throw new Error("the managed Claude parent-direct permission grant is invalid");
|
|
694
|
+
}
|
|
695
|
+
return tools;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function priorParentDirectPermissionGrants(prior, ownsManagedProfile) {
|
|
699
|
+
const grants = ownsManagedProfile ? prior?.nativeParentDirectPermissionGrants : null;
|
|
700
|
+
const trusted = new Set(CLAUDE_PARENT_DIRECT_TOOL_PERMISSIONS);
|
|
701
|
+
if (!Array.isArray(grants)
|
|
702
|
+
|| new Set(grants).size !== grants.length
|
|
703
|
+
|| !grants.every((tool) => trusted.has(tool))) return [];
|
|
704
|
+
return [...grants];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function readClaudeSettingsForPermissions(settingsPath) {
|
|
708
|
+
let raw;
|
|
709
|
+
try {
|
|
710
|
+
const stat = fs.lstatSync(settingsPath);
|
|
711
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
712
|
+
throw new Error(`refusing to update unsafe Claude settings path ${settingsPath}`);
|
|
713
|
+
}
|
|
714
|
+
raw = fs.readFileSync(settingsPath, "utf8");
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (error?.code === "ENOENT") return { settings: {} };
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const settings = raw.trim() ? JSON.parse(raw) : {};
|
|
721
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error();
|
|
722
|
+
return { settings };
|
|
723
|
+
} catch {
|
|
724
|
+
throw new Error(`${settingsPath} exists but isn't a valid JSON object. Fix or remove it, then re-run.`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function syncClaudeParentDirectPermissions(root, desiredTools, priorGrants) {
|
|
729
|
+
if (desiredTools.length === 0 && priorGrants.length === 0) return [];
|
|
730
|
+
|
|
731
|
+
const settingsPath = path.join(root, "settings.json");
|
|
732
|
+
const { settings } = readClaudeSettingsForPermissions(settingsPath);
|
|
733
|
+
const permissionsExisted = Object.hasOwn(settings, "permissions");
|
|
734
|
+
if (permissionsExisted && (
|
|
735
|
+
!settings.permissions
|
|
736
|
+
|| typeof settings.permissions !== "object"
|
|
737
|
+
|| Array.isArray(settings.permissions)
|
|
738
|
+
)) {
|
|
739
|
+
throw new Error(`${settingsPath} has an invalid permissions value. Fix or remove it, then re-run.`);
|
|
740
|
+
}
|
|
741
|
+
const permissions = permissionsExisted ? { ...settings.permissions } : {};
|
|
742
|
+
if (Object.hasOwn(permissions, "allow") && (
|
|
743
|
+
!Array.isArray(permissions.allow)
|
|
744
|
+
|| !permissions.allow.every((rule) => typeof rule === "string")
|
|
745
|
+
)) {
|
|
746
|
+
throw new Error(`${settingsPath} has an invalid permissions.allow value. Fix or remove it, then re-run.`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const desired = new Set(desiredTools);
|
|
750
|
+
const previouslyManaged = new Set(priorGrants);
|
|
751
|
+
const allow = Array.isArray(permissions.allow) ? [...permissions.allow] : [];
|
|
752
|
+
const nextAllow = allow.filter((rule) => !previouslyManaged.has(rule) || desired.has(rule));
|
|
753
|
+
const managedGrants = [];
|
|
754
|
+
for (const tool of desiredTools) {
|
|
755
|
+
if (previouslyManaged.has(tool)) {
|
|
756
|
+
managedGrants.push(tool);
|
|
757
|
+
if (!nextAllow.includes(tool)) nextAllow.push(tool);
|
|
758
|
+
} else if (!nextAllow.includes(tool)) {
|
|
759
|
+
managedGrants.push(tool);
|
|
760
|
+
nextAllow.push(tool);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// permissions.allow is Claude's ordinary pre-approval surface. Record only
|
|
765
|
+
// entries this sync actually adds, so turning the experiment back off can
|
|
766
|
+
// remove them without claiming any operator-authored permission rule.
|
|
767
|
+
if (JSON.stringify(allow) !== JSON.stringify(nextAllow)) {
|
|
768
|
+
settings.permissions = { ...permissions, allow: nextAllow };
|
|
769
|
+
atomicPrivateWrite(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
770
|
+
}
|
|
771
|
+
return managedGrants;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function claudeParentDirectPermissionsCurrent(root, manifest) {
|
|
775
|
+
try {
|
|
776
|
+
const desiredTools = parentDirectPermissionTools(
|
|
777
|
+
Array.isArray(manifest.agents) ? manifest.agents.map((record) => ({
|
|
778
|
+
parentLaunchDefinition: record?.launchMode === "parent-direct"
|
|
779
|
+
? record.parentLaunchDefinition
|
|
780
|
+
: null,
|
|
781
|
+
})) : [],
|
|
782
|
+
);
|
|
783
|
+
if (desiredTools.length === 0) return true;
|
|
784
|
+
const { settings } = readClaudeSettingsForPermissions(path.join(root, "settings.json"));
|
|
785
|
+
const allow = settings.permissions?.allow;
|
|
786
|
+
return Array.isArray(allow) && desiredTools.every((tool) => allow.includes(tool));
|
|
787
|
+
} catch {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
673
792
|
function codexAdapterInstructions(tenantId, agent) {
|
|
674
793
|
const contextRequirement = agent.requiredContext.length
|
|
675
794
|
? ` Required context keys are ${JSON.stringify(agent.requiredContext)}; if any are absent, ask for them before starting the run.`
|
|
@@ -814,7 +933,7 @@ function claudeParentDirectDefinition({ tenantId, agent, invocation }) {
|
|
|
814
933
|
return {
|
|
815
934
|
prompt: claudeParentDirectInstructions(tenantId, agent),
|
|
816
935
|
permissionMode: "bypassPermissions",
|
|
817
|
-
tools: [
|
|
936
|
+
tools: [...CLAUDE_PARENT_DIRECT_TOOL_PERMISSIONS],
|
|
818
937
|
mcpServers: [{
|
|
819
938
|
[MANAGED_AGENT_MCP_SERVER]: {
|
|
820
939
|
type: "stdio",
|
|
@@ -826,11 +945,35 @@ function claudeParentDirectDefinition({ tenantId, agent, invocation }) {
|
|
|
826
945
|
};
|
|
827
946
|
}
|
|
828
947
|
|
|
948
|
+
function nativeAgentTelemetryEnvironment(environment = process.env) {
|
|
949
|
+
return Object.fromEntries(IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES.flatMap((name) => {
|
|
950
|
+
const value = environment?.[name];
|
|
951
|
+
return typeof value === "string" && value.length > 0 ? [[name, value]] : [];
|
|
952
|
+
}));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function telemetryEnvironmentMatches(recorded, expected) {
|
|
956
|
+
const normalizedRecorded = recorded === undefined ? {} : recorded;
|
|
957
|
+
return normalizedRecorded
|
|
958
|
+
&& typeof normalizedRecorded === "object"
|
|
959
|
+
&& !Array.isArray(normalizedRecorded)
|
|
960
|
+
&& JSON.stringify(normalizedRecorded) === JSON.stringify(expected);
|
|
961
|
+
}
|
|
962
|
+
|
|
829
963
|
function codexInvocationEnvironment(invocation, { durableProfile = false } = {}) {
|
|
830
964
|
const entries = Object.entries(invocation.env || {});
|
|
831
965
|
const transient = new Set(IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES);
|
|
966
|
+
// Ordinary durable profiles must never retain a one-off telemetry path.
|
|
967
|
+
// The native Codex profiler is different: it generates a throwaway profile
|
|
968
|
+
// for the whole benchmark cohort and needs Codex's explicit MCP environment
|
|
969
|
+
// map to carry that cohort path into the server process. The isolated root is
|
|
970
|
+
// removed after the cohort, so benchmark-tagged telemetry cannot linger in
|
|
971
|
+
// an operator's managed profile.
|
|
972
|
+
const isolatedBenchmark = nativeBenchmarkHeaderValue(invocation.env || {}) !== null;
|
|
832
973
|
return [
|
|
833
|
-
...(durableProfile
|
|
974
|
+
...(durableProfile && !isolatedBenchmark
|
|
975
|
+
? entries.filter(([key]) => !transient.has(key))
|
|
976
|
+
: entries),
|
|
834
977
|
[IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV, String(CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS)],
|
|
835
978
|
];
|
|
836
979
|
}
|
|
@@ -914,6 +1057,7 @@ function boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
|
914
1057
|
policyFingerprint = nativeAgentPolicyFingerprint(agent),
|
|
915
1058
|
mode = usesDirectAnswer(agent) ? "answer" : "durable",
|
|
916
1059
|
answerToolAttribution = false,
|
|
1060
|
+
environment = process.env,
|
|
917
1061
|
} = {}) {
|
|
918
1062
|
const bound = !invocation
|
|
919
1063
|
? impelNativeAgentMcpInvocation({
|
|
@@ -922,7 +1066,7 @@ function boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
|
922
1066
|
scopeParam: agent.scopeParam,
|
|
923
1067
|
policyFingerprint,
|
|
924
1068
|
mode,
|
|
925
|
-
})
|
|
1069
|
+
}, { environment })
|
|
926
1070
|
: (() => {
|
|
927
1071
|
const mcpIndex = invocation.args?.lastIndexOf("mcp") ?? -1;
|
|
928
1072
|
if (mcpIndex < 0) throw new Error("native-agent MCP invocation has no mcp command");
|
|
@@ -948,6 +1092,7 @@ function boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
|
948
1092
|
export function renderManagedAgents(client, tenantId, agents, invocation = null, {
|
|
949
1093
|
directCodeMode = true,
|
|
950
1094
|
nativeParentDirect = false,
|
|
1095
|
+
environment = process.env,
|
|
951
1096
|
} = {}) {
|
|
952
1097
|
if (client !== "claude" && client !== "codex") throw new Error(`unknown agent client ${client}`);
|
|
953
1098
|
const normalizedTenant = normalizeTenantId(tenantId);
|
|
@@ -965,6 +1110,7 @@ export function renderManagedAgents(client, tenantId, agents, invocation = null,
|
|
|
965
1110
|
const parentDirect = client === "claude" && nativeParentDirect && usesDirectAnswer(agent);
|
|
966
1111
|
const boundInvocation = boundNativeAgentInvocation(normalizedTenant, agent, invocation, {
|
|
967
1112
|
answerToolAttribution: parentDirect,
|
|
1113
|
+
environment,
|
|
968
1114
|
});
|
|
969
1115
|
const claudeRendered = client === "claude" && !parentDirect
|
|
970
1116
|
? renderClaudeAgent({ tenantId: normalizedTenant, agent, name, invocation: boundInvocation })
|
|
@@ -1059,7 +1205,10 @@ export function renderManagedClaudeSlashCommands(tenantId, agents, invocation =
|
|
|
1059
1205
|
});
|
|
1060
1206
|
}
|
|
1061
1207
|
|
|
1062
|
-
function renderRetiredManagedAgents(client, tenantId, bindings, active, invocation = null, {
|
|
1208
|
+
function renderRetiredManagedAgents(client, tenantId, bindings, active, invocation = null, {
|
|
1209
|
+
directCodeMode = true,
|
|
1210
|
+
environment = process.env,
|
|
1211
|
+
} = {}) {
|
|
1063
1212
|
const usedNames = new Set(active.map(({ name }) => name));
|
|
1064
1213
|
const usedFiles = new Set(active.map(({ fileName }) => fileName));
|
|
1065
1214
|
return bindings.map((binding) => {
|
|
@@ -1097,6 +1246,7 @@ function renderRetiredManagedAgents(client, tenantId, bindings, active, invocati
|
|
|
1097
1246
|
const boundInvocation = boundNativeAgentInvocation(tenantId, agent, invocation, {
|
|
1098
1247
|
policyFingerprint: binding.policyFingerprint,
|
|
1099
1248
|
mode: "recovery",
|
|
1249
|
+
environment,
|
|
1100
1250
|
});
|
|
1101
1251
|
const claudeRendered = client === "claude"
|
|
1102
1252
|
? renderClaudeAgent({ tenantId, agent, name, invocation: boundInvocation, recoveryOnly: true })
|
|
@@ -1233,7 +1383,11 @@ function validateClaudeLaunchDefinition(record, tenantId) {
|
|
|
1233
1383
|
throw new Error("the managed Claude launch definition is invalid; run `impel agents sync claude`");
|
|
1234
1384
|
}
|
|
1235
1385
|
const server = definition.mcpServers[0][MANAGED_AGENT_MCP_SERVER];
|
|
1236
|
-
const allowedEnvironment = new Set([
|
|
1386
|
+
const allowedEnvironment = new Set([
|
|
1387
|
+
IMPEL_MANAGED_MCP_ENV,
|
|
1388
|
+
...IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES,
|
|
1389
|
+
IMPEL_NATIVE_BENCHMARK_ENV,
|
|
1390
|
+
]);
|
|
1237
1391
|
if (!exactObjectKeys(server, ["type", "command", "args", "env"])
|
|
1238
1392
|
|| server.type !== "stdio"
|
|
1239
1393
|
|| typeof server.command !== "string"
|
|
@@ -1274,8 +1428,7 @@ function validateClaudeLaunchDefinition(record, tenantId) {
|
|
|
1274
1428
|
|
|
1275
1429
|
function validateClaudeParentLaunchDefinition(record, tenantId) {
|
|
1276
1430
|
const definition = record.parentLaunchDefinition;
|
|
1277
|
-
const expectedTools =
|
|
1278
|
-
.map((name) => nativeToolName(name));
|
|
1431
|
+
const expectedTools = CLAUDE_PARENT_DIRECT_TOOL_PERMISSIONS;
|
|
1279
1432
|
if (!exactObjectKeys(definition, ["prompt", "permissionMode", "tools", "mcpServers"])
|
|
1280
1433
|
|| definition.prompt !== claudeParentDirectInstructions(tenantId, record)
|
|
1281
1434
|
|| definition.permissionMode !== "bypassPermissions"
|
|
@@ -1286,7 +1439,11 @@ function validateClaudeParentLaunchDefinition(record, tenantId) {
|
|
|
1286
1439
|
throw new Error("the managed Claude parent-direct definition is invalid; run `impel agents sync claude`");
|
|
1287
1440
|
}
|
|
1288
1441
|
const server = definition.mcpServers[0][MANAGED_AGENT_MCP_SERVER];
|
|
1289
|
-
const allowedEnvironment = new Set([
|
|
1442
|
+
const allowedEnvironment = new Set([
|
|
1443
|
+
IMPEL_MANAGED_MCP_ENV,
|
|
1444
|
+
...IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES,
|
|
1445
|
+
IMPEL_NATIVE_BENCHMARK_ENV,
|
|
1446
|
+
]);
|
|
1290
1447
|
if (!exactObjectKeys(server, ["type", "command", "args", "env"])
|
|
1291
1448
|
|| server.type !== "stdio"
|
|
1292
1449
|
|| typeof server.command !== "string"
|
|
@@ -1519,24 +1676,39 @@ function profileIsFresh(profile, tenantId, now, ttlMs) {
|
|
|
1519
1676
|
if (!Array.isArray(manifest.files) || !manifest.contentDigests
|
|
1520
1677
|
|| typeof manifest.contentDigests !== "object"
|
|
1521
1678
|
|| Array.isArray(manifest.contentDigests)) return false;
|
|
1679
|
+
const profileEnvironment = profile.environment ?? process.env;
|
|
1522
1680
|
const expectedDirectProfiles = profile.client === "codex" && profile.directProfiles !== false;
|
|
1523
1681
|
const expectedDirectCodeMode = profile.client === "codex" && profile.directCodeMode !== false;
|
|
1524
1682
|
const expectedNativeParentDirect = profile.client === "claude"
|
|
1525
|
-
&& (profile.nativeParentDirect ?? nativeParentDirectEnabled());
|
|
1683
|
+
&& (profile.nativeParentDirect ?? nativeParentDirectEnabled(profileEnvironment));
|
|
1526
1684
|
const expectedNativeSlashCommands = profile.client === "claude"
|
|
1527
|
-
&& (profile.nativeSlashCommands ?? nativeSlashCommandsEnabled());
|
|
1685
|
+
&& (profile.nativeSlashCommands ?? nativeSlashCommandsEnabled(profileEnvironment));
|
|
1528
1686
|
const expectedNativeIntercept = profile.client === "claude"
|
|
1529
|
-
&& (profile.nativeIntercept ?? nativeInterceptEnabled());
|
|
1687
|
+
&& (profile.nativeIntercept ?? nativeInterceptEnabled(profileEnvironment));
|
|
1530
1688
|
const expectedNativeInterceptTimeoutMs = expectedNativeIntercept
|
|
1531
|
-
? (profile.nativeInterceptTimeoutMs ?? nativeInterceptTimeoutMs())
|
|
1689
|
+
? (profile.nativeInterceptTimeoutMs ?? nativeInterceptTimeoutMs(profileEnvironment))
|
|
1532
1690
|
: null;
|
|
1691
|
+
const expectedTelemetryEnvironment = nativeAgentTelemetryEnvironment(
|
|
1692
|
+
profileEnvironment,
|
|
1693
|
+
);
|
|
1694
|
+
const expectedBenchmark = nativeBenchmarkHeaderValue(
|
|
1695
|
+
profileEnvironment,
|
|
1696
|
+
) !== null;
|
|
1533
1697
|
if (manifest.directProfiles !== expectedDirectProfiles
|
|
1534
1698
|
|| manifest.directCodeMode !== expectedDirectCodeMode
|
|
1535
1699
|
|| Boolean(manifest.nativeParentDirect) !== expectedNativeParentDirect
|
|
1536
1700
|
|| Boolean(manifest.nativeSlashCommands) !== expectedNativeSlashCommands
|
|
1537
1701
|
|| Boolean(manifest.nativeIntercept) !== expectedNativeIntercept
|
|
1702
|
+
|| !telemetryEnvironmentMatches(
|
|
1703
|
+
manifest.nativeAgentTelemetryEnvironment,
|
|
1704
|
+
expectedTelemetryEnvironment,
|
|
1705
|
+
)
|
|
1706
|
+
|| Boolean(manifest.nativeBenchmark) !== expectedBenchmark
|
|
1538
1707
|
|| (expectedNativeIntercept
|
|
1539
1708
|
&& manifest.nativeInterceptTimeoutMs !== expectedNativeInterceptTimeoutMs)) return false;
|
|
1709
|
+
if (expectedNativeParentDirect && !claudeParentDirectPermissionsCurrent(profile.root, manifest)) {
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1540
1712
|
const syncedAt = Date.parse(manifest.syncedAt || "");
|
|
1541
1713
|
if (!Number.isFinite(syncedAt) || now - syncedAt >= ttlMs) return false;
|
|
1542
1714
|
const artifacts = [
|
|
@@ -1565,10 +1737,14 @@ export function syncAgentProfile({
|
|
|
1565
1737
|
agents,
|
|
1566
1738
|
directProfiles = client === "codex",
|
|
1567
1739
|
directCodeMode = client === "codex",
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1740
|
+
environment = process.env,
|
|
1741
|
+
nativeParentDirect = client === "claude" && nativeParentDirectEnabled(environment),
|
|
1742
|
+
nativeSlashCommands = client === "claude" && nativeSlashCommandsEnabled(environment),
|
|
1743
|
+
nativeIntercept = client === "claude" && nativeInterceptEnabled(environment),
|
|
1744
|
+
nativeInterceptTimeoutMs: interceptTimeoutMs = nativeIntercept
|
|
1745
|
+
? nativeInterceptTimeoutMs(environment)
|
|
1746
|
+
: null,
|
|
1747
|
+
invocation = null,
|
|
1572
1748
|
now = Date.now(),
|
|
1573
1749
|
nativeAgentRunsRoot = path.join(CONFIG_DIR, "native-agent-runs"),
|
|
1574
1750
|
}) {
|
|
@@ -1588,9 +1764,10 @@ export function syncAgentProfile({
|
|
|
1588
1764
|
&& fs.lstatSync(commandsDir).isSymbolicLink()) {
|
|
1589
1765
|
throw new Error(`refusing to use symlinked native-agent path ${commandsDir}`);
|
|
1590
1766
|
}
|
|
1591
|
-
const active = renderManagedAgents(client, tenantId, agents,
|
|
1767
|
+
const active = renderManagedAgents(client, tenantId, agents, invocation, {
|
|
1592
1768
|
directCodeMode,
|
|
1593
1769
|
nativeParentDirect,
|
|
1770
|
+
environment,
|
|
1594
1771
|
});
|
|
1595
1772
|
const activeBindings = new Set(active.map((agent) =>
|
|
1596
1773
|
`${agent.agentId}\0${agent.scopeParam}\0${agent.policyFingerprint}`
|
|
@@ -1599,7 +1776,10 @@ export function syncAgentProfile({
|
|
|
1599
1776
|
.filter((binding) => !activeBindings.has(
|
|
1600
1777
|
`${binding.agentId}\0${binding.scopeParam}\0${binding.policyFingerprint}`,
|
|
1601
1778
|
));
|
|
1602
|
-
const retired = renderRetiredManagedAgents(client, tenantId, retiredBindings, active,
|
|
1779
|
+
const retired = renderRetiredManagedAgents(client, tenantId, retiredBindings, active, invocation, {
|
|
1780
|
+
directCodeMode,
|
|
1781
|
+
environment,
|
|
1782
|
+
});
|
|
1603
1783
|
const rendered = [...active, ...retired];
|
|
1604
1784
|
const commands = client === "claude" && nativeSlashCommands
|
|
1605
1785
|
? renderManagedClaudeSlashCommands(tenantId, agents)
|
|
@@ -1679,6 +1859,13 @@ export function syncAgentProfile({
|
|
|
1679
1859
|
}
|
|
1680
1860
|
}
|
|
1681
1861
|
}
|
|
1862
|
+
const parentDirectPermissions = client === "claude"
|
|
1863
|
+
? syncClaudeParentDirectPermissions(
|
|
1864
|
+
root,
|
|
1865
|
+
parentDirectPermissionTools(rendered),
|
|
1866
|
+
priorParentDirectPermissionGrants(prior, priorOwnsManagedFiles),
|
|
1867
|
+
)
|
|
1868
|
+
: null;
|
|
1682
1869
|
for (const agent of rendered) {
|
|
1683
1870
|
if (agent.fileName) atomicPrivateWrite(path.join(agentsDir, agent.fileName), agent.contents);
|
|
1684
1871
|
if (client === "codex" && directProfiles) atomicPrivateWrite(path.join(root, agent.profileFileName), agent.profileContents);
|
|
@@ -1755,6 +1942,8 @@ export function syncAgentProfile({
|
|
|
1755
1942
|
`commands/${command.fileName}`,
|
|
1756
1943
|
contentDigest(command.contents),
|
|
1757
1944
|
])).sort(([left], [right]) => left.localeCompare(right)));
|
|
1945
|
+
const telemetryEnvironment = nativeAgentTelemetryEnvironment(environment);
|
|
1946
|
+
const benchmark = nativeBenchmarkHeaderValue(environment);
|
|
1758
1947
|
atomicPrivateWrite(manifestPath, `${JSON.stringify({
|
|
1759
1948
|
version: MANAGED_AGENT_MANIFEST_VERSION,
|
|
1760
1949
|
tenantId,
|
|
@@ -1762,11 +1951,18 @@ export function syncAgentProfile({
|
|
|
1762
1951
|
directProfiles: client === "codex" && directProfiles,
|
|
1763
1952
|
directCodeMode: client === "codex" && directCodeMode,
|
|
1764
1953
|
...(client === "claude" && nativeParentDirect ? { nativeParentDirect: true } : {}),
|
|
1954
|
+
...(client === "claude" && parentDirectPermissions.length > 0 ? {
|
|
1955
|
+
nativeParentDirectPermissionGrants: parentDirectPermissions,
|
|
1956
|
+
} : {}),
|
|
1765
1957
|
...(client === "claude" && nativeSlashCommands ? { nativeSlashCommands: true } : {}),
|
|
1766
1958
|
...(client === "claude" && nativeIntercept ? {
|
|
1767
1959
|
nativeIntercept: true,
|
|
1768
1960
|
nativeInterceptTimeoutMs: interceptTimeoutMs,
|
|
1769
1961
|
} : {}),
|
|
1962
|
+
...(Object.keys(telemetryEnvironment).length > 0 ? {
|
|
1963
|
+
nativeAgentTelemetryEnvironment: telemetryEnvironment,
|
|
1964
|
+
} : {}),
|
|
1965
|
+
...(benchmark ? { nativeBenchmark: true } : {}),
|
|
1770
1966
|
syncedAt: new Date(now).toISOString(),
|
|
1771
1967
|
files: [...currentFiles].sort(),
|
|
1772
1968
|
...(client === "claude" && nativeSlashCommands ? { commands: [...currentCommands].sort() } : {}),
|
package/src/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ Work:
|
|
|
38
38
|
impel claude --agent <id|exact-title> ... Run one fixed tenant agent without a parent hop
|
|
39
39
|
impel codex [args...] Launch Codex with an isolated Impel profile
|
|
40
40
|
impel codex --agent <id|exact-title> ... Run one fixed tenant agent without a parent hop
|
|
41
|
+
impel codex --benchmark ... Tag native-agent MCP calls as benchmark traffic
|
|
41
42
|
impel remote handoff|dispatch|handback Move or control provider-native sessions remotely
|
|
42
43
|
impel remote status|viewer|proxy Inspect, control, or connect to a remote run
|
|
43
44
|
impel tenant list List accessible organizations
|
package/src/commands/agents.js
CHANGED
|
@@ -38,6 +38,7 @@ export function managedAgentProfiles(client, options = {}) {
|
|
|
38
38
|
environment,
|
|
39
39
|
homeDir,
|
|
40
40
|
}),
|
|
41
|
+
environment,
|
|
41
42
|
...(client === "codex" ? {
|
|
42
43
|
directProfiles: profile.label !== "Codex CLI (native profile)",
|
|
43
44
|
directCodeMode: profile.label !== "Codex CLI (native profile)",
|
package/src/commands/launch.js
CHANGED
|
@@ -24,9 +24,8 @@ import { parentVerbatimRelayAppendix } from "../verbatimRelay.js";
|
|
|
24
24
|
import { withGitEnvironment } from "../skills.js";
|
|
25
25
|
import { assertProviderScopes, ensureTenantSelection, tenantCredential } from "../tenants.js";
|
|
26
26
|
import { maybePrintUpdateNotice } from "../updates.js";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
} from "../nativeProcess.js";
|
|
27
|
+
import { nativeSpawnInvocation } from "../nativeProcess.js";
|
|
28
|
+
import { IMPEL_NATIVE_BENCHMARK_ENV } from "../selfInvocation.js";
|
|
30
29
|
import { resolveReviewedVendorCliBinary } from "../vendorCliBinaries.js";
|
|
31
30
|
import { PINNED_VENDOR_CLI_VERSIONS } from "../vendorCliVersions.js";
|
|
32
31
|
import { RUNTIME_BRAND } from "../runtimeBrand.js";
|
|
@@ -305,6 +304,7 @@ function codexAgentLockedOption(argument) {
|
|
|
305
304
|
export function parseCodexAgentLaunchArguments(argv) {
|
|
306
305
|
const passthrough = [];
|
|
307
306
|
let selector = null;
|
|
307
|
+
let benchmark = false;
|
|
308
308
|
let literal = false;
|
|
309
309
|
for (let index = 0; index < argv.length; index += 1) {
|
|
310
310
|
const argument = argv[index];
|
|
@@ -317,6 +317,14 @@ export function parseCodexAgentLaunchArguments(argv) {
|
|
|
317
317
|
passthrough.push(argument);
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
320
|
+
if (argument === "--benchmark") {
|
|
321
|
+
if (benchmark) throw new Error("`--benchmark` may be specified only once");
|
|
322
|
+
benchmark = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (argument.startsWith("--benchmark=")) {
|
|
326
|
+
throw new Error("`--benchmark` does not accept a value");
|
|
327
|
+
}
|
|
320
328
|
if (argument === "--agent" || argument.startsWith("--agent=")) {
|
|
321
329
|
if (selector !== null) throw new Error("`--agent` may be specified only once");
|
|
322
330
|
const value = argument === "--agent" ? argv[index += 1] : argument.slice("--agent=".length);
|
|
@@ -339,7 +347,7 @@ export function parseCodexAgentLaunchArguments(argv) {
|
|
|
339
347
|
}
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
|
-
return { selector, argv: passthrough };
|
|
350
|
+
return { selector, benchmark, argv: passthrough };
|
|
343
351
|
}
|
|
344
352
|
|
|
345
353
|
function codexJsonOutput(argv) {
|
|
@@ -436,11 +444,16 @@ export async function cmdLaunch(tool, argv) {
|
|
|
436
444
|
let nativeArgv = [...argv];
|
|
437
445
|
let claudeAgentSelector = null;
|
|
438
446
|
let codexAgentSelector = null;
|
|
447
|
+
let codexBenchmark = false;
|
|
439
448
|
if (tool === "claude" && RUNTIME_BRAND.features.agents) {
|
|
440
449
|
({ selector: claudeAgentSelector, argv: nativeArgv } = parseClaudeAgentLaunchArguments(argv));
|
|
441
450
|
} else if (tool === "codex") {
|
|
442
451
|
try {
|
|
443
|
-
({
|
|
452
|
+
({
|
|
453
|
+
selector: codexAgentSelector,
|
|
454
|
+
benchmark: codexBenchmark,
|
|
455
|
+
argv: nativeArgv,
|
|
456
|
+
} = parseCodexAgentLaunchArguments(argv));
|
|
444
457
|
} catch (error) {
|
|
445
458
|
console.error(`impel codex: ${error.message}`);
|
|
446
459
|
process.exitCode = 1;
|
|
@@ -489,12 +502,18 @@ export async function cmdLaunch(tool, argv) {
|
|
|
489
502
|
}
|
|
490
503
|
}
|
|
491
504
|
const environment = { ...process.env };
|
|
505
|
+
if (codexBenchmark) environment[IMPEL_NATIVE_BENCHMARK_ENV] = "1";
|
|
492
506
|
environment.IMPEL_TENANT_ID = tenantId;
|
|
493
507
|
let agentProfile;
|
|
494
508
|
|
|
495
509
|
if (tool === "claude") {
|
|
496
510
|
const profile = ensureImpelClaudeProfile(gatewayUrl, tenantId, { crossAppModels });
|
|
497
|
-
agentProfile = {
|
|
511
|
+
agentProfile = {
|
|
512
|
+
client: "claude",
|
|
513
|
+
root: profile.configDir,
|
|
514
|
+
label: "Impel isolated Claude (impel claude)",
|
|
515
|
+
environment,
|
|
516
|
+
};
|
|
498
517
|
deleteEnvironmentKeys(environment, CLAUDE_DIRECT_AUTH_ENV);
|
|
499
518
|
delete environment.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY;
|
|
500
519
|
environment.CLAUDE_CONFIG_DIR = profile.configDir;
|
|
@@ -508,7 +527,12 @@ export async function cmdLaunch(tool, argv) {
|
|
|
508
527
|
environment.ANTHROPIC_AUTH_TOKEN = gatewayCredential;
|
|
509
528
|
} else if (tool === "codex") {
|
|
510
529
|
const profile = ensureImpelCodexProfile(gatewayUrl, tenantId);
|
|
511
|
-
agentProfile = {
|
|
530
|
+
agentProfile = {
|
|
531
|
+
client: "codex",
|
|
532
|
+
root: profile.codexHome,
|
|
533
|
+
label: "Impel isolated Codex (impel codex)",
|
|
534
|
+
environment,
|
|
535
|
+
};
|
|
512
536
|
deleteEnvironmentKeys(environment, CODEX_DIRECT_AUTH_ENV);
|
|
513
537
|
environment.CODEX_HOME = profile.codexHome;
|
|
514
538
|
environment[CODEX_GATEWAY_TOKEN_ENV] = gatewayCredential;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Fleet generation shared by managed-profile writers and upstream clients.
|
|
2
2
|
// Keep this isolated from apps.js so latency-sensitive transports do not load
|
|
3
3
|
// desktop bundle machinery just to identify their managed config contract.
|
|
4
|
-
export const CURRENT_CONFIG_VERSION =
|
|
4
|
+
export const CURRENT_CONFIG_VERSION = 36;
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { extractAnswerFinalText } from "./directAnswer.js";
|
|
26
26
|
import {
|
|
27
27
|
IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV,
|
|
28
|
+
nativeBenchmarkHeaderValue,
|
|
28
29
|
} from "./selfInvocation.js";
|
|
29
30
|
import { normalizeTenantId } from "./tenants.js";
|
|
30
31
|
import { renameWithWindowsRetry } from "./windowsFs.js";
|
|
@@ -40,6 +41,7 @@ export {
|
|
|
40
41
|
export const NATIVE_AGENT_HANDLE_SCHEMA = "impel.native-agent-run.v1";
|
|
41
42
|
export const NATIVE_AGENT_RESULT_SCHEMA = "impel.native-agent-result.v1";
|
|
42
43
|
export const NATIVE_AGENT_RECOVERY_SCHEMA = "impel.native-agent-recovery.v1";
|
|
44
|
+
export const NATIVE_AGENT_BENCHMARK_HEADER = "X-Impel-Client-Benchmark";
|
|
43
45
|
export const NATIVE_AGENT_CLIENT_BUILD = `cli/${JSON.parse(
|
|
44
46
|
fs.readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"),
|
|
45
47
|
).version}+manifest.v${CURRENT_CONFIG_VERSION}`;
|
|
@@ -52,8 +54,8 @@ const SAFE_FINGERPRINT_RE = /^[a-f0-9]{64}$/u;
|
|
|
52
54
|
const SAFE_INVOCATION_RE = /^[a-f0-9-]{36}$/u;
|
|
53
55
|
const DEFAULT_WAIT_SECONDS = 20;
|
|
54
56
|
const MAX_WAIT_SECONDS = 35;
|
|
55
|
-
const DEFAULT_ATTACHMENT_WINDOW_MS = 40_000;
|
|
56
|
-
const DEFAULT_UPSTREAM_TIMEOUT_MS = 42_000;
|
|
57
|
+
export const DEFAULT_ATTACHMENT_WINDOW_MS = 40_000;
|
|
58
|
+
export const DEFAULT_UPSTREAM_TIMEOUT_MS = 42_000;
|
|
57
59
|
const DEFAULT_ANSWER_HEDGE_MS = 38_000;
|
|
58
60
|
const DEFAULT_MAX_POLLS = 8;
|
|
59
61
|
const DEFAULT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -742,6 +744,7 @@ export class NativeAgentUpstreamSession {
|
|
|
742
744
|
answerHedgeMs = nativeAgentAnswerHedgeMs(),
|
|
743
745
|
setTimeoutImpl = setTimeout,
|
|
744
746
|
clearTimeoutImpl = clearTimeout,
|
|
747
|
+
environment = process.env,
|
|
745
748
|
}) {
|
|
746
749
|
this.endpoint = `${normalizeGatewayUrl(gatewayUrl)}/mcp`;
|
|
747
750
|
this.credential = credential;
|
|
@@ -755,6 +758,7 @@ export class NativeAgentUpstreamSession {
|
|
|
755
758
|
this.answerHedgeMs = Math.max(0, answerHedgeMs);
|
|
756
759
|
this.setTimeoutImpl = setTimeoutImpl;
|
|
757
760
|
this.clearTimeoutImpl = clearTimeoutImpl;
|
|
761
|
+
this.benchmarkHeaderValue = nativeBenchmarkHeaderValue(environment);
|
|
758
762
|
this.sessionId = null;
|
|
759
763
|
this.nextId = 1;
|
|
760
764
|
}
|
|
@@ -785,6 +789,9 @@ export class NativeAgentUpstreamSession {
|
|
|
785
789
|
Accept: "application/json, text/event-stream",
|
|
786
790
|
"X-Impel-Client-Request-Id": correlationId,
|
|
787
791
|
"X-Impel-Client-Build": NATIVE_AGENT_CLIENT_BUILD,
|
|
792
|
+
...(this.benchmarkHeaderValue
|
|
793
|
+
? { [NATIVE_AGENT_BENCHMARK_HEADER]: this.benchmarkHeaderValue }
|
|
794
|
+
: {}),
|
|
788
795
|
...(this.sessionId ? { "Mcp-Session-Id": this.sessionId } : {}),
|
|
789
796
|
},
|
|
790
797
|
body: JSON.stringify(message),
|
package/src/selfInvocation.js
CHANGED
|
@@ -82,9 +82,23 @@ export const IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES = [
|
|
|
82
82
|
"IMPEL_NATIVE_HOST",
|
|
83
83
|
"IMPEL_NATIVE_HOST_BUILD",
|
|
84
84
|
];
|
|
85
|
+
export const IMPEL_NATIVE_BENCHMARK_ENV = "IMPEL_NATIVE_BENCHMARK";
|
|
85
86
|
export const IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV = "IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_MS";
|
|
86
87
|
export const CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS = 100_000;
|
|
87
88
|
|
|
89
|
+
const SAFE_NATIVE_BENCHMARK_HEADER_VALUE = /^[\x21-\x7e]{1,128}$/u;
|
|
90
|
+
|
|
91
|
+
export function nativeBenchmarkHeaderValue(environment = process.env) {
|
|
92
|
+
const value = environment?.[IMPEL_NATIVE_BENCHMARK_ENV];
|
|
93
|
+
if (value === undefined) return null;
|
|
94
|
+
if (typeof value !== "string"
|
|
95
|
+
|| value !== "1"
|
|
96
|
+
|| !SAFE_NATIVE_BENCHMARK_HEADER_VALUE.test(value)) {
|
|
97
|
+
throw new Error(`${IMPEL_NATIVE_BENCHMARK_ENV} must be exactly 1 using visible ASCII`);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
function managedMcpEnvironment(environment = process.env) {
|
|
89
103
|
const telemetry = Object.fromEntries(
|
|
90
104
|
IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES.flatMap((name) => {
|
|
@@ -92,7 +106,12 @@ function managedMcpEnvironment(environment = process.env) {
|
|
|
92
106
|
return typeof value === "string" && value.length > 0 ? [[name, value]] : [];
|
|
93
107
|
}),
|
|
94
108
|
);
|
|
95
|
-
|
|
109
|
+
const benchmark = nativeBenchmarkHeaderValue(environment);
|
|
110
|
+
return {
|
|
111
|
+
[IMPEL_MANAGED_MCP_ENV]: "1",
|
|
112
|
+
...telemetry,
|
|
113
|
+
...(benchmark ? { [IMPEL_NATIVE_BENCHMARK_ENV]: benchmark } : {}),
|
|
114
|
+
};
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
export function impelMcpInvocation(args = [], options = {}) {
|