@kyo-so/cli 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.ja.md +225 -123
- package/README.md +225 -126
- package/README.zh-CN.md +225 -123
- package/dist/acp/AcpAgentProcess.d.ts +2 -1
- package/dist/acp/FakeAgentManager.d.ts +1 -1
- package/dist/bin/kyoso.js +35895 -34770
- package/dist/cli/openRouterAcpSmoke.d.ts +25 -0
- package/dist/cli/pluginRuntimeContract.d.ts +4 -4
- package/dist/cli/setup.d.ts +3 -2
- package/dist/config/loadConfig.d.ts +19 -0
- package/dist/config/projectScope.d.ts +1 -1
- package/dist/config/schema.d.ts +9 -0
- package/dist/core/constants.d.ts +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/index.js +580 -111
- package/dist/utils/env.d.ts +13 -0
- package/examples/codex-config.toml +4 -1
- package/examples/kyoso.toml +14 -0
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -169500,6 +169500,9 @@ function defineConfig(config) {
|
|
|
169500
169500
|
// src/core/runReview.ts
|
|
169501
169501
|
import { resolve as resolve6 } from "node:path";
|
|
169502
169502
|
|
|
169503
|
+
// src/config/schema.ts
|
|
169504
|
+
import { isAbsolute } from "node:path";
|
|
169505
|
+
|
|
169503
169506
|
// node_modules/zod/v4/classic/external.js
|
|
169504
169507
|
var exports_external = {};
|
|
169505
169508
|
__export(exports_external, {
|
|
@@ -183777,7 +183780,10 @@ function date4(params) {
|
|
|
183777
183780
|
// node_modules/zod/v4/classic/external.js
|
|
183778
183781
|
config(en_default());
|
|
183779
183782
|
// src/config/schema.ts
|
|
183780
|
-
var
|
|
183783
|
+
var CODEX_OPENROUTER_PROVIDER = "openrouter";
|
|
183784
|
+
var CODEX_DEFAULT_PROVIDER = "default";
|
|
183785
|
+
var CODEX_OPENROUTER_MODEL_REQUIRED_ISSUE = "codex_openrouter_model_required";
|
|
183786
|
+
var baseAgentSchema = exports_external.object({
|
|
183781
183787
|
enabled: exports_external.boolean().default(true),
|
|
183782
183788
|
type: exports_external.literal("acp").default("acp"),
|
|
183783
183789
|
command: exports_external.string(),
|
|
@@ -183799,6 +183805,24 @@ var agentSchema = exports_external.object({
|
|
|
183799
183805
|
envWhitelist: exports_external.array(exports_external.string())
|
|
183800
183806
|
})
|
|
183801
183807
|
});
|
|
183808
|
+
var codexAgentSchema = baseAgentSchema.extend({
|
|
183809
|
+
provider: exports_external.enum([CODEX_OPENROUTER_PROVIDER, CODEX_DEFAULT_PROVIDER]).optional(),
|
|
183810
|
+
allowProjectProvider: exports_external.array(exports_external.string().min(1).refine(isAbsolute, {
|
|
183811
|
+
message: "must contain only absolute project directory paths for exact matching"
|
|
183812
|
+
})).default([])
|
|
183813
|
+
}).superRefine((agent, context) => {
|
|
183814
|
+
if (agent.provider !== CODEX_OPENROUTER_PROVIDER || (agent.model?.trim().length ?? 0) > 0) {
|
|
183815
|
+
return;
|
|
183816
|
+
}
|
|
183817
|
+
context.addIssue({
|
|
183818
|
+
code: exports_external.ZodIssueCode.custom,
|
|
183819
|
+
path: ["model"],
|
|
183820
|
+
message: 'model must be a non-empty string when provider is "openrouter".',
|
|
183821
|
+
params: {
|
|
183822
|
+
kyosoIssue: CODEX_OPENROUTER_MODEL_REQUIRED_ISSUE
|
|
183823
|
+
}
|
|
183824
|
+
});
|
|
183825
|
+
});
|
|
183802
183826
|
var kyosoConfigSchema = exports_external.object({
|
|
183803
183827
|
entrypoints: exports_external.object({
|
|
183804
183828
|
mcp: exports_external.boolean(),
|
|
@@ -183811,8 +183835,8 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183811
183835
|
diffReview: exports_external.boolean()
|
|
183812
183836
|
}),
|
|
183813
183837
|
agents: exports_external.object({
|
|
183814
|
-
codex:
|
|
183815
|
-
claude:
|
|
183838
|
+
codex: codexAgentSchema,
|
|
183839
|
+
claude: baseAgentSchema
|
|
183816
183840
|
}),
|
|
183817
183841
|
workspace: exports_external.object({
|
|
183818
183842
|
mode: exports_external.literal("temp_snapshot"),
|
|
@@ -183865,7 +183889,7 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183865
183889
|
})
|
|
183866
183890
|
});
|
|
183867
183891
|
function agentConfigLeafPaths(agent) {
|
|
183868
|
-
|
|
183892
|
+
const paths = [
|
|
183869
183893
|
`agents.${agent}.enabled`,
|
|
183870
183894
|
`agents.${agent}.type`,
|
|
183871
183895
|
`agents.${agent}.command`,
|
|
@@ -183881,6 +183905,10 @@ function agentConfigLeafPaths(agent) {
|
|
|
183881
183905
|
`agents.${agent}.auth.recommendedEnv`,
|
|
183882
183906
|
`agents.${agent}.auth.envWhitelist`
|
|
183883
183907
|
];
|
|
183908
|
+
if (agent === "codex") {
|
|
183909
|
+
paths.push("agents.codex.provider", "agents.codex.allowProjectProvider");
|
|
183910
|
+
}
|
|
183911
|
+
return paths;
|
|
183884
183912
|
}
|
|
183885
183913
|
var kyosoConfigKnownLeafPaths = [
|
|
183886
183914
|
"entrypoints.mcp",
|
|
@@ -183953,9 +183981,10 @@ var defaultConfig = {
|
|
|
183953
183981
|
enabled: true,
|
|
183954
183982
|
type: "acp",
|
|
183955
183983
|
command: "npx",
|
|
183956
|
-
args: ["-y", "@agentclientprotocol/codex-acp@1.1.
|
|
183984
|
+
args: ["-y", "@agentclientprotocol/codex-acp@1.1.2"],
|
|
183957
183985
|
role: "implementation_reviewer",
|
|
183958
183986
|
timeoutMs: 120000,
|
|
183987
|
+
allowProjectProvider: [],
|
|
183959
183988
|
env: {
|
|
183960
183989
|
INITIAL_AGENT_MODE: "read-only",
|
|
183961
183990
|
KYOSO_CHILD_AGENT: "1"
|
|
@@ -183977,7 +184006,7 @@ var defaultConfig = {
|
|
|
183977
184006
|
enabled: true,
|
|
183978
184007
|
type: "acp",
|
|
183979
184008
|
command: "npx",
|
|
183980
|
-
args: ["-y", "@agentclientprotocol/claude-agent-acp@0.
|
|
184009
|
+
args: ["-y", "@agentclientprotocol/claude-agent-acp@0.58.1"],
|
|
183981
184010
|
role: "architecture_security_reviewer",
|
|
183982
184011
|
timeoutMs: 300000,
|
|
183983
184012
|
env: {
|
|
@@ -184068,17 +184097,21 @@ var defaultConfig = {
|
|
|
184068
184097
|
};
|
|
184069
184098
|
|
|
184070
184099
|
// src/config/loadConfig.ts
|
|
184071
|
-
import { access, readFile as readFile3 } from "node:fs/promises";
|
|
184100
|
+
import { access, readFile as readFile3, realpath } from "node:fs/promises";
|
|
184072
184101
|
import { homedir as homedir2 } from "node:os";
|
|
184073
184102
|
import { stderr, stdin } from "node:process";
|
|
184074
|
-
import { extname as extname2, join as join2, resolve as resolve2 } from "node:path";
|
|
184103
|
+
import { dirname as dirname3, extname as extname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
184075
184104
|
import { createInterface } from "node:readline/promises";
|
|
184076
184105
|
|
|
184077
184106
|
// src/config/projectScope.ts
|
|
184078
184107
|
var PROJECT_GLOBAL_ONLY_MESSAGE = "Move global-only settings to the user global config:";
|
|
184108
|
+
var PROJECT_GLOBAL_ONLY_REASONS = {
|
|
184109
|
+
"agents.codex.allowProjectProvider": "must be a user-global exact project-directory allowlist"
|
|
184110
|
+
};
|
|
184079
184111
|
var kyosoConfigOverridePaths = [
|
|
184080
184112
|
"agents.codex.enabled",
|
|
184081
184113
|
"agents.codex.model",
|
|
184114
|
+
"agents.codex.provider",
|
|
184082
184115
|
"agents.codex.effort",
|
|
184083
184116
|
"agents.codex.role",
|
|
184084
184117
|
"agents.codex.timeoutMs",
|
|
@@ -184118,6 +184151,11 @@ function collectProjectScopeViolations(config2) {
|
|
|
184118
184151
|
const violations = [];
|
|
184119
184152
|
for (const leaf of leaves) {
|
|
184120
184153
|
const path = leaf.path.join(".");
|
|
184154
|
+
const globalOnlyReason = PROJECT_GLOBAL_ONLY_REASONS[path];
|
|
184155
|
+
if (globalOnlyReason) {
|
|
184156
|
+
violations.push({ path, reason: globalOnlyReason });
|
|
184157
|
+
continue;
|
|
184158
|
+
}
|
|
184121
184159
|
if (!isAllowedProjectPath(leaf.path)) {
|
|
184122
184160
|
violations.push({ path });
|
|
184123
184161
|
continue;
|
|
@@ -184278,7 +184316,7 @@ function sanitizeText(value) {
|
|
|
184278
184316
|
return SENSITIVE_TEXT_PATTERNS.reduce((text, pattern) => text.replace(pattern, REDACTION), value);
|
|
184279
184317
|
}
|
|
184280
184318
|
function sanitizeTextForDisplay(value, maxChars = 240) {
|
|
184281
|
-
const compact = sanitizeText(value).replace(/\s+/g, " ").trim();
|
|
184319
|
+
const compact = sanitizeText(value).replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "").replace(/\s+/g, " ").trim();
|
|
184282
184320
|
if (compact.length <= maxChars)
|
|
184283
184321
|
return compact;
|
|
184284
184322
|
return `${compact.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
@@ -185322,6 +185360,25 @@ function isMissingPathError(error51) {
|
|
|
185322
185360
|
}
|
|
185323
185361
|
|
|
185324
185362
|
// src/config/loadConfig.ts
|
|
185363
|
+
var configValidationContexts = new WeakMap;
|
|
185364
|
+
class ProjectOpenRouterAuthorizationError extends Error {
|
|
185365
|
+
code = "PROJECT_OPENROUTER_AUTHORIZATION_REQUIRED";
|
|
185366
|
+
projectPath;
|
|
185367
|
+
projectDirectory;
|
|
185368
|
+
layer;
|
|
185369
|
+
globalConfigPath;
|
|
185370
|
+
constructor(input) {
|
|
185371
|
+
const projectPath = sanitizeWarningText(input.projectPath);
|
|
185372
|
+
const projectDirectory = sanitizeWarningText(input.projectDirectory);
|
|
185373
|
+
const globalConfigPath = sanitizeWarningText(input.globalConfigPath);
|
|
185374
|
+
super(`Project config ${projectPath} changes Codex OpenRouter routing, but its directory ${projectDirectory} is not in the user-global allowlist. Add ${JSON.stringify(projectDirectory)} to agents.codex.allowProjectProvider in ${globalConfigPath} to permit project-level external provider routing.`);
|
|
185375
|
+
this.name = "ProjectOpenRouterAuthorizationError";
|
|
185376
|
+
this.projectPath = projectPath;
|
|
185377
|
+
this.projectDirectory = projectDirectory;
|
|
185378
|
+
this.layer = input.layer;
|
|
185379
|
+
this.globalConfigPath = globalConfigPath;
|
|
185380
|
+
}
|
|
185381
|
+
}
|
|
185325
185382
|
var KNOWN_GLOBAL_CONFIG_LEAF_PATHS = new Set(kyosoConfigKnownLeafPaths);
|
|
185326
185383
|
var GLOBAL_CONFIG_RECORD_PREFIXES = kyosoConfigRecordPrefixes.map((path) => path.split("."));
|
|
185327
185384
|
var SECURITY_SENSITIVE_GLOBAL_PREFIXES = kyosoConfigSecuritySensitivePrefixes.map((path) => path.split("."));
|
|
@@ -185349,11 +185406,12 @@ async function loadConfig(options = {}) {
|
|
|
185349
185406
|
}
|
|
185350
185407
|
if (options.configPath) {
|
|
185351
185408
|
const explicitConfigPath = resolve2(cwd, options.configPath);
|
|
185352
|
-
|
|
185409
|
+
const projectConfig = await resolveProjectConfigIdentity(explicitConfigPath);
|
|
185410
|
+
if (!projectConfig) {
|
|
185353
185411
|
throw new Error(`Config file not found: ${explicitConfigPath} (from --config)`);
|
|
185354
185412
|
}
|
|
185355
185413
|
const loaded = await loadProjectConfig({
|
|
185356
|
-
|
|
185414
|
+
projectConfig,
|
|
185357
185415
|
baseConfig: mergedConfig,
|
|
185358
185416
|
globalConfigPath,
|
|
185359
185417
|
options
|
|
@@ -185367,19 +185425,29 @@ async function loadConfig(options = {}) {
|
|
|
185367
185425
|
} else {
|
|
185368
185426
|
const projectTomlPath = resolve2(cwd, "kyoso.toml");
|
|
185369
185427
|
const projectTsPath = resolve2(cwd, "kyoso.config.ts");
|
|
185370
|
-
const
|
|
185371
|
-
const
|
|
185372
|
-
if (
|
|
185373
|
-
|
|
185374
|
-
|
|
185375
|
-
|
|
185376
|
-
|
|
185428
|
+
const projectToml = await resolveProjectConfigIdentity(projectTomlPath);
|
|
185429
|
+
const projectTs = await resolveProjectConfigIdentity(projectTsPath);
|
|
185430
|
+
if (projectToml) {
|
|
185431
|
+
const loaded = await loadProjectConfig({
|
|
185432
|
+
projectConfig: projectToml,
|
|
185433
|
+
baseConfig: mergedConfig,
|
|
185434
|
+
globalConfigPath,
|
|
185435
|
+
options
|
|
185436
|
+
});
|
|
185437
|
+
mergedConfig = loaded.mergedConfig;
|
|
185438
|
+
configPath = loaded.configPath;
|
|
185439
|
+
configHash = loaded.configHash;
|
|
185440
|
+
configTrustStatus = loaded.configTrustStatus;
|
|
185441
|
+
sources.push(loaded.source);
|
|
185442
|
+
warnings.push(...loaded.warnings);
|
|
185443
|
+
if (projectTs) {
|
|
185377
185444
|
warnings.push(`kyoso.config.ts was ignored because kyoso.toml takes precedence: ${projectTsPath}`);
|
|
185378
185445
|
}
|
|
185379
|
-
} else if (
|
|
185446
|
+
} else if (projectTs) {
|
|
185380
185447
|
const loaded = await loadProjectTsConfig({
|
|
185381
|
-
|
|
185448
|
+
projectConfig: projectTs,
|
|
185382
185449
|
baseConfig: mergedConfig,
|
|
185450
|
+
globalConfigPath,
|
|
185383
185451
|
options
|
|
185384
185452
|
});
|
|
185385
185453
|
mergedConfig = loaded.mergedConfig;
|
|
@@ -185391,9 +185459,17 @@ async function loadConfig(options = {}) {
|
|
|
185391
185459
|
}
|
|
185392
185460
|
}
|
|
185393
185461
|
}
|
|
185394
|
-
const parsed = kyosoConfigSchema.
|
|
185462
|
+
const parsed = kyosoConfigSchema.safeParse(mergedConfig);
|
|
185463
|
+
if (!parsed.success) {
|
|
185464
|
+
const source = sources.at(-1);
|
|
185465
|
+
configValidationContexts.set(parsed.error, {
|
|
185466
|
+
source,
|
|
185467
|
+
projectTsExecuted: source?.layer === "project_ts" && isTrustedProjectConfigExecution(configTrustStatus)
|
|
185468
|
+
});
|
|
185469
|
+
throw parsed.error;
|
|
185470
|
+
}
|
|
185395
185471
|
return {
|
|
185396
|
-
config: parsed,
|
|
185472
|
+
config: parsed.data,
|
|
185397
185473
|
configPath,
|
|
185398
185474
|
configHash,
|
|
185399
185475
|
configTrustStatus,
|
|
@@ -185441,62 +185517,170 @@ function pathStartsWith(path, prefix) {
|
|
|
185441
185517
|
return path.length >= prefix.length && prefix.every((part, index) => path[index] === part);
|
|
185442
185518
|
}
|
|
185443
185519
|
async function loadProjectConfig(input) {
|
|
185444
|
-
const
|
|
185520
|
+
const { canonicalDirectory, canonicalPath, requestedPath } = input.projectConfig;
|
|
185521
|
+
const extension = extname2(requestedPath);
|
|
185445
185522
|
if (extension === ".toml") {
|
|
185523
|
+
const projectTomlConfig = await loadTomlConfigFile(canonicalPath);
|
|
185524
|
+
const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(projectTomlConfig, input.baseConfig);
|
|
185525
|
+
await assertProjectOpenRouterAuthorization({
|
|
185526
|
+
projectConfig: projectTomlConfig,
|
|
185527
|
+
projectPath: requestedPath,
|
|
185528
|
+
projectDirectory: canonicalDirectory,
|
|
185529
|
+
layer: "project_toml",
|
|
185530
|
+
baseConfig: input.baseConfig,
|
|
185531
|
+
globalConfigPath: input.globalConfigPath
|
|
185532
|
+
});
|
|
185533
|
+
const projectMergedConfig = mergeProjectTomlConfig(input.baseConfig, projectTomlConfig, {
|
|
185534
|
+
projectPath: requestedPath,
|
|
185535
|
+
globalConfigPath: input.globalConfigPath
|
|
185536
|
+
});
|
|
185446
185537
|
return {
|
|
185447
|
-
mergedConfig:
|
|
185448
|
-
|
|
185449
|
-
globalConfigPath: input.globalConfigPath
|
|
185450
|
-
}),
|
|
185451
|
-
configPath: input.configPath,
|
|
185538
|
+
mergedConfig: applyProjectCodexProviderReset(input.baseConfig, projectTomlConfig, projectMergedConfig),
|
|
185539
|
+
configPath: requestedPath,
|
|
185452
185540
|
configTrustStatus: "not_found",
|
|
185453
|
-
source: { path:
|
|
185454
|
-
warnings: []
|
|
185541
|
+
source: { path: requestedPath, layer: "project_toml" },
|
|
185542
|
+
warnings: projectChangesOpenRouterRoute ? [openRouterProjectConfigWarning(requestedPath)] : []
|
|
185455
185543
|
};
|
|
185456
185544
|
}
|
|
185457
185545
|
if (extension === ".ts") {
|
|
185458
185546
|
return await loadProjectTsConfig(input);
|
|
185459
185547
|
}
|
|
185460
|
-
throw new Error(`Unsupported config file extension for ${
|
|
185548
|
+
throw new Error(`Unsupported config file extension for ${requestedPath}. Expected .toml or .ts.`);
|
|
185549
|
+
}
|
|
185550
|
+
function projectConfigSelectsOpenRouter(config2) {
|
|
185551
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_OPENROUTER_PROVIDER);
|
|
185552
|
+
}
|
|
185553
|
+
function projectConfigSelectsDefaultProvider(config2) {
|
|
185554
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_DEFAULT_PROVIDER);
|
|
185555
|
+
}
|
|
185556
|
+
function projectConfigSuppliesCodexModel(config2) {
|
|
185557
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.model");
|
|
185558
|
+
}
|
|
185559
|
+
function projectConfigChangesOpenRouterRoute(projectConfig, baseConfig) {
|
|
185560
|
+
return projectConfigSelectsOpenRouter(projectConfig) || configSelectsOpenRouter(baseConfig) && projectConfigSuppliesCodexModel(projectConfig) && !projectConfigSelectsDefaultProvider(projectConfig);
|
|
185561
|
+
}
|
|
185562
|
+
function configSelectsOpenRouter(config2) {
|
|
185563
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_OPENROUTER_PROVIDER);
|
|
185564
|
+
}
|
|
185565
|
+
function applyProjectCodexProviderReset(baseConfig, projectConfig, mergedConfig) {
|
|
185566
|
+
if (!projectConfigSelectsDefaultProvider(projectConfig) || projectConfigSuppliesCodexModel(projectConfig) || !configSelectsOpenRouter(baseConfig) || !isRecord3(mergedConfig) || !isRecord3(mergedConfig.agents) || !isRecord3(mergedConfig.agents.codex)) {
|
|
185567
|
+
return mergedConfig;
|
|
185568
|
+
}
|
|
185569
|
+
const codexWithoutInheritedModel = Object.fromEntries(Object.entries(mergedConfig.agents.codex).filter(([key]) => key !== "model"));
|
|
185570
|
+
return {
|
|
185571
|
+
...mergedConfig,
|
|
185572
|
+
agents: {
|
|
185573
|
+
...mergedConfig.agents,
|
|
185574
|
+
codex: codexWithoutInheritedModel
|
|
185575
|
+
}
|
|
185576
|
+
};
|
|
185577
|
+
}
|
|
185578
|
+
function openRouterProjectConfigWarning(configPath) {
|
|
185579
|
+
return `Project config ${sanitizeWarningText(configPath)} changes Codex OpenRouter routing under user-global authorization; it can route Codex review content through OpenRouter.`;
|
|
185580
|
+
}
|
|
185581
|
+
async function assertProjectOpenRouterAuthorization(input) {
|
|
185582
|
+
if (!projectConfigChangesOpenRouterRoute(input.projectConfig, input.baseConfig)) {
|
|
185583
|
+
return;
|
|
185584
|
+
}
|
|
185585
|
+
if (await projectProviderIsAuthorized(input.baseConfig, input.projectDirectory)) {
|
|
185586
|
+
return;
|
|
185587
|
+
}
|
|
185588
|
+
throw new ProjectOpenRouterAuthorizationError({
|
|
185589
|
+
projectPath: input.projectPath,
|
|
185590
|
+
projectDirectory: input.projectDirectory,
|
|
185591
|
+
layer: input.layer,
|
|
185592
|
+
globalConfigPath: input.globalConfigPath
|
|
185593
|
+
});
|
|
185594
|
+
}
|
|
185595
|
+
async function projectProviderIsAuthorized(config2, projectDirectory) {
|
|
185596
|
+
if (!isRecord3(config2))
|
|
185597
|
+
return false;
|
|
185598
|
+
const agents = config2.agents;
|
|
185599
|
+
if (!isRecord3(agents))
|
|
185600
|
+
return false;
|
|
185601
|
+
const codex = agents.codex;
|
|
185602
|
+
if (!isRecord3(codex) || !Array.isArray(codex.allowProjectProvider)) {
|
|
185603
|
+
return false;
|
|
185604
|
+
}
|
|
185605
|
+
for (const directory of codex.allowProjectProvider) {
|
|
185606
|
+
if (typeof directory !== "string" || !isAbsolute2(directory))
|
|
185607
|
+
continue;
|
|
185608
|
+
const allowedDirectory = await existingRealpath(directory);
|
|
185609
|
+
if (allowedDirectory === projectDirectory)
|
|
185610
|
+
return true;
|
|
185611
|
+
}
|
|
185612
|
+
return false;
|
|
185613
|
+
}
|
|
185614
|
+
async function resolveProjectConfigIdentity(requestedPath) {
|
|
185615
|
+
const canonicalPath = await existingRealpath(requestedPath);
|
|
185616
|
+
return canonicalPath === undefined ? undefined : {
|
|
185617
|
+
requestedPath,
|
|
185618
|
+
canonicalPath,
|
|
185619
|
+
canonicalDirectory: dirname3(canonicalPath)
|
|
185620
|
+
};
|
|
185621
|
+
}
|
|
185622
|
+
async function existingRealpath(path) {
|
|
185623
|
+
try {
|
|
185624
|
+
return await realpath(path);
|
|
185625
|
+
} catch {
|
|
185626
|
+
return;
|
|
185627
|
+
}
|
|
185461
185628
|
}
|
|
185462
185629
|
async function loadProjectTsConfig(input) {
|
|
185630
|
+
const { canonicalDirectory, canonicalPath, requestedPath } = input.projectConfig;
|
|
185463
185631
|
const warnings = [
|
|
185464
185632
|
'kyoso.config.ts is deprecated; migrate to kyoso.toml (see README "Configuration")'
|
|
185465
185633
|
];
|
|
185466
|
-
const source = await readFile3(
|
|
185634
|
+
const source = await readFile3(canonicalPath, "utf8");
|
|
185467
185635
|
const configHash = hashConfigSource(source);
|
|
185468
185636
|
const trustStorePath = input.options.trustStorePath ?? defaultTrustedConfigStorePath(input.options.env);
|
|
185469
|
-
const trusted = await isTrustedConfig(trustStorePath,
|
|
185637
|
+
const trusted = await isTrustedConfig(trustStorePath, canonicalPath, configHash);
|
|
185470
185638
|
const trustDecision = await resolveTrustDecision({
|
|
185471
|
-
configPath:
|
|
185639
|
+
configPath: requestedPath,
|
|
185472
185640
|
configHash,
|
|
185473
185641
|
trusted,
|
|
185474
185642
|
options: input.options
|
|
185475
185643
|
});
|
|
185476
185644
|
if (trustDecision.execute) {
|
|
185477
|
-
const userConfig = await loadUserConfig(
|
|
185645
|
+
const userConfig = await loadUserConfig(canonicalPath, source);
|
|
185646
|
+
const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(userConfig, input.baseConfig);
|
|
185647
|
+
await assertProjectOpenRouterAuthorization({
|
|
185648
|
+
projectConfig: userConfig,
|
|
185649
|
+
projectPath: requestedPath,
|
|
185650
|
+
projectDirectory: canonicalDirectory,
|
|
185651
|
+
layer: "project_ts",
|
|
185652
|
+
baseConfig: input.baseConfig,
|
|
185653
|
+
globalConfigPath: input.globalConfigPath
|
|
185654
|
+
});
|
|
185478
185655
|
if (trustDecision.shouldPersist) {
|
|
185479
|
-
await trustConfig(trustStorePath,
|
|
185656
|
+
await trustConfig(trustStorePath, canonicalPath, configHash);
|
|
185657
|
+
}
|
|
185658
|
+
const projectMergedConfig = deepMerge2(input.baseConfig, userConfig);
|
|
185659
|
+
if (projectChangesOpenRouterRoute) {
|
|
185660
|
+
warnings.push(openRouterProjectConfigWarning(requestedPath));
|
|
185480
185661
|
}
|
|
185481
185662
|
return {
|
|
185482
|
-
mergedConfig:
|
|
185483
|
-
configPath:
|
|
185663
|
+
mergedConfig: applyProjectCodexProviderReset(input.baseConfig, userConfig, projectMergedConfig),
|
|
185664
|
+
configPath: requestedPath,
|
|
185484
185665
|
configHash,
|
|
185485
185666
|
configTrustStatus: trustDecision.status,
|
|
185486
|
-
source: { path:
|
|
185667
|
+
source: { path: requestedPath, layer: "project_ts" },
|
|
185487
185668
|
warnings
|
|
185488
185669
|
};
|
|
185489
185670
|
}
|
|
185490
|
-
warnings.push(`untrusted config was not executed: ${
|
|
185671
|
+
warnings.push(`untrusted config was not executed: ${requestedPath}; run \`kyoso doctor --trust-config\` or pass \`--trust-config\` once to trust it`);
|
|
185491
185672
|
return {
|
|
185492
185673
|
mergedConfig: input.baseConfig,
|
|
185493
|
-
configPath:
|
|
185674
|
+
configPath: requestedPath,
|
|
185494
185675
|
configHash,
|
|
185495
185676
|
configTrustStatus: trustDecision.status,
|
|
185496
|
-
source: { path:
|
|
185677
|
+
source: { path: requestedPath, layer: "project_ts" },
|
|
185497
185678
|
warnings
|
|
185498
185679
|
};
|
|
185499
185680
|
}
|
|
185681
|
+
function isTrustedProjectConfigExecution(status) {
|
|
185682
|
+
return status === "trusted" || status === "trusted_by_flag" || status === "trusted_interactively";
|
|
185683
|
+
}
|
|
185500
185684
|
async function loadUserConfig(configPath, source) {
|
|
185501
185685
|
try {
|
|
185502
185686
|
const loaded = await loadConfigModule(configPath, source);
|
|
@@ -185575,6 +185759,8 @@ function applyConfigOverrides(config2, assignments) {
|
|
|
185575
185759
|
for (const override of overrides) {
|
|
185576
185760
|
writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
|
|
185577
185761
|
}
|
|
185762
|
+
clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides);
|
|
185763
|
+
assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, overrides);
|
|
185578
185764
|
const parsed = kyosoConfigSchema.safeParse(overridden);
|
|
185579
185765
|
if (parsed.success)
|
|
185580
185766
|
return parsed.data;
|
|
@@ -185583,6 +185769,29 @@ function applyConfigOverrides(config2, assignments) {
|
|
|
185583
185769
|
const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
|
|
185584
185770
|
throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
|
|
185585
185771
|
}
|
|
185772
|
+
function assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, overrides) {
|
|
185773
|
+
const providerPath = ["agents", "codex", "provider"];
|
|
185774
|
+
const modelPath = ["agents", "codex", "model"];
|
|
185775
|
+
const selectsOpenRouter = readPath2(overridden, providerPath) === CODEX_OPENROUTER_PROVIDER;
|
|
185776
|
+
const alreadySelected = readPath2(baseConfig, providerPath) === CODEX_OPENROUTER_PROVIDER;
|
|
185777
|
+
const suppliesModel = overrides.some((override) => override.path.join(".") === modelPath.join("."));
|
|
185778
|
+
if (!selectsOpenRouter || alreadySelected || suppliesModel)
|
|
185779
|
+
return;
|
|
185780
|
+
const assignment = findAssignmentForPath(overrides, providerPath.join(".")) ?? "agents.codex.provider=openrouter";
|
|
185781
|
+
throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: selecting agents.codex.provider=openrouter requires agents.codex.model in the same --set invocation.`);
|
|
185782
|
+
}
|
|
185783
|
+
function clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides) {
|
|
185784
|
+
const providerPath = ["agents", "codex", "provider"];
|
|
185785
|
+
const modelPath = ["agents", "codex", "model"];
|
|
185786
|
+
const selectsDefaultProvider = overrides.some((override) => override.path.join(".") === providerPath.join(".") && override.value === CODEX_DEFAULT_PROVIDER);
|
|
185787
|
+
const suppliesModel = overrides.some((override) => override.path.join(".") === modelPath.join("."));
|
|
185788
|
+
if (!selectsDefaultProvider || suppliesModel || readPath2(baseConfig, providerPath) !== CODEX_OPENROUTER_PROVIDER || readPath2(overridden, providerPath) !== CODEX_DEFAULT_PROVIDER) {
|
|
185789
|
+
return;
|
|
185790
|
+
}
|
|
185791
|
+
const codex = readPath2(overridden, ["agents", "codex"]);
|
|
185792
|
+
if (isRecord4(codex))
|
|
185793
|
+
delete codex.model;
|
|
185794
|
+
}
|
|
185586
185795
|
function findAssignmentForPath(overrides, path) {
|
|
185587
185796
|
for (let index = overrides.length - 1;index >= 0; index -= 1) {
|
|
185588
185797
|
const override = overrides[index];
|
|
@@ -185650,8 +185859,8 @@ function isRecord4(value) {
|
|
|
185650
185859
|
|
|
185651
185860
|
// src/acp/AcpAgentProcess.ts
|
|
185652
185861
|
import { spawn } from "node:child_process";
|
|
185653
|
-
import { readFile as readFile4, realpath } from "node:fs/promises";
|
|
185654
|
-
import { isAbsolute, relative, resolve as resolve3 } from "node:path";
|
|
185862
|
+
import { readFile as readFile4, realpath as realpath2 } from "node:fs/promises";
|
|
185863
|
+
import { isAbsolute as isAbsolute3, relative, resolve as resolve3 } from "node:path";
|
|
185655
185864
|
import { Readable, Writable } from "node:stream";
|
|
185656
185865
|
|
|
185657
185866
|
// node_modules/@agentclientprotocol/sdk/dist/schema/index.js
|
|
@@ -189583,6 +189792,7 @@ var legacyClientNotificationMethods = new Set([
|
|
|
189583
189792
|
]);
|
|
189584
189793
|
|
|
189585
189794
|
// src/utils/env.ts
|
|
189795
|
+
import { stderr as stderr2 } from "node:process";
|
|
189586
189796
|
var MINIMAL_ENV_KEYS = [
|
|
189587
189797
|
"PATH",
|
|
189588
189798
|
"HOME",
|
|
@@ -189596,29 +189806,110 @@ var MINIMAL_ENV_KEYS = [
|
|
|
189596
189806
|
"USERNAME",
|
|
189597
189807
|
"SystemRoot"
|
|
189598
189808
|
];
|
|
189809
|
+
var CREDENTIAL_ENV_KEYS = new Set([
|
|
189810
|
+
"ANTHROPIC_API_KEY",
|
|
189811
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
189812
|
+
"CODEX_ACCESS_TOKEN",
|
|
189813
|
+
"CODEX_API_KEY",
|
|
189814
|
+
"OPENAI_API_KEY",
|
|
189815
|
+
"OPENROUTER_API_KEY"
|
|
189816
|
+
]);
|
|
189817
|
+
var CREDENTIAL_LIKE_ENV_KEY_PATTERN = /(?:^|_)(?:KEY|TOKEN|SECRET|PASSWORD)$/i;
|
|
189818
|
+
var UNEXPANDED_ENV_PLACEHOLDER_PATTERN = /^\s*(?:\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$[A-Za-z_][A-Za-z0-9_]*|%[A-Za-z_][A-Za-z0-9_]*%)\s*$/;
|
|
189819
|
+
var OPENROUTER_EXCLUDED_CREDENTIAL_ENV_KEYS = [
|
|
189820
|
+
"OPENAI_API_KEY",
|
|
189821
|
+
"CODEX_API_KEY",
|
|
189822
|
+
"CODEX_ACCESS_TOKEN"
|
|
189823
|
+
];
|
|
189824
|
+
var OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
|
|
189825
|
+
var KYOSO_OPENROUTER_PROVIDER_ID = "kyoso-openrouter";
|
|
189826
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
189827
|
+
var OPENROUTER_PROVIDER_PRESET = {
|
|
189828
|
+
name: "OpenRouter",
|
|
189829
|
+
base_url: OPENROUTER_BASE_URL,
|
|
189830
|
+
env_key: OPENROUTER_API_KEY_ENV,
|
|
189831
|
+
wire_api: "responses",
|
|
189832
|
+
requires_openai_auth: false
|
|
189833
|
+
};
|
|
189834
|
+
|
|
189835
|
+
class ChildEnvPreflightError extends Error {
|
|
189836
|
+
code;
|
|
189837
|
+
constructor(code, message) {
|
|
189838
|
+
super(message);
|
|
189839
|
+
this.code = code;
|
|
189840
|
+
this.name = "ChildEnvPreflightError";
|
|
189841
|
+
}
|
|
189842
|
+
}
|
|
189599
189843
|
function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
|
|
189600
189844
|
if (!parentEnv.PATH) {
|
|
189601
189845
|
throw new Error("PATH is required to launch ACP child agents.");
|
|
189602
189846
|
}
|
|
189603
189847
|
const env = {};
|
|
189848
|
+
const warnedCredentialPlaceholderKeys = new Set;
|
|
189849
|
+
const onCredentialPlaceholderDiscarded = (key) => {
|
|
189850
|
+
if (warnedCredentialPlaceholderKeys.has(key))
|
|
189851
|
+
return;
|
|
189852
|
+
warnedCredentialPlaceholderKeys.add(key);
|
|
189853
|
+
(options.onCredentialPlaceholderDiscarded ?? warnCredentialPlaceholderDiscarded)(key);
|
|
189854
|
+
};
|
|
189855
|
+
const openRouterSelected = options.agent === "codex" && options.provider === CODEX_OPENROUTER_PROVIDER;
|
|
189604
189856
|
for (const key of MINIMAL_ENV_KEYS) {
|
|
189605
189857
|
if (parentEnv[key])
|
|
189606
189858
|
env[key] = parentEnv[key];
|
|
189607
189859
|
}
|
|
189608
189860
|
for (const key of whitelist) {
|
|
189609
|
-
if (
|
|
189861
|
+
if (key === OPENROUTER_API_KEY_ENV && !openRouterSelected)
|
|
189862
|
+
continue;
|
|
189863
|
+
if (parentEnv[key] && canCopyEnvValue(key, parentEnv[key], onCredentialPlaceholderDiscarded)) {
|
|
189610
189864
|
env[key] = parentEnv[key];
|
|
189865
|
+
}
|
|
189611
189866
|
}
|
|
189612
189867
|
for (const [key, value] of Object.entries(explicit)) {
|
|
189613
|
-
|
|
189868
|
+
if (key === OPENROUTER_API_KEY_ENV && !openRouterSelected) {
|
|
189869
|
+
if (value.trim().length > 0) {
|
|
189870
|
+
(options.onOpenRouterCredentialWithheld ?? warnOpenRouterCredentialWithheld)(key);
|
|
189871
|
+
}
|
|
189872
|
+
continue;
|
|
189873
|
+
}
|
|
189874
|
+
if (canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded)) {
|
|
189875
|
+
env[key] = value;
|
|
189876
|
+
}
|
|
189877
|
+
}
|
|
189878
|
+
if (openRouterSelected) {
|
|
189879
|
+
discardOpenRouterExcludedCredentials(env);
|
|
189880
|
+
applyOpenRouterConfig(env, parentEnv, options.model, onCredentialPlaceholderDiscarded, options.onOpenRouterProvidersDiscarded ?? warnOpenRouterProvidersDiscarded);
|
|
189881
|
+
} else {
|
|
189882
|
+
applyModelConfig(env, options.agent, options.model);
|
|
189614
189883
|
}
|
|
189615
|
-
applyModelConfig(env, options.agent, options.model);
|
|
189616
189884
|
env.KYOSO_CHILD_AGENT = "1";
|
|
189617
189885
|
if (options.agent === "claude") {
|
|
189618
189886
|
applyClaudeAuthPreference(env, options.preferApiKey === true);
|
|
189619
189887
|
}
|
|
189620
189888
|
return env;
|
|
189621
189889
|
}
|
|
189890
|
+
function canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded) {
|
|
189891
|
+
if (!isUnexpandedCredentialEnvValue(key, value))
|
|
189892
|
+
return true;
|
|
189893
|
+
(onCredentialPlaceholderDiscarded ?? warnCredentialPlaceholderDiscarded)(key);
|
|
189894
|
+
return false;
|
|
189895
|
+
}
|
|
189896
|
+
function warnCredentialPlaceholderDiscarded(key) {
|
|
189897
|
+
stderr2.write(`kyoso: ignored an unexpanded credential placeholder for ${key}; ensure the client expands it before starting Kyoso.
|
|
189898
|
+
`);
|
|
189899
|
+
}
|
|
189900
|
+
function warnOpenRouterCredentialWithheld(key) {
|
|
189901
|
+
stderr2.write(`kyoso: ignored explicit ${key} because the OpenRouter provider is not selected for this child.
|
|
189902
|
+
`);
|
|
189903
|
+
}
|
|
189904
|
+
function warnOpenRouterProvidersDiscarded(count) {
|
|
189905
|
+
stderr2.write(`kyoso: discarded foreign CODEX_CONFIG.model_providers entries: ${count}; provider IDs and configuration values were not displayed.
|
|
189906
|
+
`);
|
|
189907
|
+
}
|
|
189908
|
+
function discardOpenRouterExcludedCredentials(env) {
|
|
189909
|
+
for (const key of OPENROUTER_EXCLUDED_CREDENTIAL_ENV_KEYS) {
|
|
189910
|
+
delete env[key];
|
|
189911
|
+
}
|
|
189912
|
+
}
|
|
189622
189913
|
function applyModelConfig(env, agent, model) {
|
|
189623
189914
|
if (!model)
|
|
189624
189915
|
return;
|
|
@@ -189630,7 +189921,67 @@ function applyModelConfig(env, agent, model) {
|
|
|
189630
189921
|
env.CODEX_CONFIG = JSON.stringify({ model });
|
|
189631
189922
|
}
|
|
189632
189923
|
}
|
|
189924
|
+
function applyOpenRouterConfig(env, parentEnv, model, onCredentialPlaceholderDiscarded, onOpenRouterProvidersDiscarded) {
|
|
189925
|
+
const configuredModel = model?.trim();
|
|
189926
|
+
if (!configuredModel) {
|
|
189927
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", 'agents.codex.provider="openrouter" requires a non-empty agents.codex.model.');
|
|
189928
|
+
}
|
|
189929
|
+
if (!hasEnv(env, OPENROUTER_API_KEY_ENV)) {
|
|
189930
|
+
const parentKey = nonEmptyEnv(parentEnv, OPENROUTER_API_KEY_ENV);
|
|
189931
|
+
if (parentKey)
|
|
189932
|
+
env[OPENROUTER_API_KEY_ENV] = parentKey;
|
|
189933
|
+
else if (isUnexpandedCredentialEnvValue(OPENROUTER_API_KEY_ENV, parentEnv[OPENROUTER_API_KEY_ENV] ?? "")) {
|
|
189934
|
+
onCredentialPlaceholderDiscarded(OPENROUTER_API_KEY_ENV);
|
|
189935
|
+
}
|
|
189936
|
+
}
|
|
189937
|
+
if (!hasEnv(env, OPENROUTER_API_KEY_ENV)) {
|
|
189938
|
+
throw new ChildEnvPreflightError("OPENROUTER_KEY_MISSING", 'agents.codex.provider="openrouter" requires OPENROUTER_API_KEY, but it is not visible to the Kyoso process. Add OPENROUTER_API_KEY to the MCP registration, restart the client, then run `kyoso doctor`.');
|
|
189939
|
+
}
|
|
189940
|
+
const config2 = parseCodexConfig(env.CODEX_CONFIG);
|
|
189941
|
+
assertOpenRouterConfigDoesNotSelectProfile(config2);
|
|
189942
|
+
if (config2.model_providers !== undefined && !isPlainObject2(config2.model_providers)) {
|
|
189943
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG.model_providers must be a JSON object for the OpenRouter provider.");
|
|
189944
|
+
}
|
|
189945
|
+
reportDiscardedOpenRouterProviders(config2.model_providers, onOpenRouterProvidersDiscarded);
|
|
189946
|
+
env.MODEL_PROVIDER = KYOSO_OPENROUTER_PROVIDER_ID;
|
|
189947
|
+
env.CODEX_CONFIG = JSON.stringify({
|
|
189948
|
+
...config2,
|
|
189949
|
+
model: configuredModel,
|
|
189950
|
+
model_provider: KYOSO_OPENROUTER_PROVIDER_ID,
|
|
189951
|
+
model_providers: {
|
|
189952
|
+
[KYOSO_OPENROUTER_PROVIDER_ID]: OPENROUTER_PROVIDER_PRESET
|
|
189953
|
+
}
|
|
189954
|
+
});
|
|
189955
|
+
}
|
|
189956
|
+
function reportDiscardedOpenRouterProviders(modelProviders, onDiscarded) {
|
|
189957
|
+
if (!isPlainObject2(modelProviders))
|
|
189958
|
+
return;
|
|
189959
|
+
const foreignProviderCount = Object.keys(modelProviders).filter((providerId) => providerId !== KYOSO_OPENROUTER_PROVIDER_ID).length;
|
|
189960
|
+
if (foreignProviderCount > 0)
|
|
189961
|
+
onDiscarded(foreignProviderCount);
|
|
189962
|
+
}
|
|
189963
|
+
function parseCodexConfig(value) {
|
|
189964
|
+
if (!value)
|
|
189965
|
+
return {};
|
|
189966
|
+
let parsed;
|
|
189967
|
+
try {
|
|
189968
|
+
parsed = JSON.parse(value);
|
|
189969
|
+
} catch {
|
|
189970
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG must contain a JSON object for the OpenRouter provider.");
|
|
189971
|
+
}
|
|
189972
|
+
if (!isPlainObject2(parsed)) {
|
|
189973
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG must contain a JSON object for the OpenRouter provider.");
|
|
189974
|
+
}
|
|
189975
|
+
return parsed;
|
|
189976
|
+
}
|
|
189977
|
+
function assertOpenRouterConfigDoesNotSelectProfile(config2) {
|
|
189978
|
+
if (Object.hasOwn(config2, "profile") || Object.hasOwn(config2, "profiles")) {
|
|
189979
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG.profile and CODEX_CONFIG.profiles are not supported for the OpenRouter provider.");
|
|
189980
|
+
}
|
|
189981
|
+
}
|
|
189633
189982
|
function applyClaudeAuthPreference(env, preferApiKey) {
|
|
189983
|
+
discardUnusableEnvValue(env, "ANTHROPIC_API_KEY");
|
|
189984
|
+
discardUnusableEnvValue(env, "CLAUDE_CODE_OAUTH_TOKEN");
|
|
189634
189985
|
const hasApiKey = hasEnv(env, "ANTHROPIC_API_KEY");
|
|
189635
189986
|
const hasOAuthToken = hasEnv(env, "CLAUDE_CODE_OAUTH_TOKEN");
|
|
189636
189987
|
if (!hasApiKey || !hasOAuthToken)
|
|
@@ -189641,8 +189992,32 @@ function applyClaudeAuthPreference(env, preferApiKey) {
|
|
|
189641
189992
|
delete env.ANTHROPIC_API_KEY;
|
|
189642
189993
|
}
|
|
189643
189994
|
}
|
|
189995
|
+
function discardUnusableEnvValue(env, key) {
|
|
189996
|
+
if (env[key] !== undefined && !hasEnv(env, key)) {
|
|
189997
|
+
delete env[key];
|
|
189998
|
+
}
|
|
189999
|
+
}
|
|
190000
|
+
function hasUsableEnvValue(env, key) {
|
|
190001
|
+
return nonEmptyEnv(env, key) !== undefined;
|
|
190002
|
+
}
|
|
190003
|
+
function isUnexpandedEnvPlaceholder(value) {
|
|
190004
|
+
return typeof value === "string" && UNEXPANDED_ENV_PLACEHOLDER_PATTERN.test(value);
|
|
190005
|
+
}
|
|
189644
190006
|
function hasEnv(env, key) {
|
|
189645
|
-
return
|
|
190007
|
+
return hasUsableEnvValue(env, key);
|
|
190008
|
+
}
|
|
190009
|
+
function nonEmptyEnv(env, key) {
|
|
190010
|
+
const value = env[key];
|
|
190011
|
+
return typeof value === "string" && value.trim().length > 0 && !isUnexpandedCredentialEnvValue(key, value) ? value : undefined;
|
|
190012
|
+
}
|
|
190013
|
+
function isUnexpandedCredentialEnvValue(key, value) {
|
|
190014
|
+
return (CREDENTIAL_ENV_KEYS.has(key) || CREDENTIAL_LIKE_ENV_KEY_PATTERN.test(key)) && isUnexpandedEnvPlaceholder(value);
|
|
190015
|
+
}
|
|
190016
|
+
function isPlainObject2(value) {
|
|
190017
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
190018
|
+
return false;
|
|
190019
|
+
}
|
|
190020
|
+
return Object.getPrototypeOf(value) === Object.prototype;
|
|
189646
190021
|
}
|
|
189647
190022
|
|
|
189648
190023
|
// src/acp/AcpAgentManager.ts
|
|
@@ -189828,31 +190203,59 @@ function isRecord6(value) {
|
|
|
189828
190203
|
// src/acp/AcpAgentProcess.ts
|
|
189829
190204
|
class SubprocessAcpAgentManager extends BaseAcpAgentManager {
|
|
189830
190205
|
config;
|
|
189831
|
-
|
|
190206
|
+
parentEnv;
|
|
190207
|
+
constructor(config2, parentEnv = process.env) {
|
|
189832
190208
|
super();
|
|
189833
190209
|
this.config = config2;
|
|
190210
|
+
this.parentEnv = parentEnv;
|
|
189834
190211
|
}
|
|
189835
190212
|
async runAgent(input) {
|
|
189836
190213
|
const agentConfig = this.config.agents[input.agent];
|
|
190214
|
+
const startedAt = new Date().toISOString();
|
|
189837
190215
|
if (!agentConfig.enabled) {
|
|
189838
190216
|
return {
|
|
189839
190217
|
agent: input.agent,
|
|
189840
190218
|
role: input.role,
|
|
189841
190219
|
status: "skipped",
|
|
189842
|
-
startedAt
|
|
189843
|
-
completedAt:
|
|
190220
|
+
startedAt,
|
|
190221
|
+
completedAt: startedAt
|
|
190222
|
+
};
|
|
190223
|
+
}
|
|
190224
|
+
const provider = input.agent === "codex" ? this.config.agents.codex.provider : undefined;
|
|
190225
|
+
let env;
|
|
190226
|
+
try {
|
|
190227
|
+
env = buildChildEnv(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
|
|
190228
|
+
agent: input.agent,
|
|
190229
|
+
model: agentConfig.model,
|
|
190230
|
+
provider,
|
|
190231
|
+
preferApiKey: agentConfig.auth.preferApiKey
|
|
190232
|
+
});
|
|
190233
|
+
} catch (error51) {
|
|
190234
|
+
return {
|
|
190235
|
+
agent: input.agent,
|
|
190236
|
+
role: input.role,
|
|
190237
|
+
status: "failed",
|
|
190238
|
+
startedAt,
|
|
190239
|
+
completedAt: new Date().toISOString(),
|
|
190240
|
+
error: buildPreflightFailure(error51)
|
|
190241
|
+
};
|
|
190242
|
+
}
|
|
190243
|
+
try {
|
|
190244
|
+
return await runSubprocessAgent(input.agent, agentConfig, input, env);
|
|
190245
|
+
} catch (error51) {
|
|
190246
|
+
return {
|
|
190247
|
+
agent: input.agent,
|
|
190248
|
+
role: input.role,
|
|
190249
|
+
status: "failed",
|
|
190250
|
+
startedAt,
|
|
190251
|
+
completedAt: new Date().toISOString(),
|
|
190252
|
+
error: buildAgentFailure(formatAgentErrorDetail(error51), "Agent process failed.")
|
|
189844
190253
|
};
|
|
189845
190254
|
}
|
|
189846
|
-
return runSubprocessAgent(input.agent, agentConfig, input);
|
|
189847
190255
|
}
|
|
189848
190256
|
}
|
|
189849
|
-
async function runSubprocessAgent(agent, agentConfig, input) {
|
|
190257
|
+
async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
189850
190258
|
const startedAt = new Date().toISOString();
|
|
189851
|
-
const env = buildChildEnv(process.env, agentConfig.auth.envWhitelist, agentConfig.env, {
|
|
189852
|
-
agent,
|
|
189853
|
-
model: agentConfig.model,
|
|
189854
|
-
preferApiKey: agentConfig.auth.preferApiKey
|
|
189855
|
-
});
|
|
189856
190259
|
return new Promise((resolveResult) => {
|
|
189857
190260
|
const child = spawn(agentConfig.command, agentConfig.args, {
|
|
189858
190261
|
cwd: input.workspaceDir,
|
|
@@ -189860,15 +190263,23 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189860
190263
|
stdio: ["pipe", "pipe", "pipe"]
|
|
189861
190264
|
});
|
|
189862
190265
|
let stdout = "";
|
|
189863
|
-
let
|
|
190266
|
+
let stderr3 = "";
|
|
189864
190267
|
let settled = false;
|
|
190268
|
+
let startedWrite;
|
|
190269
|
+
child.once("spawn", () => {
|
|
190270
|
+
if (settled)
|
|
190271
|
+
return;
|
|
190272
|
+
startedWrite = Promise.resolve().then(() => input.onStarted?.()).catch(() => {
|
|
190273
|
+
return;
|
|
190274
|
+
});
|
|
190275
|
+
});
|
|
189865
190276
|
const abortController = new AbortController;
|
|
189866
190277
|
const resolveOnce = (result) => {
|
|
189867
190278
|
if (settled)
|
|
189868
190279
|
return;
|
|
189869
190280
|
settled = true;
|
|
189870
190281
|
clearTimeout(timeout);
|
|
189871
|
-
resolveResult(result);
|
|
190282
|
+
(startedWrite ?? Promise.resolve()).then(() => resolveResult(result));
|
|
189872
190283
|
};
|
|
189873
190284
|
const timeout = setTimeout(() => {
|
|
189874
190285
|
abortController.abort(new Error("Kyoso agent timeout"));
|
|
@@ -189886,7 +190297,7 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189886
190297
|
});
|
|
189887
190298
|
}, input.timeoutMs);
|
|
189888
190299
|
child.stderr.on("data", (chunk) => {
|
|
189889
|
-
|
|
190300
|
+
stderr3 += chunk.toString("utf8");
|
|
189890
190301
|
});
|
|
189891
190302
|
child.on("error", (error51) => {
|
|
189892
190303
|
const failure = buildAgentFailure(error51.message, "Agent process could not be started.");
|
|
@@ -189915,7 +190326,7 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189915
190326
|
}).catch((error51) => {
|
|
189916
190327
|
if (abortController.signal.aborted)
|
|
189917
190328
|
return;
|
|
189918
|
-
const failureText = [
|
|
190329
|
+
const failureText = [stderr3, formatAgentErrorDetail(error51)].filter((part) => part.trim().length > 0).join(`
|
|
189919
190330
|
`);
|
|
189920
190331
|
resolveOnce({
|
|
189921
190332
|
agent,
|
|
@@ -189940,7 +190351,7 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189940
190351
|
rawText: stdout,
|
|
189941
190352
|
startedAt,
|
|
189942
190353
|
completedAt: new Date().toISOString(),
|
|
189943
|
-
error: buildAgentFailure(
|
|
190354
|
+
error: buildAgentFailure(stderr3, fallback)
|
|
189944
190355
|
});
|
|
189945
190356
|
});
|
|
189946
190357
|
});
|
|
@@ -190024,7 +190435,7 @@ function resolveEffortConfigOption(agent, effort) {
|
|
|
190024
190435
|
return;
|
|
190025
190436
|
}
|
|
190026
190437
|
async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
|
|
190027
|
-
const workspaceRoot = await
|
|
190438
|
+
const workspaceRoot = await realpath2(workspaceDir);
|
|
190028
190439
|
const candidates = resolveReadablePaths(workspaceRoot, requestedPath);
|
|
190029
190440
|
let content;
|
|
190030
190441
|
for (const absolute of candidates) {
|
|
@@ -190047,7 +190458,7 @@ async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
|
|
|
190047
190458
|
`);
|
|
190048
190459
|
}
|
|
190049
190460
|
async function resolveReadableFile(workspaceRoot, absolute) {
|
|
190050
|
-
const realPath = await
|
|
190461
|
+
const realPath = await realpath2(absolute).catch((error51) => {
|
|
190051
190462
|
if (isMissingPathError2(error51))
|
|
190052
190463
|
return;
|
|
190053
190464
|
throw error51;
|
|
@@ -190066,7 +190477,7 @@ function resolveReadablePaths(workspaceRoot, requestedPath) {
|
|
|
190066
190477
|
}
|
|
190067
190478
|
const repoPath = resolve3(workspaceRoot, "repo", relativePath);
|
|
190068
190479
|
assertWithinWorkspace(workspaceRoot, repoPath);
|
|
190069
|
-
return
|
|
190480
|
+
return isAbsolute3(requestedPath) ? [primary, repoPath] : [repoPath, primary];
|
|
190070
190481
|
}
|
|
190071
190482
|
function assertWithinWorkspace(workspaceRoot, absolute) {
|
|
190072
190483
|
const relativePath = relative(workspaceRoot, absolute);
|
|
@@ -190098,6 +190509,20 @@ function buildAgentFailure(rawDetail, fallbackMessage) {
|
|
|
190098
190509
|
...detail ? { detail } : {}
|
|
190099
190510
|
};
|
|
190100
190511
|
}
|
|
190512
|
+
function buildPreflightFailure(error51) {
|
|
190513
|
+
if (error51 instanceof ChildEnvPreflightError) {
|
|
190514
|
+
return {
|
|
190515
|
+
code: error51.code,
|
|
190516
|
+
message: error51.message
|
|
190517
|
+
};
|
|
190518
|
+
}
|
|
190519
|
+
const detail = sanitizeTextForDisplay(formatAgentErrorDetail(error51));
|
|
190520
|
+
return {
|
|
190521
|
+
code: "AGENT_CONFIG_INVALID",
|
|
190522
|
+
message: "Agent configuration is invalid. Run kyoso doctor and check agent configuration.",
|
|
190523
|
+
...detail ? { detail } : {}
|
|
190524
|
+
};
|
|
190525
|
+
}
|
|
190101
190526
|
function safeAgentFailureMessage(code, fallbackMessage) {
|
|
190102
190527
|
if (code === "AUTH_FAILED") {
|
|
190103
190528
|
return "Agent authentication failed. Run kyoso doctor and check configured credentials.";
|
|
@@ -190113,15 +190538,15 @@ function safeAgentFailureMessage(code, fallbackMessage) {
|
|
|
190113
190538
|
}
|
|
190114
190539
|
return sanitizeTextForDisplay(fallbackMessage);
|
|
190115
190540
|
}
|
|
190116
|
-
function classifyAgentFailure(
|
|
190117
|
-
if (/spawn/i.test(
|
|
190541
|
+
function classifyAgentFailure(stderr3) {
|
|
190542
|
+
if (/spawn/i.test(stderr3))
|
|
190118
190543
|
return "AGENT_SPAWN_FAILED";
|
|
190119
|
-
if (/ENOTFOUND|ENOTCACHED|ECONNREFUSED|ETIMEDOUT|registry\.npmjs\.org|network request|cache mode/i.test(
|
|
190544
|
+
if (/ENOTFOUND|ENOTCACHED|ECONNREFUSED|ETIMEDOUT|registry\.npmjs\.org|network request|cache mode/i.test(stderr3)) {
|
|
190120
190545
|
return "AGENT_NETWORK_FAILED";
|
|
190121
190546
|
}
|
|
190122
|
-
if (/auth|api key|login|credential/i.test(
|
|
190547
|
+
if (/auth|api key|login|credential/i.test(stderr3))
|
|
190123
190548
|
return "AUTH_FAILED";
|
|
190124
|
-
if (/permission|policy|write|terminal/i.test(
|
|
190549
|
+
if (/permission|policy|write|terminal/i.test(stderr3))
|
|
190125
190550
|
return "PERMISSION_DENIED";
|
|
190126
190551
|
return "AGENT_FAILED";
|
|
190127
190552
|
}
|
|
@@ -190158,9 +190583,24 @@ class FakeAgentManager extends BaseAcpAgentManager {
|
|
|
190158
190583
|
this.calls.push(input);
|
|
190159
190584
|
const startedAt = new Date().toISOString();
|
|
190160
190585
|
if (input.role === "finding_verifier") {
|
|
190586
|
+
await input.onStarted?.();
|
|
190161
190587
|
return verifierResult(input, startedAt, this.verifierScenarios[input.agent] ?? "confirmed");
|
|
190162
190588
|
}
|
|
190163
190589
|
const scenario = this.scenarios[input.agent] ?? "success";
|
|
190590
|
+
if (scenario === "preflight_failure" || scenario === "openrouter_key_missing") {
|
|
190591
|
+
return {
|
|
190592
|
+
agent: input.agent,
|
|
190593
|
+
role: input.role,
|
|
190594
|
+
status: "failed",
|
|
190595
|
+
startedAt,
|
|
190596
|
+
completedAt: new Date().toISOString(),
|
|
190597
|
+
error: {
|
|
190598
|
+
code: scenario === "openrouter_key_missing" ? "OPENROUTER_KEY_MISSING" : "AGENT_CONFIG_INVALID",
|
|
190599
|
+
message: scenario === "openrouter_key_missing" ? "Fake OpenRouter key is missing." : "Fake agent configuration is invalid."
|
|
190600
|
+
}
|
|
190601
|
+
};
|
|
190602
|
+
}
|
|
190603
|
+
await input.onStarted?.();
|
|
190164
190604
|
if (scenario === "timeout") {
|
|
190165
190605
|
return {
|
|
190166
190606
|
agent: input.agent,
|
|
@@ -190784,8 +191224,8 @@ function normalizeTitle(value) {
|
|
|
190784
191224
|
|
|
190785
191225
|
// src/audit/stateRoot.ts
|
|
190786
191226
|
import { createHash as createHash2 } from "node:crypto";
|
|
190787
|
-
import { lstat, mkdir as mkdir2, realpath as
|
|
190788
|
-
import { basename, dirname as
|
|
191227
|
+
import { lstat, mkdir as mkdir2, realpath as realpath3 } from "node:fs/promises";
|
|
191228
|
+
import { basename, dirname as dirname4, isAbsolute as isAbsolute4, join as join3, resolve as resolve5 } from "node:path";
|
|
190789
191229
|
|
|
190790
191230
|
// src/context/pathPolicy.ts
|
|
190791
191231
|
import { normalize, sep } from "node:path";
|
|
@@ -190876,7 +191316,7 @@ async function resolveAuditStateRoot(options) {
|
|
|
190876
191316
|
return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_CAPABILITY] };
|
|
190877
191317
|
}
|
|
190878
191318
|
try {
|
|
190879
|
-
const workspaceRoot = await
|
|
191319
|
+
const workspaceRoot = await realpath3(resolve5(options.cwd));
|
|
190880
191320
|
const candidate = resolveStateBaseCandidate(options.env ?? process.env);
|
|
190881
191321
|
if (!candidate)
|
|
190882
191322
|
throw new Error("missing trusted state base");
|
|
@@ -190905,7 +191345,7 @@ async function resolveAuditStateRoot(options) {
|
|
|
190905
191345
|
}
|
|
190906
191346
|
}
|
|
190907
191347
|
async function ensureTrustedDirectory(options) {
|
|
190908
|
-
const realRoot = await
|
|
191348
|
+
const realRoot = await realpath3(options.root);
|
|
190909
191349
|
assertSafeDirectory(realRoot, options.uid, await lstat(realRoot));
|
|
190910
191350
|
if (options.workspaceRoot && isPathWithin(realRoot, options.workspaceRoot)) {
|
|
190911
191351
|
throw new Error("trusted directory resolves inside workspace");
|
|
@@ -190922,7 +191362,7 @@ async function ensureTrustedDirectory(options) {
|
|
|
190922
191362
|
entry = await lstat(next);
|
|
190923
191363
|
}
|
|
190924
191364
|
assertSafeDirectory(next, options.uid, entry);
|
|
190925
|
-
const realNext = await
|
|
191365
|
+
const realNext = await realpath3(next);
|
|
190926
191366
|
if (!isPathWithin(realNext, realRoot)) {
|
|
190927
191367
|
throw new Error("managed directory escaped trusted root");
|
|
190928
191368
|
}
|
|
@@ -190938,7 +191378,7 @@ function isResolvedAuditStateRoot(resolution) {
|
|
|
190938
191378
|
}
|
|
190939
191379
|
function validateAuditDirectory(directory, warnings) {
|
|
190940
191380
|
try {
|
|
190941
|
-
if (directory.trim().length === 0 ||
|
|
191381
|
+
if (directory.trim().length === 0 || isAbsolute4(directory) || directory.split(/[\\/]+/).includes("..")) {
|
|
190942
191382
|
throw new Error("unsafe logical directory");
|
|
190943
191383
|
}
|
|
190944
191384
|
const normalized = normalizeRelativePath(directory);
|
|
@@ -190953,11 +191393,11 @@ function validateAuditDirectory(directory, warnings) {
|
|
|
190953
191393
|
}
|
|
190954
191394
|
function resolveStateBaseCandidate(env) {
|
|
190955
191395
|
const xdgStateHome = env.XDG_STATE_HOME?.trim();
|
|
190956
|
-
if (xdgStateHome &&
|
|
191396
|
+
if (xdgStateHome && isAbsolute4(xdgStateHome)) {
|
|
190957
191397
|
return resolve5(xdgStateHome);
|
|
190958
191398
|
}
|
|
190959
191399
|
const home = env.HOME?.trim();
|
|
190960
|
-
if (!home || !
|
|
191400
|
+
if (!home || !isAbsolute4(home))
|
|
190961
191401
|
return;
|
|
190962
191402
|
return join3(resolve5(home), ".local", "state");
|
|
190963
191403
|
}
|
|
@@ -190969,7 +191409,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190969
191409
|
assertSafeDirectory(existing.path, options.uid, await lstat(existing.path), {
|
|
190970
191410
|
allowFilesystemRoot: true
|
|
190971
191411
|
});
|
|
190972
|
-
const realExisting = await
|
|
191412
|
+
const realExisting = await realpath3(existing.path);
|
|
190973
191413
|
if (isPathWithin(realExisting, options.workspaceRoot)) {
|
|
190974
191414
|
throw new Error("state base resolves inside workspace");
|
|
190975
191415
|
}
|
|
@@ -190980,7 +191420,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190980
191420
|
await createDirectory(current);
|
|
190981
191421
|
const entry = await lstat(current);
|
|
190982
191422
|
assertSafeDirectory(current, options.uid, entry);
|
|
190983
|
-
const realCurrent = await
|
|
191423
|
+
const realCurrent = await realpath3(current);
|
|
190984
191424
|
if (!isPathWithin(realCurrent, realExisting)) {
|
|
190985
191425
|
throw new Error("state base changed while being created");
|
|
190986
191426
|
}
|
|
@@ -190989,7 +191429,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190989
191429
|
}
|
|
190990
191430
|
current = realCurrent;
|
|
190991
191431
|
}
|
|
190992
|
-
const stateBase = await
|
|
191432
|
+
const stateBase = await realpath3(current);
|
|
190993
191433
|
assertSafeDirectory(stateBase, options.uid, await lstat(stateBase));
|
|
190994
191434
|
if (isPathWithin(stateBase, options.workspaceRoot)) {
|
|
190995
191435
|
throw new Error("state base resolves inside workspace");
|
|
@@ -191008,7 +191448,7 @@ async function findExistingAncestor(candidate) {
|
|
|
191008
191448
|
}
|
|
191009
191449
|
return { path: current, missingSegments };
|
|
191010
191450
|
}
|
|
191011
|
-
const parent =
|
|
191451
|
+
const parent = dirname4(current);
|
|
191012
191452
|
if (parent === current)
|
|
191013
191453
|
throw new Error("state base has no existing ancestor");
|
|
191014
191454
|
missingSegments.unshift(basename(current));
|
|
@@ -191041,12 +191481,12 @@ function getCurrentUid(getuid) {
|
|
|
191041
191481
|
}
|
|
191042
191482
|
}
|
|
191043
191483
|
function isFilesystemRoot(path) {
|
|
191044
|
-
return
|
|
191484
|
+
return dirname4(path) === path;
|
|
191045
191485
|
}
|
|
191046
191486
|
async function assertTrustedAncestorChain(path, uid) {
|
|
191047
191487
|
let child = path;
|
|
191048
191488
|
while (!isFilesystemRoot(child)) {
|
|
191049
|
-
const parent =
|
|
191489
|
+
const parent = dirname4(child);
|
|
191050
191490
|
const [parentStat, childStat] = await Promise.all([
|
|
191051
191491
|
lstat(parent),
|
|
191052
191492
|
lstat(child)
|
|
@@ -191094,7 +191534,7 @@ function isAlreadyExistsError(error51) {
|
|
|
191094
191534
|
|
|
191095
191535
|
// src/audit/safeTraceFile.ts
|
|
191096
191536
|
import { constants } from "node:fs";
|
|
191097
|
-
import { lstat as lstat2, open, realpath as
|
|
191537
|
+
import { lstat as lstat2, open, realpath as realpath4, stat } from "node:fs/promises";
|
|
191098
191538
|
import { join as join4 } from "node:path";
|
|
191099
191539
|
var AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
|
|
191100
191540
|
async function openVerifiedTraceFile(options) {
|
|
@@ -191124,7 +191564,7 @@ async function openVerifiedTraceFile(options) {
|
|
|
191124
191564
|
const [handleStat, pathStat, realTracePath] = await Promise.all([
|
|
191125
191565
|
handle.stat({ bigint: true }),
|
|
191126
191566
|
stat(tracePath, { bigint: true }),
|
|
191127
|
-
|
|
191567
|
+
realpath4(tracePath)
|
|
191128
191568
|
]);
|
|
191129
191569
|
if (!handleStat.isFile() || !pathStat.isFile() || handleStat.dev !== pathStat.dev || handleStat.ino !== pathStat.ino || !isPathWithin(realTracePath, options.kyosoRoot)) {
|
|
191130
191570
|
throw new Error("trace file identity could not be verified");
|
|
@@ -192020,7 +192460,7 @@ function decide(input) {
|
|
|
192020
192460
|
|
|
192021
192461
|
// src/workspace/createSnapshot.ts
|
|
192022
192462
|
import { chmod, mkdir as mkdir3, mkdtemp, writeFile as writeFile2 } from "node:fs/promises";
|
|
192023
|
-
import { dirname as
|
|
192463
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
192024
192464
|
import { tmpdir } from "node:os";
|
|
192025
192465
|
async function createSnapshot(traceId, tool, request, options = {}) {
|
|
192026
192466
|
const root = await mkdtemp(join5(tmpdir(), `kyoso-${traceId}-`));
|
|
@@ -192036,7 +192476,7 @@ async function createSnapshot(traceId, tool, request, options = {}) {
|
|
|
192036
192476
|
if (!isAllowedPath(relative2, options.allowPatterns ?? []))
|
|
192037
192477
|
continue;
|
|
192038
192478
|
const dest = join5(repoDir, relative2);
|
|
192039
|
-
await mkdir3(
|
|
192479
|
+
await mkdir3(dirname5(dest), { recursive: true });
|
|
192040
192480
|
await writeFile2(dest, file2.content, "utf8");
|
|
192041
192481
|
await chmod(dest, 292).catch(() => {
|
|
192042
192482
|
return;
|
|
@@ -192374,7 +192814,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192374
192814
|
fileCount: snapshot.fileCount,
|
|
192375
192815
|
timestamp: new Date().toISOString()
|
|
192376
192816
|
});
|
|
192377
|
-
const manager = options.agentManager ?? defaultAgentManager(loaded.config);
|
|
192817
|
+
const manager = options.agentManager ?? defaultAgentManager(loaded.config, options.env ?? process.env);
|
|
192378
192818
|
const agentResults = await runAgents({
|
|
192379
192819
|
tool,
|
|
192380
192820
|
request: built.request,
|
|
@@ -192383,7 +192823,8 @@ async function runReview(tool, request, options = {}) {
|
|
|
192383
192823
|
workspaceDir: snapshot.root,
|
|
192384
192824
|
networkMode,
|
|
192385
192825
|
manager,
|
|
192386
|
-
trace
|
|
192826
|
+
trace,
|
|
192827
|
+
warnings
|
|
192387
192828
|
});
|
|
192388
192829
|
warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
|
|
192389
192830
|
const normalizedAgentResults = agentResults.map(normalizeAgentRunResult);
|
|
@@ -192658,24 +193099,51 @@ function buildCrossModelAnalysis(judge, reviewMode) {
|
|
|
192658
193099
|
}
|
|
192659
193100
|
async function runAgents(input) {
|
|
192660
193101
|
const agentRoles = resolveAgentRoles(input.config);
|
|
192661
|
-
const
|
|
192662
|
-
|
|
192663
|
-
|
|
192664
|
-
|
|
192665
|
-
|
|
192666
|
-
|
|
192667
|
-
|
|
192668
|
-
|
|
192669
|
-
|
|
192670
|
-
|
|
192671
|
-
|
|
192672
|
-
|
|
192673
|
-
|
|
192674
|
-
|
|
192675
|
-
|
|
192676
|
-
|
|
192677
|
-
|
|
193102
|
+
const startedWrites = [];
|
|
193103
|
+
let acceptingStartedEvents = true;
|
|
193104
|
+
const agentInputs = ["codex", "claude"].filter((agent) => input.config.agents[agent].enabled).map((agent) => {
|
|
193105
|
+
const agentConfig = input.config.agents[agent];
|
|
193106
|
+
const role = agentRoles[agent] ?? agentConfig.role;
|
|
193107
|
+
return {
|
|
193108
|
+
traceId: input.traceId,
|
|
193109
|
+
agent,
|
|
193110
|
+
role,
|
|
193111
|
+
tool: input.tool,
|
|
193112
|
+
prompt: buildAgentPrompt(input.tool, input.request, agent, role),
|
|
193113
|
+
workspaceDir: input.workspaceDir,
|
|
193114
|
+
timeoutMs: input.request.options?.maxAgentTimeoutMs ?? agentConfig.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS,
|
|
193115
|
+
networkMode: input.networkMode,
|
|
193116
|
+
onStarted: () => {
|
|
193117
|
+
if (!acceptingStartedEvents)
|
|
193118
|
+
return Promise.resolve();
|
|
193119
|
+
const event = {
|
|
193120
|
+
type: "agent_started",
|
|
193121
|
+
traceId: input.traceId,
|
|
193122
|
+
agent,
|
|
193123
|
+
role,
|
|
193124
|
+
timestamp: new Date().toISOString()
|
|
193125
|
+
};
|
|
193126
|
+
if (agentConfig.model) {
|
|
193127
|
+
event.model = sanitizeTextForDisplay(agentConfig.model);
|
|
193128
|
+
}
|
|
193129
|
+
if (agent === "codex" && input.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER) {
|
|
193130
|
+
event.provider = CODEX_OPENROUTER_PROVIDER;
|
|
193131
|
+
}
|
|
193132
|
+
const write = (async () => {
|
|
193133
|
+
try {
|
|
193134
|
+
await input.trace.write(event);
|
|
193135
|
+
} catch {
|
|
193136
|
+
input.warnings.push("AUDIT_WRITE_FAILED: agent_started event could not be recorded.");
|
|
193137
|
+
}
|
|
193138
|
+
})();
|
|
193139
|
+
startedWrites.push(write);
|
|
193140
|
+
return write;
|
|
193141
|
+
}
|
|
193142
|
+
};
|
|
193143
|
+
});
|
|
192678
193144
|
const results = await input.manager.runAll(agentInputs);
|
|
193145
|
+
acceptingStartedEvents = false;
|
|
193146
|
+
await Promise.all(startedWrites);
|
|
192679
193147
|
await Promise.all(results.map((result) => {
|
|
192680
193148
|
const event = {
|
|
192681
193149
|
type: "agent_completed",
|
|
@@ -192707,10 +193175,11 @@ function resolveAgentRoles(config2) {
|
|
|
192707
193175
|
}
|
|
192708
193176
|
return roles;
|
|
192709
193177
|
}
|
|
192710
|
-
function defaultAgentManager(config2) {
|
|
192711
|
-
if (
|
|
193178
|
+
function defaultAgentManager(config2, parentEnv) {
|
|
193179
|
+
if (parentEnv.KYOSO_TEST_FAKE_AGENTS === "1") {
|
|
192712
193180
|
return new FakeAgentManager;
|
|
192713
|
-
|
|
193181
|
+
}
|
|
193182
|
+
return new SubprocessAcpAgentManager(config2, parentEnv);
|
|
192714
193183
|
}
|
|
192715
193184
|
function normalizeAgentRunResult(result) {
|
|
192716
193185
|
if (result.status === "completed" && result.rawText && !result.normalized) {
|