agentera 0.0.0 → 3.0.0-dev.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/README.md +6 -45
- package/bundle/.agentera-npx-bundle.json +4 -0
- package/bundle/references/adapters/cursor.md +213 -0
- package/bundle/references/adapters/opencode.md +530 -0
- package/bundle/references/adapters/package-manifest-interface-model.yaml +337 -0
- package/bundle/references/adapters/package-registry.yaml +247 -0
- package/bundle/references/adapters/package-surface-characterization.md +48 -0
- package/bundle/references/adapters/runtime-adapter-characterization.md +79 -0
- package/bundle/references/adapters/runtime-adapter-interface-model.yaml +200 -0
- package/bundle/references/adapters/runtime-adapter-registry.yaml +548 -0
- package/bundle/references/adapters/runtime-feature-parity.md +189 -0
- package/bundle/references/analysis/benchmark.md +267 -0
- package/bundle/references/analysis/startup-measurement-contract.yaml +424 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +288 -0
- package/bundle/references/cli/agent-ready-state-contract.yaml +950 -0
- package/bundle/references/cli/app-lifecycle-vocabulary.yaml +233 -0
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +355 -0
- package/bundle/references/cli/bundle-skill-vocabulary.yaml +278 -0
- package/bundle/references/cli/capability-instruction-contract.yaml +123 -0
- package/bundle/references/cli/capability-tool-classification.yaml +53 -0
- package/bundle/references/cli/routing-execution-vocabulary.yaml +281 -0
- package/bundle/references/cli/update-channels.yaml +120 -0
- package/bundle/references/cli/vocabulary-index.yaml +160 -0
- package/bundle/references/cli/vocabulary.md +562 -0
- package/bundle/references/meta/documentation-inventory.md +43 -0
- package/bundle/references/v1-section-mapping.md +47 -0
- package/bundle/registry.json +39 -0
- package/bundle/skills/agentera/.claude-plugin/plugin.json +27 -0
- package/bundle/skills/agentera/SKILL.md +470 -0
- package/bundle/skills/agentera/agents/dokumentera.toml +6 -0
- package/bundle/skills/agentera/agents/hej.toml +6 -0
- package/bundle/skills/agentera/agents/inspektera.toml +6 -0
- package/bundle/skills/agentera/agents/inspirera.toml +6 -0
- package/bundle/skills/agentera/agents/optimera.toml +6 -0
- package/bundle/skills/agentera/agents/orkestrera.toml +6 -0
- package/bundle/skills/agentera/agents/planera.toml +6 -0
- package/bundle/skills/agentera/agents/profilera.toml +6 -0
- package/bundle/skills/agentera/agents/realisera.toml +6 -0
- package/bundle/skills/agentera/agents/resonera.toml +6 -0
- package/bundle/skills/agentera/agents/visionera.toml +6 -0
- package/bundle/skills/agentera/agents/visualisera.toml +6 -0
- package/bundle/skills/agentera/capabilities/dokumentera/instructions.md +428 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/artifacts.yaml +73 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/triggers.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/validation.yaml +139 -0
- package/bundle/skills/agentera/capabilities/hej/instructions.md +331 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/exit.yaml +32 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/triggers.yaml +58 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/validation.yaml +55 -0
- package/bundle/skills/agentera/capabilities/inspektera/instructions.md +514 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/artifacts.yaml +76 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/exit.yaml +36 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/triggers.yaml +38 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/validation.yaml +113 -0
- package/bundle/skills/agentera/capabilities/inspirera/instructions.md +280 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/artifacts.yaml +24 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/validation.yaml +58 -0
- package/bundle/skills/agentera/capabilities/optimera/instructions.md +437 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/validation.yaml +91 -0
- package/bundle/skills/agentera/capabilities/orkestrera/instructions.md +433 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/artifacts.yaml +64 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/triggers.yaml +42 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/validation.yaml +107 -0
- package/bundle/skills/agentera/capabilities/planera/instructions.md +368 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/artifacts.yaml +62 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/validation.yaml +61 -0
- package/bundle/skills/agentera/capabilities/profilera/instructions.md +419 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/artifacts.yaml +18 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/triggers.yaml +45 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/validation.yaml +57 -0
- package/bundle/skills/agentera/capabilities/realisera/instructions.md +403 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/artifacts.yaml +80 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/validation.yaml +110 -0
- package/bundle/skills/agentera/capabilities/resonera/instructions.md +329 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/artifacts.yaml +47 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/triggers.yaml +46 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/validation.yaml +77 -0
- package/bundle/skills/agentera/capabilities/visionera/instructions.md +309 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/artifacts.yaml +57 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/triggers.yaml +41 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/validation.yaml +74 -0
- package/bundle/skills/agentera/capabilities/visualisera/instructions.md +400 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/artifacts.yaml +44 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/triggers.yaml +33 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/validation.yaml +80 -0
- package/bundle/skills/agentera/capability_schema_contract.yaml +385 -0
- package/bundle/skills/agentera/protocol.yaml +463 -0
- package/bundle/skills/agentera/references/contract.md +1039 -0
- package/bundle/skills/agentera/schemas/artifacts/changelog.yaml +60 -0
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +461 -0
- package/bundle/skills/agentera/schemas/artifacts/design.yaml +55 -0
- package/bundle/skills/agentera/schemas/artifacts/docs.yaml +402 -0
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +373 -0
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +484 -0
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +399 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +342 -0
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +325 -0
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +110 -0
- package/bundle/skills/agentera/schemas/artifacts/vision.yaml +262 -0
- package/bundle/skills/hej/.claude-plugin/plugin.json +6 -0
- package/bundle/skills/hej/SKILL.md +69 -0
- package/bundle/skills/hej/agents/hej.toml +11 -0
- package/bundle/skills/hej/agents/openai.yaml +8 -0
- package/dist/analytics/extractCorpus.js +1791 -0
- package/dist/analytics/extractCorpus.js.map +1 -0
- package/dist/analytics/usageStats.js +487 -0
- package/dist/analytics/usageStats.js.map +1 -0
- package/dist/bin/agentera.js +4 -0
- package/dist/bin/agentera.js.map +1 -0
- package/dist/cli/appContext.js +226 -0
- package/dist/cli/appContext.js.map +1 -0
- package/dist/cli/argvalidate.js +41 -0
- package/dist/cli/argvalidate.js.map +1 -0
- package/dist/cli/capabilityContext.js +2421 -0
- package/dist/cli/capabilityContext.js.map +1 -0
- package/dist/cli/commands/backfill.js +84 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/capability.js +44 -0
- package/dist/cli/commands/capability.js.map +1 -0
- package/dist/cli/commands/compact.js +148 -0
- package/dist/cli/commands/compact.js.map +1 -0
- package/dist/cli/commands/doctor.js +180 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/lint.js +179 -0
- package/dist/cli/commands/lint.js.map +1 -0
- package/dist/cli/commands/prime.js +545 -0
- package/dist/cli/commands/prime.js.map +1 -0
- package/dist/cli/commands/query.js +346 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/report.js +210 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/schema.js +306 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/state.js +1012 -0
- package/dist/cli/commands/state.js.map +1 -0
- package/dist/cli/commands/upgrade.js +49 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/commands/validate.js +519 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.js +204 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/dispatch.js +962 -0
- package/dist/cli/dispatch.js.map +1 -0
- package/dist/cli/orientation.js +595 -0
- package/dist/cli/orientation.js.map +1 -0
- package/dist/cli/prime-blob.js +3 -0
- package/dist/cli/prime-blob.js.map +1 -0
- package/dist/cli/stateQuery.js +292 -0
- package/dist/cli/stateQuery.js.map +1 -0
- package/dist/cli/structured.js +18 -0
- package/dist/cli/structured.js.map +1 -0
- package/dist/core/difflib.js +274 -0
- package/dist/core/difflib.js.map +1 -0
- package/dist/core/git.js +43 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/paths.js +50 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/pyjson.js +101 -0
- package/dist/core/pyjson.js.map +1 -0
- package/dist/core/sourceRoot.js +72 -0
- package/dist/core/sourceRoot.js.map +1 -0
- package/dist/core/toml.js +11 -0
- package/dist/core/toml.js.map +1 -0
- package/dist/core/yaml.js +25 -0
- package/dist/core/yaml.js.map +1 -0
- package/dist/eval/evalSkills.js +258 -0
- package/dist/eval/evalSkills.js.map +1 -0
- package/dist/eval/semanticEval.js +148 -0
- package/dist/eval/semanticEval.js.map +1 -0
- package/dist/eval/semanticFixtures.js +227 -0
- package/dist/eval/semanticFixtures.js.map +1 -0
- package/dist/hooks/common.js +160 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/compaction.js +935 -0
- package/dist/hooks/compaction.js.map +1 -0
- package/dist/hooks/cursorPreToolUse.js +19 -0
- package/dist/hooks/cursorPreToolUse.js.map +1 -0
- package/dist/hooks/cursorSessionStart.js +71 -0
- package/dist/hooks/cursorSessionStart.js.map +1 -0
- package/dist/hooks/sessionStart.js +209 -0
- package/dist/hooks/sessionStart.js.map +1 -0
- package/dist/hooks/sessionStop.js +212 -0
- package/dist/hooks/sessionStop.js.map +1 -0
- package/dist/hooks/validateArtifact.js +933 -0
- package/dist/hooks/validateArtifact.js.map +1 -0
- package/dist/registries/artifactRegistry.js +206 -0
- package/dist/registries/artifactRegistry.js.map +1 -0
- package/dist/registries/capabilityContract.js +310 -0
- package/dist/registries/capabilityContract.js.map +1 -0
- package/dist/registries/packageRegistry.js +641 -0
- package/dist/registries/packageRegistry.js.map +1 -0
- package/dist/registries/runtimeAdapterRegistry.js +315 -0
- package/dist/registries/runtimeAdapterRegistry.js.map +1 -0
- package/dist/setup/codex.js +1052 -0
- package/dist/setup/codex.js.map +1 -0
- package/dist/setup/copilot.js +227 -0
- package/dist/setup/copilot.js.map +1 -0
- package/dist/setup/cursor.js +127 -0
- package/dist/setup/cursor.js.map +1 -0
- package/dist/setup/doctor.js +1269 -0
- package/dist/setup/doctor.js.map +1 -0
- package/dist/state/installRoot.js +279 -0
- package/dist/state/installRoot.js.map +1 -0
- package/dist/state/progressCommit.js +289 -0
- package/dist/state/progressCommit.js.map +1 -0
- package/dist/state/startupAnalysis.js +1953 -0
- package/dist/state/startupAnalysis.js.map +1 -0
- package/dist/upgrade/appModel.js +189 -0
- package/dist/upgrade/appModel.js.map +1 -0
- package/dist/upgrade/channels.js +197 -0
- package/dist/upgrade/channels.js.map +1 -0
- package/dist/upgrade/compatibility.js +197 -0
- package/dist/upgrade/compatibility.js.map +1 -0
- package/dist/upgrade/doctor.js +368 -0
- package/dist/upgrade/doctor.js.map +1 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js +412 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -0
- package/dist/upgrade/upgradeCommands.js +40 -0
- package/dist/upgrade/upgradeCommands.js.map +1 -0
- package/dist/upgrade/upgradeOrchestrator.js +280 -0
- package/dist/upgrade/upgradeOrchestrator.js.map +1 -0
- package/dist/validate/appHomeContract.js +150 -0
- package/dist/validate/appHomeContract.js.map +1 -0
- package/dist/validate/capability.js +412 -0
- package/dist/validate/capability.js.map +1 -0
- package/dist/validate/crossCapability.js +145 -0
- package/dist/validate/crossCapability.js.map +1 -0
- package/dist/validate/lifecycleAdapters.js +772 -0
- package/dist/validate/lifecycleAdapters.js.map +1 -0
- package/dist/validate/selfAudit.js +107 -0
- package/dist/validate/selfAudit.js.map +1 -0
- package/package.json +28 -8
- package/LICENSE +0 -201
- package/bin/agentera.mjs +0 -50
- package/lib/exec.mjs +0 -116
- package/lib/resolve.mjs +0 -129
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { expanduser, isFile, pathExists, resolvePath } from "../core/paths.js";
|
|
6
|
+
import { splitLinesKeepEnds, unifiedDiff } from "../core/difflib.js";
|
|
7
|
+
import { parseToml } from "../core/toml.js";
|
|
8
|
+
import { SETUP_EVIDENCE, classifyResolvedRoot } from "../state/installRoot.js";
|
|
9
|
+
export const CANONICAL_ENTRIES = SETUP_EVIDENCE;
|
|
10
|
+
export const MANAGED_KEY = "AGENTERA_HOME";
|
|
11
|
+
export const SECTION_NAME = "shell_environment_policy";
|
|
12
|
+
export const DEFAULT_AGENT_LIMITS = { max_depth: 1 };
|
|
13
|
+
export const CAPABILITY_AGENT_NAMES = [
|
|
14
|
+
"hej",
|
|
15
|
+
"visionera",
|
|
16
|
+
"resonera",
|
|
17
|
+
"inspirera",
|
|
18
|
+
"planera",
|
|
19
|
+
"realisera",
|
|
20
|
+
"optimera",
|
|
21
|
+
"inspektera",
|
|
22
|
+
"dokumentera",
|
|
23
|
+
"profilera",
|
|
24
|
+
"visualisera",
|
|
25
|
+
"orkestrera",
|
|
26
|
+
];
|
|
27
|
+
const ENV_FALLBACKS = ["AGENTERA_HOME", "CLAUDE_PLUGIN_ROOT"];
|
|
28
|
+
export const CODEX_HOOK_COMMAND = 'uv run "${AGENTERA_HOME}/hooks/validate_artifact.py"';
|
|
29
|
+
export const CODEX_PLUGIN_ID = "agentera@agentera";
|
|
30
|
+
export const CODEX_PLUGIN_HOOKS_PATH = "hooks/codex-plugin-hooks.json";
|
|
31
|
+
export const CODEX_PLUGIN_HOOK_SOURCE = `${CODEX_PLUGIN_ID}:${CODEX_PLUGIN_HOOKS_PATH}`;
|
|
32
|
+
export const CODEX_PLUGIN_HOOK_COMMAND = 'uv run "${PLUGIN_ROOT}/hooks/validate_artifact.py"';
|
|
33
|
+
export const CODEX_HOOK_MATCHER = "^apply_patch$";
|
|
34
|
+
export const CODEX_HOOK_TIMEOUT = 10;
|
|
35
|
+
export const CODEX_HOOK_STATUS_MESSAGE = "validating artifact";
|
|
36
|
+
export function defaultConfigPath(home = os.homedir()) {
|
|
37
|
+
return path.join(home, ".codex", "config.toml");
|
|
38
|
+
}
|
|
39
|
+
export class InstallRootError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "InstallRootError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function verifyInstallRoot(root) {
|
|
46
|
+
const classification = classifyResolvedRoot(root, { source: "explicit" });
|
|
47
|
+
if (classification.kind === "managed_fresh") {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return SETUP_EVIDENCE.filter((entry) => !pathExists(path.join(root, entry)));
|
|
51
|
+
}
|
|
52
|
+
export function autoDetectInstallRoot(start = null, env = process.env) {
|
|
53
|
+
for (const variable of ENV_FALLBACKS) {
|
|
54
|
+
const candidate = env[variable];
|
|
55
|
+
if (candidate) {
|
|
56
|
+
const p = resolvePath(candidate);
|
|
57
|
+
if (verifyInstallRoot(p).length === 0) {
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let current = resolvePath(start === null ? process.cwd() : start);
|
|
63
|
+
for (;;) {
|
|
64
|
+
if (verifyInstallRoot(current).length === 0) {
|
|
65
|
+
return current;
|
|
66
|
+
}
|
|
67
|
+
const parent = path.dirname(current);
|
|
68
|
+
if (parent === current) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
current = parent;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function resolveInstallRoot(explicit, env = process.env) {
|
|
75
|
+
if (explicit !== null && explicit !== undefined) {
|
|
76
|
+
const root = resolvePath(explicit);
|
|
77
|
+
if (classifyResolvedRoot(root, { source: "explicit" }).kind !== "managed_fresh") {
|
|
78
|
+
const missing = verifyInstallRoot(root);
|
|
79
|
+
throw new InstallRootError(`--install-root ${root} is not a valid Agentera directory: ` +
|
|
80
|
+
`missing canonical entries: ${missing.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
return root;
|
|
83
|
+
}
|
|
84
|
+
const detected = autoDetectInstallRoot(null, env);
|
|
85
|
+
if (detected === null) {
|
|
86
|
+
throw new InstallRootError("could not auto-detect the Agentera directory. " +
|
|
87
|
+
"Pass --install-root PATH where PATH contains " +
|
|
88
|
+
`${CANONICAL_ENTRIES.join(", ")}.`);
|
|
89
|
+
}
|
|
90
|
+
return detected;
|
|
91
|
+
}
|
|
92
|
+
/** Python repr() for a scalar, used to display non-string sibling set values. */
|
|
93
|
+
function pyRepr(value) {
|
|
94
|
+
if (value === null || value === undefined)
|
|
95
|
+
return "None";
|
|
96
|
+
if (value === true)
|
|
97
|
+
return "True";
|
|
98
|
+
if (value === false)
|
|
99
|
+
return "False";
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
return value.includes("'") && !value.includes('"') ? `"${value}"` : `'${value}'`;
|
|
102
|
+
}
|
|
103
|
+
return String(value);
|
|
104
|
+
}
|
|
105
|
+
export function classifyToml(text) {
|
|
106
|
+
if (!text.trim()) {
|
|
107
|
+
return { sectionPresent: false, setPresent: false, setTable: {} };
|
|
108
|
+
}
|
|
109
|
+
const parsed = parseToml(text);
|
|
110
|
+
const section = parsed[SECTION_NAME];
|
|
111
|
+
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
112
|
+
return { sectionPresent: false, setPresent: false, setTable: {} };
|
|
113
|
+
}
|
|
114
|
+
const setValue = section.set;
|
|
115
|
+
if (!setValue || typeof setValue !== "object" || Array.isArray(setValue)) {
|
|
116
|
+
return { sectionPresent: true, setPresent: false, setTable: {} };
|
|
117
|
+
}
|
|
118
|
+
const coerced = {};
|
|
119
|
+
for (const [key, value] of Object.entries(setValue)) {
|
|
120
|
+
coerced[String(key)] = typeof value === "string" ? value : pyRepr(value);
|
|
121
|
+
}
|
|
122
|
+
return { sectionPresent: true, setPresent: true, setTable: coerced };
|
|
123
|
+
}
|
|
124
|
+
// ── TOML emission ──────────────────────────────────────────────────
|
|
125
|
+
const BASIC_STRING_ESCAPE = {
|
|
126
|
+
"\\": "\\\\",
|
|
127
|
+
'"': '\\"',
|
|
128
|
+
"\b": "\\b",
|
|
129
|
+
"\f": "\\f",
|
|
130
|
+
"\n": "\\n",
|
|
131
|
+
"\r": "\\r",
|
|
132
|
+
"\t": "\\t",
|
|
133
|
+
};
|
|
134
|
+
export function tomlBasicString(value) {
|
|
135
|
+
const escapedChars = [];
|
|
136
|
+
for (const char of value) {
|
|
137
|
+
if (char in BASIC_STRING_ESCAPE) {
|
|
138
|
+
escapedChars.push(BASIC_STRING_ESCAPE[char]);
|
|
139
|
+
}
|
|
140
|
+
else if (char.charCodeAt(0) < 0x20) {
|
|
141
|
+
escapedChars.push("\\u" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0"));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
escapedChars.push(char);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return '"' + escapedChars.join("") + '"';
|
|
148
|
+
}
|
|
149
|
+
export function emitSetInlineTable(pairs) {
|
|
150
|
+
const entries = Object.entries(pairs);
|
|
151
|
+
const rendered = entries.map(([key, value]) => `${key} = ${tomlBasicString(value)}`).join(", ");
|
|
152
|
+
return entries.length > 0 ? `{ ${rendered} }` : "{ }";
|
|
153
|
+
}
|
|
154
|
+
export function renderAgentsConfigSection() {
|
|
155
|
+
return "[agents]\n" + Object.entries(DEFAULT_AGENT_LIMITS).map(([k, v]) => `${k} = ${v}`).join("\n") + "\n";
|
|
156
|
+
}
|
|
157
|
+
export function renderFreshConfig(installRoot) {
|
|
158
|
+
const setValue = emitSetInlineTable({ [MANAGED_KEY]: installRoot });
|
|
159
|
+
return (`[${SECTION_NAME}]\nset = ${setValue}\n\n` +
|
|
160
|
+
`${renderAgentsConfigSection()}\n` +
|
|
161
|
+
`[features.multi_agent_v2]\n` +
|
|
162
|
+
`max_concurrent_threads_per_session = 6\n`);
|
|
163
|
+
}
|
|
164
|
+
// ── Codex hook trust hashing ───────────────────────────────────────
|
|
165
|
+
/** Compact canonical JSON: sort_keys=True, separators=(",",":"), ensure_ascii. */
|
|
166
|
+
function canonicalJson(value) {
|
|
167
|
+
if (value === null || value === undefined)
|
|
168
|
+
return "null";
|
|
169
|
+
if (value === true)
|
|
170
|
+
return "true";
|
|
171
|
+
if (value === false)
|
|
172
|
+
return "false";
|
|
173
|
+
if (typeof value === "number")
|
|
174
|
+
return String(value);
|
|
175
|
+
if (typeof value === "string")
|
|
176
|
+
return jsonStringAscii(value);
|
|
177
|
+
if (Array.isArray(value))
|
|
178
|
+
return "[" + value.map((v) => canonicalJson(v)).join(",") + "]";
|
|
179
|
+
if (typeof value === "object") {
|
|
180
|
+
const keys = Object.keys(value).sort();
|
|
181
|
+
return "{" + keys.map((k) => `${jsonStringAscii(k)}:${canonicalJson(value[k])}`).join(",") + "}";
|
|
182
|
+
}
|
|
183
|
+
return "null";
|
|
184
|
+
}
|
|
185
|
+
function jsonStringAscii(str) {
|
|
186
|
+
let out = '"';
|
|
187
|
+
for (const ch of str) {
|
|
188
|
+
const cp = ch.codePointAt(0);
|
|
189
|
+
if (ch === '"')
|
|
190
|
+
out += '\\"';
|
|
191
|
+
else if (ch === "\\")
|
|
192
|
+
out += "\\\\";
|
|
193
|
+
else if (cp === 0x08)
|
|
194
|
+
out += "\\b";
|
|
195
|
+
else if (cp === 0x09)
|
|
196
|
+
out += "\\t";
|
|
197
|
+
else if (cp === 0x0a)
|
|
198
|
+
out += "\\n";
|
|
199
|
+
else if (cp === 0x0c)
|
|
200
|
+
out += "\\f";
|
|
201
|
+
else if (cp === 0x0d)
|
|
202
|
+
out += "\\r";
|
|
203
|
+
else if (cp < 0x20)
|
|
204
|
+
out += "\\u" + cp.toString(16).padStart(4, "0");
|
|
205
|
+
else if (cp < 0x80)
|
|
206
|
+
out += ch;
|
|
207
|
+
else if (cp > 0xffff) {
|
|
208
|
+
const v = cp - 0x10000;
|
|
209
|
+
out += "\\u" + (0xd800 + (v >> 10)).toString(16).padStart(4, "0");
|
|
210
|
+
out += "\\u" + (0xdc00 + (v & 0x3ff)).toString(16).padStart(4, "0");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
out += "\\u" + cp.toString(16).padStart(4, "0");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out + '"';
|
|
217
|
+
}
|
|
218
|
+
export function codexHookTrustedHash(eventLabel, matcher, command = CODEX_HOOK_COMMAND, timeout = CODEX_HOOK_TIMEOUT, statusMessage = CODEX_HOOK_STATUS_MESSAGE) {
|
|
219
|
+
const handler = { type: "command", command, timeout, async: false };
|
|
220
|
+
if (statusMessage !== null)
|
|
221
|
+
handler.statusMessage = statusMessage;
|
|
222
|
+
const identity = { event_name: eventLabel, hooks: [handler] };
|
|
223
|
+
if (matcher !== null)
|
|
224
|
+
identity.matcher = matcher;
|
|
225
|
+
const payload = canonicalJson(identity);
|
|
226
|
+
return "sha256:" + crypto.createHash("sha256").update(payload, "utf8").digest("hex");
|
|
227
|
+
}
|
|
228
|
+
export function codexValidatorCommand(installRoot) {
|
|
229
|
+
const candidates = [
|
|
230
|
+
path.join(installRoot, "app", "hooks", "validate_artifact.py"),
|
|
231
|
+
path.join(installRoot, "hooks", "validate_artifact.py"),
|
|
232
|
+
];
|
|
233
|
+
const validator = candidates.find((c) => isFile(c)) ?? candidates[0];
|
|
234
|
+
return `uv run "${validator}"`;
|
|
235
|
+
}
|
|
236
|
+
export function renderCodexHooksConfig(command) {
|
|
237
|
+
const hooks = {};
|
|
238
|
+
for (const event of ["PreToolUse", "PostToolUse"]) {
|
|
239
|
+
hooks[event] = [
|
|
240
|
+
{
|
|
241
|
+
matcher: CODEX_HOOK_MATCHER,
|
|
242
|
+
hooks: [
|
|
243
|
+
{ type: "command", command, timeout: CODEX_HOOK_TIMEOUT, statusMessage: CODEX_HOOK_STATUS_MESSAGE },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
const payload = {
|
|
249
|
+
description: "agentera v2 Codex hooks: schema-backed apply_patch artifact validation",
|
|
250
|
+
hooks,
|
|
251
|
+
};
|
|
252
|
+
return JSON.stringify(payload, null, 2) + "\n";
|
|
253
|
+
}
|
|
254
|
+
export function codexHookStateEntries(hooksPath, command = CODEX_HOOK_COMMAND) {
|
|
255
|
+
const resolved = resolvePath(hooksPath);
|
|
256
|
+
return {
|
|
257
|
+
[`${resolved}:pre_tool_use:0:0`]: codexHookTrustedHash("pre_tool_use", CODEX_HOOK_MATCHER, command),
|
|
258
|
+
[`${resolved}:post_tool_use:0:0`]: codexHookTrustedHash("post_tool_use", CODEX_HOOK_MATCHER, command),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
export function codexPluginHookStateEntries(command = CODEX_PLUGIN_HOOK_COMMAND) {
|
|
262
|
+
return {
|
|
263
|
+
[`${CODEX_PLUGIN_HOOK_SOURCE}:pre_tool_use:0:0`]: codexHookTrustedHash("pre_tool_use", CODEX_HOOK_MATCHER, command),
|
|
264
|
+
[`${CODEX_PLUGIN_HOOK_SOURCE}:post_tool_use:0:0`]: codexHookTrustedHash("post_tool_use", CODEX_HOOK_MATCHER, command),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
export function codexPluginHooksEnabled(text) {
|
|
268
|
+
if (!text || !text.trim())
|
|
269
|
+
return false;
|
|
270
|
+
const parsed = parseToml(text);
|
|
271
|
+
const plugins = parsed.plugins;
|
|
272
|
+
if (!plugins || typeof plugins !== "object" || Array.isArray(plugins))
|
|
273
|
+
return false;
|
|
274
|
+
const agentera = plugins[CODEX_PLUGIN_ID];
|
|
275
|
+
return Boolean(agentera && typeof agentera === "object" && agentera.enabled === true);
|
|
276
|
+
}
|
|
277
|
+
// ===========================================================================
|
|
278
|
+
// Slice 2: line-based TOML mutation engine
|
|
279
|
+
// ===========================================================================
|
|
280
|
+
function splitKeepEnds(text) {
|
|
281
|
+
return text.match(/[^\n]*\n|[^\n]+/g) ?? [];
|
|
282
|
+
}
|
|
283
|
+
function rstripEol(line) {
|
|
284
|
+
return line.replace(/[\r\n]+$/, "");
|
|
285
|
+
}
|
|
286
|
+
function lineTerminator(lineWithEnd) {
|
|
287
|
+
if (lineWithEnd.endsWith("\r\n"))
|
|
288
|
+
return "\r\n";
|
|
289
|
+
if (lineWithEnd.endsWith("\n"))
|
|
290
|
+
return "\n";
|
|
291
|
+
return "\n";
|
|
292
|
+
}
|
|
293
|
+
const SECTION_HEADER_RE = new RegExp(`^\\s*\\[\\s*${SECTION_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]\\s*$`);
|
|
294
|
+
const SET_LINE_RE = /^\s*set\s*=\s*/;
|
|
295
|
+
export function findSectionHeaderIndex(lines) {
|
|
296
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
297
|
+
if (SECTION_HEADER_RE.test(lines[idx]))
|
|
298
|
+
return idx;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
export function findSetLineIndex(lines, sectionIdx) {
|
|
303
|
+
for (let idx = sectionIdx + 1; idx < lines.length; idx++) {
|
|
304
|
+
const line = lines[idx];
|
|
305
|
+
if (SECTION_HEADER_RE.test(line))
|
|
306
|
+
return null;
|
|
307
|
+
if (/^\s*\[/.test(line))
|
|
308
|
+
return null;
|
|
309
|
+
if (SET_LINE_RE.test(line))
|
|
310
|
+
return idx;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
export function insertSetLine(text, installRoot) {
|
|
315
|
+
const linesWithEnds = splitKeepEnds(text);
|
|
316
|
+
const plainLines = linesWithEnds.map(rstripEol);
|
|
317
|
+
const sectionIdx = findSectionHeaderIndex(plainLines);
|
|
318
|
+
if (sectionIdx === null) {
|
|
319
|
+
throw new Error(`insert_set_line called but [${SECTION_NAME}] header not found`);
|
|
320
|
+
}
|
|
321
|
+
const terminator = lineTerminator(linesWithEnds[sectionIdx]);
|
|
322
|
+
const setValue = emitSetInlineTable({ [MANAGED_KEY]: installRoot });
|
|
323
|
+
const insertedLine = `set = ${setValue}${terminator}`;
|
|
324
|
+
return [...linesWithEnds.slice(0, sectionIdx + 1), insertedLine, ...linesWithEnds.slice(sectionIdx + 1)].join("");
|
|
325
|
+
}
|
|
326
|
+
export function rewriteSetLine(text, mergedPairs) {
|
|
327
|
+
const linesWithEnds = splitKeepEnds(text);
|
|
328
|
+
const plainLines = linesWithEnds.map(rstripEol);
|
|
329
|
+
const sectionIdx = findSectionHeaderIndex(plainLines);
|
|
330
|
+
if (sectionIdx === null) {
|
|
331
|
+
throw new Error(`rewrite_set_line called but [${SECTION_NAME}] header not found`);
|
|
332
|
+
}
|
|
333
|
+
const setIdx = findSetLineIndex(plainLines, sectionIdx);
|
|
334
|
+
if (setIdx === null) {
|
|
335
|
+
throw new Error(`rewrite_set_line called but no set line found in [${SECTION_NAME}]`);
|
|
336
|
+
}
|
|
337
|
+
const setLine = plainLines[setIdx];
|
|
338
|
+
if (setLine.includes("{") && !setLine.includes("}")) {
|
|
339
|
+
throw new Error("existing set value spans multiple lines; cannot safely merge");
|
|
340
|
+
}
|
|
341
|
+
const setLineWithEnd = linesWithEnds[setIdx];
|
|
342
|
+
const terminator = setLineWithEnd.endsWith("\r\n") ? "\r\n" : setLineWithEnd.endsWith("\n") ? "\n" : "";
|
|
343
|
+
const setValue = emitSetInlineTable(mergedPairs);
|
|
344
|
+
const newLine = `set = ${setValue}${terminator}`;
|
|
345
|
+
return [...linesWithEnds.slice(0, setIdx), newLine, ...linesWithEnds.slice(setIdx + 1)].join("");
|
|
346
|
+
}
|
|
347
|
+
function tableHeaderRe(table) {
|
|
348
|
+
const dotted = table
|
|
349
|
+
.split(".")
|
|
350
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
351
|
+
.join("\\s*\\.\\s*");
|
|
352
|
+
return new RegExp(`^\\s*\\[\\s*${dotted}\\s*\\]\\s*$`);
|
|
353
|
+
}
|
|
354
|
+
function findTableHeaderIndex(lines, table) {
|
|
355
|
+
const pattern = tableHeaderRe(table);
|
|
356
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
357
|
+
if (pattern.test(lines[idx]))
|
|
358
|
+
return idx;
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
function findTableKeyIndex(lines, tableIdx, keyLiteral) {
|
|
363
|
+
const keyRe = new RegExp(`^\\s*${keyLiteral.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=`);
|
|
364
|
+
for (let idx = tableIdx + 1; idx < lines.length; idx++) {
|
|
365
|
+
const line = lines[idx];
|
|
366
|
+
if (/^\s*\[/.test(line))
|
|
367
|
+
return null;
|
|
368
|
+
if (keyRe.test(line))
|
|
369
|
+
return idx;
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
function insertTableKeyLine(text, table, line) {
|
|
374
|
+
const linesWithEnds = splitKeepEnds(text);
|
|
375
|
+
const plainLines = linesWithEnds.map(rstripEol);
|
|
376
|
+
const tableIdx = findTableHeaderIndex(plainLines, table);
|
|
377
|
+
if (tableIdx === null)
|
|
378
|
+
throw new Error(`[${table}] header not found`);
|
|
379
|
+
const terminator = lineTerminator(linesWithEnds[tableIdx]);
|
|
380
|
+
return [...linesWithEnds.slice(0, tableIdx + 1), line + terminator, ...linesWithEnds.slice(tableIdx + 1)].join("");
|
|
381
|
+
}
|
|
382
|
+
function replaceTableKeyLine(text, table, keyLiteral, line) {
|
|
383
|
+
const linesWithEnds = splitKeepEnds(text);
|
|
384
|
+
const plainLines = linesWithEnds.map(rstripEol);
|
|
385
|
+
const tableIdx = findTableHeaderIndex(plainLines, table);
|
|
386
|
+
if (tableIdx === null)
|
|
387
|
+
throw new Error(`[${table}] header not found`);
|
|
388
|
+
const keyIdx = findTableKeyIndex(plainLines, tableIdx, keyLiteral);
|
|
389
|
+
if (keyIdx === null)
|
|
390
|
+
throw new Error(`${keyLiteral} not found in [${table}]`);
|
|
391
|
+
if (plainLines[keyIdx].includes("{") && !plainLines[keyIdx].includes("}")) {
|
|
392
|
+
throw new Error(`${keyLiteral} spans multiple lines in [${table}]`);
|
|
393
|
+
}
|
|
394
|
+
const terminator = lineTerminator(linesWithEnds[keyIdx]);
|
|
395
|
+
return [...linesWithEnds.slice(0, keyIdx), line + terminator, ...linesWithEnds.slice(keyIdx + 1)].join("");
|
|
396
|
+
}
|
|
397
|
+
function appendTable(text, table, lines) {
|
|
398
|
+
let prefix = text;
|
|
399
|
+
if (!prefix.endsWith("\n"))
|
|
400
|
+
prefix += "\n";
|
|
401
|
+
if (!prefix.endsWith("\n\n"))
|
|
402
|
+
prefix += "\n";
|
|
403
|
+
return prefix + `[${table}]\n` + lines.join("\n") + "\n";
|
|
404
|
+
}
|
|
405
|
+
function tomlLoadOrEmpty(text) {
|
|
406
|
+
return text.trim() ? parseToml(text) : {};
|
|
407
|
+
}
|
|
408
|
+
function ensureFeatureEnabled(text, key) {
|
|
409
|
+
const parsed = tomlLoadOrEmpty(text);
|
|
410
|
+
const features = parsed.features;
|
|
411
|
+
if (features && typeof features === "object" && !Array.isArray(features) && features[key] === true) {
|
|
412
|
+
return text;
|
|
413
|
+
}
|
|
414
|
+
const lines = splitKeepEnds(text).map(rstripEol);
|
|
415
|
+
const tableIdx = findTableHeaderIndex(lines, "features");
|
|
416
|
+
if (tableIdx === null) {
|
|
417
|
+
if (features && typeof features === "object" && !Array.isArray(features)) {
|
|
418
|
+
if (lines.some((line) => /^\s*features\s*=/.test(line))) {
|
|
419
|
+
throw new Error("[features] uses an unsupported inline or dotted-table form");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return appendTable(text, "features", [`${key} = true`]);
|
|
423
|
+
}
|
|
424
|
+
const keyIdx = findTableKeyIndex(lines, tableIdx, key);
|
|
425
|
+
if (keyIdx === null)
|
|
426
|
+
return insertTableKeyLine(text, "features", `${key} = true`);
|
|
427
|
+
return replaceTableKeyLine(text, "features", key, `${key} = true`);
|
|
428
|
+
}
|
|
429
|
+
function ensureFeaturesHooksEnabled(text) {
|
|
430
|
+
return ensureFeatureEnabled(text, "hooks");
|
|
431
|
+
}
|
|
432
|
+
function ensureFeaturesPluginHooksEnabled(text) {
|
|
433
|
+
return ensureFeatureEnabled(ensureFeaturesHooksEnabled(text), "plugin_hooks");
|
|
434
|
+
}
|
|
435
|
+
function removeTableKeyLine(text, table, key) {
|
|
436
|
+
const linesWithEnds = splitKeepEnds(text);
|
|
437
|
+
const plainLines = linesWithEnds.map(rstripEol);
|
|
438
|
+
const tableIdx = findTableHeaderIndex(plainLines, table);
|
|
439
|
+
if (tableIdx === null)
|
|
440
|
+
return text;
|
|
441
|
+
const keyLiteral = tomlBasicString(key);
|
|
442
|
+
let keyIdx = findTableKeyIndex(plainLines, tableIdx, keyLiteral);
|
|
443
|
+
if (keyIdx === null)
|
|
444
|
+
keyIdx = findTableKeyIndex(plainLines, tableIdx, key);
|
|
445
|
+
if (keyIdx === null)
|
|
446
|
+
return text;
|
|
447
|
+
return [...linesWithEnds.slice(0, keyIdx), ...linesWithEnds.slice(keyIdx + 1)].join("");
|
|
448
|
+
}
|
|
449
|
+
function codexMultiAgentThreadLimit(parsed) {
|
|
450
|
+
const agents = parsed.agents;
|
|
451
|
+
if (agents && typeof agents === "object" && !Array.isArray(agents) && "max_threads" in agents) {
|
|
452
|
+
const n = Number(agents.max_threads);
|
|
453
|
+
if (Number.isInteger(n))
|
|
454
|
+
return n;
|
|
455
|
+
}
|
|
456
|
+
const features = parsed.features;
|
|
457
|
+
if (features && typeof features === "object" && !Array.isArray(features)) {
|
|
458
|
+
const multi = features.multi_agent_v2;
|
|
459
|
+
if (multi && typeof multi === "object" && "max_concurrent_threads_per_session" in multi) {
|
|
460
|
+
const n = Number(multi.max_concurrent_threads_per_session);
|
|
461
|
+
if (Number.isInteger(n))
|
|
462
|
+
return n;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return 6;
|
|
466
|
+
}
|
|
467
|
+
function ensureCodexMultiAgentV2(text, maxThreadsVal) {
|
|
468
|
+
const parsed = tomlLoadOrEmpty(text);
|
|
469
|
+
const features = parsed.features;
|
|
470
|
+
let multi = {};
|
|
471
|
+
if (features && typeof features === "object" && !Array.isArray(features)) {
|
|
472
|
+
const m = features.multi_agent_v2;
|
|
473
|
+
if (m && typeof m === "object" && !Array.isArray(m))
|
|
474
|
+
multi = m;
|
|
475
|
+
}
|
|
476
|
+
if (multi.max_concurrent_threads_per_session === maxThreadsVal)
|
|
477
|
+
return text;
|
|
478
|
+
const lines = splitKeepEnds(text).map(rstripEol);
|
|
479
|
+
const tableIdx = findTableHeaderIndex(lines, "features.multi_agent_v2");
|
|
480
|
+
const key = "max_concurrent_threads_per_session";
|
|
481
|
+
const line = `${key} = ${maxThreadsVal}`;
|
|
482
|
+
if (tableIdx === null)
|
|
483
|
+
return appendTable(text, "features.multi_agent_v2", [line]);
|
|
484
|
+
const keyIdx = findTableKeyIndex(lines, tableIdx, key);
|
|
485
|
+
if (keyIdx === null)
|
|
486
|
+
return insertTableKeyLine(text, "features.multi_agent_v2", line);
|
|
487
|
+
return replaceTableKeyLine(text, "features.multi_agent_v2", key, line);
|
|
488
|
+
}
|
|
489
|
+
export function ensureCodexAgentLimits(text) {
|
|
490
|
+
let parsed = tomlLoadOrEmpty(text);
|
|
491
|
+
const maxThreadsVal = codexMultiAgentThreadLimit(parsed);
|
|
492
|
+
text = removeTableKeyLine(text, "agents", "max_threads");
|
|
493
|
+
parsed = tomlLoadOrEmpty(text);
|
|
494
|
+
const agents = parsed.agents;
|
|
495
|
+
const agentsMatches = agents && typeof agents === "object" && !Array.isArray(agents) &&
|
|
496
|
+
Object.entries(DEFAULT_AGENT_LIMITS).every(([k, v]) => agents[k] === v);
|
|
497
|
+
if (!agentsMatches) {
|
|
498
|
+
const lines = splitKeepEnds(text).map(rstripEol);
|
|
499
|
+
const tableIdx = findTableHeaderIndex(lines, "agents");
|
|
500
|
+
if (tableIdx === null) {
|
|
501
|
+
if (agents && typeof agents === "object" && !Array.isArray(agents) && Object.keys(agents).length > 0) {
|
|
502
|
+
throw new Error("[agents] uses an unsupported inline or child-table-only form");
|
|
503
|
+
}
|
|
504
|
+
text = appendTable(text, "agents", Object.entries(DEFAULT_AGENT_LIMITS).map(([k, v]) => `${k} = ${v}`));
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
for (const [key, value] of Object.entries(DEFAULT_AGENT_LIMITS)) {
|
|
508
|
+
const line = `${key} = ${value}`;
|
|
509
|
+
const curLines = splitKeepEnds(text).map(rstripEol);
|
|
510
|
+
const curTableIdx = findTableHeaderIndex(curLines, "agents");
|
|
511
|
+
if (curTableIdx === null)
|
|
512
|
+
throw new Error("[agents] header disappeared during update");
|
|
513
|
+
if (findTableKeyIndex(curLines, curTableIdx, key) === null) {
|
|
514
|
+
text = insertTableKeyLine(text, "agents", line);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
text = replaceTableKeyLine(text, "agents", key, line);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return ensureCodexMultiAgentV2(text, maxThreadsVal);
|
|
523
|
+
}
|
|
524
|
+
function hookStateLine(key, trustedHash) {
|
|
525
|
+
return `${tomlBasicString(key)} = { trusted_hash = ${tomlBasicString(trustedHash)}, enabled = true }`;
|
|
526
|
+
}
|
|
527
|
+
function ensureCodexHookStateEntries(text, entries) {
|
|
528
|
+
const parsed = tomlLoadOrEmpty(text);
|
|
529
|
+
const hooks = parsed.hooks;
|
|
530
|
+
let state = {};
|
|
531
|
+
if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
|
|
532
|
+
const s = hooks.state;
|
|
533
|
+
if (s && typeof s === "object" && !Array.isArray(s))
|
|
534
|
+
state = s;
|
|
535
|
+
}
|
|
536
|
+
const allPresent = Object.entries(entries).every(([key, trustedHash]) => {
|
|
537
|
+
const e = state[key];
|
|
538
|
+
return e && typeof e === "object" && e.trusted_hash === trustedHash && e.enabled === true;
|
|
539
|
+
});
|
|
540
|
+
if (allPresent)
|
|
541
|
+
return text;
|
|
542
|
+
const lines = splitKeepEnds(text).map(rstripEol);
|
|
543
|
+
const tableIdx = findTableHeaderIndex(lines, "hooks.state");
|
|
544
|
+
if (tableIdx === null) {
|
|
545
|
+
if (Object.keys(state).length > 0) {
|
|
546
|
+
throw new Error("[hooks.state] uses an unsupported inline or dotted-table form");
|
|
547
|
+
}
|
|
548
|
+
return appendTable(text, "hooks.state", Object.entries(entries).map(([key, trustedHash]) => hookStateLine(key, trustedHash)));
|
|
549
|
+
}
|
|
550
|
+
for (const [key, trustedHash] of Object.entries(entries)) {
|
|
551
|
+
const keyLiteral = tomlBasicString(key);
|
|
552
|
+
const line = hookStateLine(key, trustedHash);
|
|
553
|
+
const curLines = splitKeepEnds(text).map(rstripEol);
|
|
554
|
+
const curTableIdx = findTableHeaderIndex(curLines, "hooks.state");
|
|
555
|
+
if (curTableIdx === null)
|
|
556
|
+
throw new Error("[hooks.state] header disappeared during update");
|
|
557
|
+
if (findTableKeyIndex(curLines, curTableIdx, keyLiteral) === null) {
|
|
558
|
+
text = insertTableKeyLine(text, "hooks.state", line);
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
text = replaceTableKeyLine(text, "hooks.state", keyLiteral, line);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return text;
|
|
565
|
+
}
|
|
566
|
+
export function ensureCodexHookTrust(text, hooksPath, command = CODEX_HOOK_COMMAND) {
|
|
567
|
+
return ensureCodexHookStateEntries(ensureFeaturesHooksEnabled(text), codexHookStateEntries(hooksPath, command));
|
|
568
|
+
}
|
|
569
|
+
export function codexCopiedHooksAreAgenteraOnly(text) {
|
|
570
|
+
let payload;
|
|
571
|
+
try {
|
|
572
|
+
payload = JSON.parse(text);
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
578
|
+
return false;
|
|
579
|
+
const p = payload;
|
|
580
|
+
if (typeof p.description !== "string" || !p.description.includes("agentera v2 Codex hooks"))
|
|
581
|
+
return false;
|
|
582
|
+
const hooks = p.hooks;
|
|
583
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks))
|
|
584
|
+
return false;
|
|
585
|
+
const keys = Object.keys(hooks);
|
|
586
|
+
if (keys.length !== 2 || !keys.includes("PreToolUse") || !keys.includes("PostToolUse"))
|
|
587
|
+
return false;
|
|
588
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
589
|
+
if (event !== "PreToolUse" && event !== "PostToolUse")
|
|
590
|
+
return false;
|
|
591
|
+
if (!Array.isArray(entries) || entries.length !== 1)
|
|
592
|
+
return false;
|
|
593
|
+
const entry = entries[0];
|
|
594
|
+
if (!entry || typeof entry !== "object" || entry.matcher !== CODEX_HOOK_MATCHER)
|
|
595
|
+
return false;
|
|
596
|
+
const handlers = entry.hooks;
|
|
597
|
+
if (!Array.isArray(handlers) || handlers.length !== 1)
|
|
598
|
+
return false;
|
|
599
|
+
const handler = handlers[0];
|
|
600
|
+
if (!handler || typeof handler !== "object")
|
|
601
|
+
return false;
|
|
602
|
+
const command = handler.command;
|
|
603
|
+
if (handler.type !== "command" || typeof command !== "string")
|
|
604
|
+
return false;
|
|
605
|
+
if (!command.includes("hooks/validate_artifact.py"))
|
|
606
|
+
return false;
|
|
607
|
+
if (handler.timeout !== CODEX_HOOK_TIMEOUT)
|
|
608
|
+
return false;
|
|
609
|
+
if (handler.statusMessage !== CODEX_HOOK_STATUS_MESSAGE)
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
export function retireCodexCopiedHookTrust(text, hooksPath) {
|
|
615
|
+
if (pathExists(hooksPath)) {
|
|
616
|
+
let hooksText;
|
|
617
|
+
try {
|
|
618
|
+
hooksText = fs.readFileSync(hooksPath, "utf8");
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
return text;
|
|
622
|
+
}
|
|
623
|
+
if (!codexCopiedHooksAreAgenteraOnly(hooksText))
|
|
624
|
+
return text;
|
|
625
|
+
}
|
|
626
|
+
const resolved = resolvePath(hooksPath);
|
|
627
|
+
text = removeTableKeyLine(text, "hooks.state", `${resolved}:pre_tool_use:0:0`);
|
|
628
|
+
text = removeTableKeyLine(text, "hooks.state", `${resolved}:post_tool_use:0:0`);
|
|
629
|
+
return text;
|
|
630
|
+
}
|
|
631
|
+
export function ensureCodexPluginHookTrust(text, command = CODEX_PLUGIN_HOOK_COMMAND, hooksPath = null) {
|
|
632
|
+
text = ensureCodexHookStateEntries(ensureFeaturesPluginHooksEnabled(text), codexPluginHookStateEntries(command));
|
|
633
|
+
const target = hooksPath ?? path.join(os.homedir(), ".codex", "hooks.json");
|
|
634
|
+
return retireCodexCopiedHookTrust(text, target);
|
|
635
|
+
}
|
|
636
|
+
// ===========================================================================
|
|
637
|
+
// Slice 3: plan/apply orchestration, agent descriptors, CLI
|
|
638
|
+
// ===========================================================================
|
|
639
|
+
export const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".codex", "config.toml");
|
|
640
|
+
function unifiedDiffText(before, after) {
|
|
641
|
+
return unifiedDiff(splitLinesKeepEnds(before), splitLinesKeepEnds(after), "config.toml (current)", "config.toml (proposed)", "", "", 3).join("");
|
|
642
|
+
}
|
|
643
|
+
function conflictDiffText(currentTable, mergedTable) {
|
|
644
|
+
const currentInline = emitSetInlineTable(currentTable);
|
|
645
|
+
const mergedInline = emitSetInlineTable(mergedTable);
|
|
646
|
+
return `current: set = ${currentInline}\nproposed: set = ${mergedInline}\n`;
|
|
647
|
+
}
|
|
648
|
+
function withCodexHookTrust(outcome, beforeText, hooksPath, hookCommand = CODEX_HOOK_COMMAND, pluginHooks = false) {
|
|
649
|
+
if (outcome.action === "conflict")
|
|
650
|
+
return outcome;
|
|
651
|
+
const before = beforeText || "";
|
|
652
|
+
let newText;
|
|
653
|
+
try {
|
|
654
|
+
newText = ensureCodexAgentLimits(outcome.newText);
|
|
655
|
+
}
|
|
656
|
+
catch (exc) {
|
|
657
|
+
return {
|
|
658
|
+
action: "conflict",
|
|
659
|
+
newText: "",
|
|
660
|
+
message: `cannot safely update Codex agent dispatch settings: ${exc.message}`,
|
|
661
|
+
diff: "",
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (newText !== outcome.newText) {
|
|
665
|
+
const action = outcome.action !== "noop" ? outcome.action : "insert";
|
|
666
|
+
const message = outcome.action === "noop"
|
|
667
|
+
? "would configure Codex agent dispatch limits"
|
|
668
|
+
: `${outcome.message}; would configure Codex agent dispatch limits`;
|
|
669
|
+
outcome = { action, newText, message, diff: unifiedDiffText(before, newText) };
|
|
670
|
+
}
|
|
671
|
+
if (hooksPath === null && !pluginHooks)
|
|
672
|
+
return outcome;
|
|
673
|
+
try {
|
|
674
|
+
if (pluginHooks) {
|
|
675
|
+
newText = ensureCodexPluginHookTrust(outcome.newText, CODEX_PLUGIN_HOOK_COMMAND, hooksPath);
|
|
676
|
+
}
|
|
677
|
+
else if (hooksPath !== null) {
|
|
678
|
+
newText = ensureCodexHookTrust(outcome.newText, hooksPath, hookCommand);
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
newText = outcome.newText;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
catch (exc) {
|
|
685
|
+
return {
|
|
686
|
+
action: "conflict",
|
|
687
|
+
newText: "",
|
|
688
|
+
message: `cannot safely update Codex hook trust state: ${exc.message}`,
|
|
689
|
+
diff: "",
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
if (newText === outcome.newText)
|
|
693
|
+
return outcome;
|
|
694
|
+
const action = outcome.action !== "noop" ? outcome.action : "insert";
|
|
695
|
+
let message;
|
|
696
|
+
if (outcome.action === "noop") {
|
|
697
|
+
message = pluginHooks
|
|
698
|
+
? "would trust Codex plugin apply_patch hooks in config.toml"
|
|
699
|
+
: "would trust Codex apply_patch hooks in config.toml";
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
const hookLabel = pluginHooks ? "Codex plugin apply_patch hooks" : "Codex apply_patch hooks";
|
|
703
|
+
message = `${outcome.message}; would trust ${hookLabel}`;
|
|
704
|
+
}
|
|
705
|
+
return { action, newText, message, diff: unifiedDiffText(before, newText) };
|
|
706
|
+
}
|
|
707
|
+
export function planChange(currentText, installRoot, opts) {
|
|
708
|
+
const force = opts.force;
|
|
709
|
+
const hooksPath = opts.hooksPath ?? null;
|
|
710
|
+
const hookCommand = opts.hookCommand ?? CODEX_HOOK_COMMAND;
|
|
711
|
+
const pluginHooks = opts.pluginHooks ?? false;
|
|
712
|
+
const desiredPath = installRoot;
|
|
713
|
+
// Branch 1: file absent (or empty) -> write fresh config.
|
|
714
|
+
if (currentText === null || !currentText.trim()) {
|
|
715
|
+
const newText = renderFreshConfig(installRoot);
|
|
716
|
+
return withCodexHookTrust({
|
|
717
|
+
action: "fresh",
|
|
718
|
+
newText,
|
|
719
|
+
message: `would write fresh config with ${SECTION_NAME}.set.${MANAGED_KEY} = ${desiredPath}`,
|
|
720
|
+
diff: unifiedDiffText("", newText),
|
|
721
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
722
|
+
}
|
|
723
|
+
const state = classifyToml(currentText);
|
|
724
|
+
// Branch 2: section absent -> append a fresh section at EOF.
|
|
725
|
+
if (!state.sectionPresent) {
|
|
726
|
+
let prefix = currentText;
|
|
727
|
+
if (!prefix.endsWith("\n"))
|
|
728
|
+
prefix += "\n";
|
|
729
|
+
if (!prefix.endsWith("\n\n"))
|
|
730
|
+
prefix += "\n";
|
|
731
|
+
const newText = prefix + renderFreshConfig(installRoot);
|
|
732
|
+
return withCodexHookTrust({
|
|
733
|
+
action: "fresh",
|
|
734
|
+
newText,
|
|
735
|
+
message: `would append [${SECTION_NAME}] section with ${MANAGED_KEY} = ${desiredPath}`,
|
|
736
|
+
diff: unifiedDiffText(currentText, newText),
|
|
737
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
738
|
+
}
|
|
739
|
+
// Branch 3a: section present, no set key -> insert set line.
|
|
740
|
+
if (!state.setPresent) {
|
|
741
|
+
const newText = insertSetLine(currentText, installRoot);
|
|
742
|
+
return withCodexHookTrust({
|
|
743
|
+
action: "insert",
|
|
744
|
+
newText,
|
|
745
|
+
message: `would insert set = { ${MANAGED_KEY} = ${desiredPath} } into [${SECTION_NAME}]`,
|
|
746
|
+
diff: unifiedDiffText(currentText, newText),
|
|
747
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
748
|
+
}
|
|
749
|
+
// Branch 3b: AGENTERA_HOME already correct -> noop.
|
|
750
|
+
const currentValue = state.setTable[MANAGED_KEY];
|
|
751
|
+
if (currentValue === desiredPath) {
|
|
752
|
+
return withCodexHookTrust({
|
|
753
|
+
action: "noop",
|
|
754
|
+
newText: currentText,
|
|
755
|
+
message: `${MANAGED_KEY} already set to ${desiredPath}; nothing to do`,
|
|
756
|
+
diff: "",
|
|
757
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
758
|
+
}
|
|
759
|
+
// Branch 3c: wrong value or sibling keys.
|
|
760
|
+
const siblings = {};
|
|
761
|
+
for (const [k, v] of Object.entries(state.setTable)) {
|
|
762
|
+
if (k !== MANAGED_KEY)
|
|
763
|
+
siblings[k] = v;
|
|
764
|
+
}
|
|
765
|
+
const merged = { ...state.setTable, [MANAGED_KEY]: desiredPath };
|
|
766
|
+
if (MANAGED_KEY in state.setTable && Object.keys(siblings).length === 0) {
|
|
767
|
+
let newText;
|
|
768
|
+
try {
|
|
769
|
+
newText = rewriteSetLine(currentText, merged);
|
|
770
|
+
}
|
|
771
|
+
catch (exc) {
|
|
772
|
+
return {
|
|
773
|
+
action: "conflict",
|
|
774
|
+
newText: "",
|
|
775
|
+
message: `${MANAGED_KEY} present but cannot be safely updated: ${exc.message}. Edit ~/.codex/config.toml manually.`,
|
|
776
|
+
diff: "",
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return withCodexHookTrust({
|
|
780
|
+
action: "insert",
|
|
781
|
+
newText,
|
|
782
|
+
message: `would update ${MANAGED_KEY} from ${state.setTable[MANAGED_KEY]} to ${desiredPath}`,
|
|
783
|
+
diff: unifiedDiffText(currentText, newText),
|
|
784
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
785
|
+
}
|
|
786
|
+
if (!force) {
|
|
787
|
+
return {
|
|
788
|
+
action: "conflict",
|
|
789
|
+
newText: "",
|
|
790
|
+
message: `[${SECTION_NAME}].set has sibling keys (${Object.keys(siblings).sort().join(", ")}). Re-run with --force to merge ${MANAGED_KEY} = ${desiredPath} alongside them.`,
|
|
791
|
+
diff: conflictDiffText(state.setTable, merged),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
let newText;
|
|
795
|
+
try {
|
|
796
|
+
newText = rewriteSetLine(currentText, merged);
|
|
797
|
+
}
|
|
798
|
+
catch (exc) {
|
|
799
|
+
return {
|
|
800
|
+
action: "conflict",
|
|
801
|
+
newText: "",
|
|
802
|
+
message: `--force requested but cannot safely merge: ${exc.message}. Edit ~/.codex/config.toml manually.`,
|
|
803
|
+
diff: "",
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return withCodexHookTrust({
|
|
807
|
+
action: "force-merge",
|
|
808
|
+
newText,
|
|
809
|
+
message: `would merge ${MANAGED_KEY} = ${desiredPath} into existing set (siblings preserved: ${Object.keys(siblings).sort().join(", ")})`,
|
|
810
|
+
diff: unifiedDiffText(currentText, newText),
|
|
811
|
+
}, currentText, hooksPath, hookCommand, pluginHooks);
|
|
812
|
+
}
|
|
813
|
+
export function codexAgentSourceDir(installRoot) {
|
|
814
|
+
const candidates = [
|
|
815
|
+
path.join(installRoot, "app", "skills", "agentera", "agents"),
|
|
816
|
+
path.join(installRoot, "skills", "agentera", "agents"),
|
|
817
|
+
];
|
|
818
|
+
for (const candidate of candidates) {
|
|
819
|
+
try {
|
|
820
|
+
if (fs.statSync(candidate).isDirectory())
|
|
821
|
+
return candidate;
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
/* not a dir */
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return candidates[0];
|
|
828
|
+
}
|
|
829
|
+
export function defaultAgentsDirForConfig(configPath) {
|
|
830
|
+
const expanded = expanduser(configPath);
|
|
831
|
+
if (path.basename(expanded) === "config.toml" && path.basename(path.dirname(expanded)) === ".codex") {
|
|
832
|
+
return path.join(path.dirname(expanded), "agents");
|
|
833
|
+
}
|
|
834
|
+
throw new Error("Codex agent descriptors can be inferred only for documented config layouts: " +
|
|
835
|
+
"~/.codex/config.toml or <project>/.codex/config.toml. " +
|
|
836
|
+
"Pass --agents-dir for nonstandard --config-file paths.");
|
|
837
|
+
}
|
|
838
|
+
function codexDescriptorManaged(text) {
|
|
839
|
+
const lines = text.split(/\r\n|\r|\n/).slice(0, 5);
|
|
840
|
+
return lines.some((line) => line.trim() === "# agentera_managed: true");
|
|
841
|
+
}
|
|
842
|
+
export function planAgentDescriptorChanges(installRoot, agentsDir, opts) {
|
|
843
|
+
const sourceDir = codexAgentSourceDir(installRoot);
|
|
844
|
+
const changes = [];
|
|
845
|
+
for (const name of CAPABILITY_AGENT_NAMES) {
|
|
846
|
+
const source = path.join(sourceDir, `${name}.toml`);
|
|
847
|
+
const target = path.join(agentsDir, `${name}.toml`);
|
|
848
|
+
let sourceText;
|
|
849
|
+
try {
|
|
850
|
+
sourceText = fs.readFileSync(source, "utf8");
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
changes.push({ action: "blocked", name, source, target, message: "source descriptor is missing", content: "" });
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (!pathExists(target)) {
|
|
857
|
+
changes.push({ action: "pending", name, source, target, message: "would install Codex agent descriptor", content: sourceText });
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (!isFile(target)) {
|
|
861
|
+
changes.push({ action: "blocked", name, source, target, message: "target exists but is not a regular file", content: sourceText });
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
let targetText;
|
|
865
|
+
try {
|
|
866
|
+
targetText = fs.readFileSync(target, "utf8");
|
|
867
|
+
}
|
|
868
|
+
catch (exc) {
|
|
869
|
+
changes.push({ action: "blocked", name, source, target, message: `cannot read target descriptor: ${exc.message}`, content: sourceText });
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (targetText === sourceText) {
|
|
873
|
+
changes.push({ action: "noop", name, source, target, message: "Codex agent descriptor is current", content: sourceText });
|
|
874
|
+
}
|
|
875
|
+
else if (opts.force || codexDescriptorManaged(targetText)) {
|
|
876
|
+
changes.push({ action: "pending", name, source, target, message: "would refresh Codex agent descriptor", content: sourceText });
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
changes.push({
|
|
880
|
+
action: "blocked",
|
|
881
|
+
name,
|
|
882
|
+
source,
|
|
883
|
+
target,
|
|
884
|
+
message: "target exists without Agentera ownership proof; treating it as user-owned",
|
|
885
|
+
content: sourceText,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return changes;
|
|
890
|
+
}
|
|
891
|
+
export function writeAgentDescriptorChanges(changes) {
|
|
892
|
+
for (const change of changes) {
|
|
893
|
+
if (change.action !== "pending")
|
|
894
|
+
continue;
|
|
895
|
+
fs.mkdirSync(path.dirname(change.target), { recursive: true });
|
|
896
|
+
fs.writeFileSync(change.target, change.content, "utf8");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
function readTextOrNull(p) {
|
|
900
|
+
if (!pathExists(p))
|
|
901
|
+
return null;
|
|
902
|
+
return fs.readFileSync(p, "utf8");
|
|
903
|
+
}
|
|
904
|
+
export function codexMain(argv = [], io = {}) {
|
|
905
|
+
const writeOut = io.out ?? ((text) => process.stdout.write(text));
|
|
906
|
+
const writeErr = io.err ?? ((text) => process.stderr.write(text));
|
|
907
|
+
const out = (line) => writeOut(line + "\n");
|
|
908
|
+
const err = (line) => writeErr(line + "\n");
|
|
909
|
+
const env = io.env ?? process.env;
|
|
910
|
+
const args = {
|
|
911
|
+
installRoot: null,
|
|
912
|
+
configFile: DEFAULT_CONFIG_PATH,
|
|
913
|
+
agentsDir: null,
|
|
914
|
+
dryRun: false,
|
|
915
|
+
force: false,
|
|
916
|
+
enableAgents: false,
|
|
917
|
+
};
|
|
918
|
+
const valueFlag = (a, name) => {
|
|
919
|
+
if (a === name)
|
|
920
|
+
return "__NEXT__";
|
|
921
|
+
if (a.startsWith(name + "="))
|
|
922
|
+
return a.slice(name.length + 1);
|
|
923
|
+
return null;
|
|
924
|
+
};
|
|
925
|
+
for (let i = 0; i < argv.length; i++) {
|
|
926
|
+
const a = argv[i];
|
|
927
|
+
let v;
|
|
928
|
+
if ((v = valueFlag(a, "--install-root")) !== null) {
|
|
929
|
+
args.installRoot = v === "__NEXT__" ? argv[++i] : v;
|
|
930
|
+
}
|
|
931
|
+
else if ((v = valueFlag(a, "--config-file")) !== null) {
|
|
932
|
+
args.configFile = v === "__NEXT__" ? argv[++i] : v;
|
|
933
|
+
}
|
|
934
|
+
else if ((v = valueFlag(a, "--agents-dir")) !== null) {
|
|
935
|
+
args.agentsDir = v === "__NEXT__" ? argv[++i] : v;
|
|
936
|
+
}
|
|
937
|
+
else if (a === "--dry-run") {
|
|
938
|
+
args.dryRun = true;
|
|
939
|
+
}
|
|
940
|
+
else if (a === "--force") {
|
|
941
|
+
args.force = true;
|
|
942
|
+
}
|
|
943
|
+
else if (a === "--enable-agents") {
|
|
944
|
+
args.enableAgents = true;
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
err(`setup_codex: error: unrecognized arguments: ${a}`);
|
|
948
|
+
return 2;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// Step 1: resolve and verify the Agentera directory.
|
|
952
|
+
let installRoot;
|
|
953
|
+
try {
|
|
954
|
+
installRoot = resolveInstallRoot(args.installRoot, env);
|
|
955
|
+
}
|
|
956
|
+
catch (errx) {
|
|
957
|
+
if (errx instanceof InstallRootError) {
|
|
958
|
+
err(errx.message);
|
|
959
|
+
return 2;
|
|
960
|
+
}
|
|
961
|
+
throw errx;
|
|
962
|
+
}
|
|
963
|
+
// Step 2: read current config (None if absent).
|
|
964
|
+
const configPath = args.configFile;
|
|
965
|
+
let currentText;
|
|
966
|
+
try {
|
|
967
|
+
currentText = readTextOrNull(configPath);
|
|
968
|
+
}
|
|
969
|
+
catch (errx) {
|
|
970
|
+
err(`error reading ${configPath}: ${errx.message}`);
|
|
971
|
+
return 2;
|
|
972
|
+
}
|
|
973
|
+
// Step 3: parse-check existing content.
|
|
974
|
+
if (currentText !== null && currentText.trim()) {
|
|
975
|
+
try {
|
|
976
|
+
parseToml(currentText);
|
|
977
|
+
}
|
|
978
|
+
catch (errx) {
|
|
979
|
+
err(`error: ${configPath} is not valid TOML (${errx.message}). ` +
|
|
980
|
+
"Repair it manually before running this helper.");
|
|
981
|
+
return 2;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// Step 4: plan the AGENTERA_HOME change and runtime-native descriptors.
|
|
985
|
+
const outcome = planChange(currentText, installRoot, { force: args.force });
|
|
986
|
+
let agentsDir;
|
|
987
|
+
try {
|
|
988
|
+
agentsDir = args.agentsDir ?? defaultAgentsDirForConfig(configPath);
|
|
989
|
+
}
|
|
990
|
+
catch (errx) {
|
|
991
|
+
err(`error: ${errx.message}`);
|
|
992
|
+
return 2;
|
|
993
|
+
}
|
|
994
|
+
const descriptorChanges = planAgentDescriptorChanges(installRoot, agentsDir, { force: args.force });
|
|
995
|
+
const pendingDescriptors = descriptorChanges.filter((c) => c.action === "pending");
|
|
996
|
+
const blockedDescriptors = descriptorChanges.filter((c) => c.action === "blocked");
|
|
997
|
+
if (args.enableAgents) {
|
|
998
|
+
err("--enable-agents is deprecated in Agentera v2; no [agents.*] " +
|
|
999
|
+
"blocks will be written; runtime-native descriptor files are managed separately.");
|
|
1000
|
+
}
|
|
1001
|
+
// Step 5: dispatch on the outcome.
|
|
1002
|
+
if (outcome.action === "conflict") {
|
|
1003
|
+
err(outcome.message);
|
|
1004
|
+
if (outcome.diff)
|
|
1005
|
+
err(outcome.diff);
|
|
1006
|
+
return 2;
|
|
1007
|
+
}
|
|
1008
|
+
if (blockedDescriptors.length > 0) {
|
|
1009
|
+
for (const change of blockedDescriptors) {
|
|
1010
|
+
err(`error: ${change.target}: ${change.message}`);
|
|
1011
|
+
}
|
|
1012
|
+
return 2;
|
|
1013
|
+
}
|
|
1014
|
+
if (outcome.action === "noop" && pendingDescriptors.length === 0) {
|
|
1015
|
+
out(outcome.message);
|
|
1016
|
+
return 0;
|
|
1017
|
+
}
|
|
1018
|
+
if (args.dryRun) {
|
|
1019
|
+
out(outcome.message);
|
|
1020
|
+
if (outcome.diff) {
|
|
1021
|
+
writeOut(outcome.diff);
|
|
1022
|
+
if (!outcome.diff.endsWith("\n"))
|
|
1023
|
+
out("");
|
|
1024
|
+
}
|
|
1025
|
+
for (const change of pendingDescriptors) {
|
|
1026
|
+
out(`${change.message}: ${change.target}`);
|
|
1027
|
+
}
|
|
1028
|
+
return 1;
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
if (outcome.action !== "noop") {
|
|
1032
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1033
|
+
fs.writeFileSync(configPath, outcome.newText, "utf8");
|
|
1034
|
+
}
|
|
1035
|
+
writeAgentDescriptorChanges(pendingDescriptors);
|
|
1036
|
+
}
|
|
1037
|
+
catch (errx) {
|
|
1038
|
+
err(`error writing Codex setup targets: ${errx.message}`);
|
|
1039
|
+
return 2;
|
|
1040
|
+
}
|
|
1041
|
+
if (outcome.action !== "noop") {
|
|
1042
|
+
out(`wrote ${configPath}: ${outcome.message.replaceAll("would ", "")}`);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
out(outcome.message);
|
|
1046
|
+
}
|
|
1047
|
+
for (const change of pendingDescriptors) {
|
|
1048
|
+
out(`wrote ${change.target}: ${change.message.replaceAll("would ", "")}`);
|
|
1049
|
+
}
|
|
1050
|
+
return 0;
|
|
1051
|
+
}
|
|
1052
|
+
//# sourceMappingURL=codex.js.map
|