agencyx-cli 0.0.0-stage → 0.1.0-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/adapters/agent.js +73 -0
  2. package/dist/adapters/ambient.js +35 -0
  3. package/dist/adapters/instructions.js +102 -0
  4. package/dist/adapters/mcp.js +237 -0
  5. package/dist/adapters/permissions.js +42 -0
  6. package/dist/adapters/plugins.js +484 -0
  7. package/dist/agency/invocation.js +97 -0
  8. package/dist/agency/prepared-input.js +124 -0
  9. package/dist/bridge/execution.js +119 -0
  10. package/dist/bridge/main.js +26 -0
  11. package/dist/bridge/plan.js +268 -0
  12. package/dist/bridge/version.js +2 -0
  13. package/dist/cli-options.js +96 -0
  14. package/dist/cli.js +41 -0
  15. package/dist/codex/agents.js +72 -0
  16. package/dist/codex/app-server.js +116 -0
  17. package/dist/codex/inspection.js +217 -0
  18. package/dist/codex/mcp-readiness.js +45 -0
  19. package/dist/codex/native-plugins.js +109 -0
  20. package/dist/codex/options.js +170 -0
  21. package/dist/codex/plugin-hooks.js +93 -0
  22. package/dist/codex/rpc.js +170 -0
  23. package/dist/codex/session-proxy.js +258 -0
  24. package/dist/codex/skill-policy.js +69 -0
  25. package/dist/codex/skill-views.js +104 -0
  26. package/dist/codex/toml.js +46 -0
  27. package/dist/commands/codex.js +118 -0
  28. package/dist/commands/doctor.js +58 -0
  29. package/dist/commands/passthrough.js +14 -0
  30. package/dist/diagnostics/plan.js +87 -0
  31. package/dist/diagnostics.js +39 -0
  32. package/dist/model.js +2 -0
  33. package/dist/runtime/ipc.js +316 -0
  34. package/dist/runtime/process.js +286 -0
  35. package/dist/runtime/resolve-command.js +38 -0
  36. package/dist/runtime/session.js +107 -0
  37. package/dist/validation.js +31 -0
  38. package/package.json +41 -4
  39. package/README.md +0 -3
@@ -0,0 +1,69 @@
1
+ import path from "node:path";
2
+ import { fail } from "../diagnostics.js";
3
+ import { isRecord } from "../validation.js";
4
+ function restrictedSkills(views) {
5
+ return views.flatMap((view) => view.skills.filter((skill) => skill.userInvocable === false));
6
+ }
7
+ function normalized(file) {
8
+ const resolved = path.resolve(file);
9
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
10
+ }
11
+ function explicitMention(text, name) {
12
+ const literal = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
+ // Match native $skill syntax, preserving escaped dollars and longer names.
14
+ return new RegExp(String.raw `(?<![\\\p{L}\p{N}_$])\$${literal}(?![\p{L}\p{N}_:-])`, "u").test(text);
15
+ }
16
+ export function assertUserSkillInvocation(request, views) {
17
+ if (request.method !== "turn/start" && request.method !== "turn/steer")
18
+ return;
19
+ const restricted = restrictedSkills(views);
20
+ if (!restricted.length)
21
+ return;
22
+ if (!isRecord(request.params) || !Array.isArray(request.params.input))
23
+ fail("CODEX_PROTOCOL_INVALID", "codex", "The frontend sent invalid turn input.", {}, 6);
24
+ for (const input of request.params.input) {
25
+ if (!isRecord(input))
26
+ continue;
27
+ for (const skill of restricted) {
28
+ const names = [
29
+ skill.name,
30
+ ...(skill.sourceName ? [skill.sourceName] : []),
31
+ ];
32
+ const paths = [
33
+ skill.path,
34
+ ...(skill.sourcePath ? [skill.sourcePath] : []),
35
+ ];
36
+ const selected = input.type === "skill" &&
37
+ ((typeof input.name === "string" && names.includes(input.name)) ||
38
+ (typeof input.path === "string" &&
39
+ paths.some((file) => normalized(file) === normalized(input.path))));
40
+ const mentioned = input.type === "text" &&
41
+ typeof input.text === "string" &&
42
+ names.some((name) => explicitMention(input.text, name));
43
+ if (selected || mentioned)
44
+ fail("SKILL_USER_INVOCATION_DISABLED", "compatibility", "Agency marks this skill as user-invocable: false. It remains registered for native implicit use and cannot be invoked explicitly by the frontend.", { component: skill.name, field: "user-invocable" });
45
+ }
46
+ }
47
+ }
48
+ export function filterUserSkillCatalogue(value, views) {
49
+ const hidden = new Set(restrictedSkills(views).map((skill) => skill.name));
50
+ if (!hidden.size ||
51
+ !isRecord(value) ||
52
+ !isRecord(value.result) ||
53
+ !Array.isArray(value.result.data))
54
+ return value;
55
+ let changed = false;
56
+ const data = value.result.data.map((entry) => {
57
+ if (!isRecord(entry) || !Array.isArray(entry.skills))
58
+ return entry;
59
+ const skills = entry.skills.filter((skill) => !(isRecord(skill) &&
60
+ typeof skill.name === "string" &&
61
+ hidden.has(skill.name)));
62
+ if (skills.length === entry.skills.length)
63
+ return entry;
64
+ changed = true;
65
+ return { ...entry, skills };
66
+ });
67
+ return changed ? { ...value, result: { ...value.result, data } } : value;
68
+ }
69
+ //# sourceMappingURL=skill-policy.js.map
@@ -0,0 +1,104 @@
1
+ import { chmod, cp, mkdir, mkdtemp, readFile, realpath, writeFile, } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fail } from "../diagnostics.js";
4
+ import { packageFingerprint } from "../adapters/plugins.js";
5
+ import { markdownDocument } from "../adapters/instructions.js";
6
+ import { parseDocument } from "yaml";
7
+ export async function createSkillView(spec, sessionRoot, options = {}) {
8
+ if ((!spec.skills.length &&
9
+ !spec.agents?.length &&
10
+ !spec.hooks.some((hook) => hook.count > 0)) ||
11
+ (spec.hooks.some((hook) => hook.count > 0) && !options.allowHooks) ||
12
+ spec.primaryAgentPath !== undefined)
13
+ fail("PLUGIN_NOT_READY", "compatibility", "A cold mixed-component plugin needs the native whole-plugin preparation and trust path.", { component: spec.name });
14
+ await packageFingerprint(spec.root);
15
+ const root = await mkdtemp(path.join(sessionRoot, "plugin-view-"));
16
+ await chmod(root, 0o700);
17
+ const packageRoot = path.join(root, "package");
18
+ const fileKey = (file) => process.platform === "win32"
19
+ ? path.resolve(file).toLowerCase()
20
+ : path.resolve(file);
21
+ const mcpFiles = new Set(spec.mcpFiles.map(fileKey));
22
+ await cp(spec.root, packageRoot, {
23
+ recursive: true,
24
+ filter: (source) => path.basename(source) !== ".git" && !mcpFiles.has(fileKey(source)),
25
+ });
26
+ // Agency already resolved these MCPs, including authentication and tool
27
+ // filters. Suppress every original declaration in the session view so it
28
+ // cannot create a duplicate connection or copy literal credentials.
29
+ for (const original of spec.mcpFiles) {
30
+ const target = path.join(packageRoot, path.relative(spec.root, original));
31
+ await mkdir(path.dirname(target), { recursive: true });
32
+ await writeFile(target, '{"mcpServers":{}}\n', { mode: 0o600 });
33
+ }
34
+ const view = {
35
+ pluginName: spec.name,
36
+ root,
37
+ extraRoots: [],
38
+ skills: [],
39
+ };
40
+ for (const original of spec.skills) {
41
+ const target = path.join(packageRoot, path.relative(spec.root, original));
42
+ const text = await readFile(target, "utf8");
43
+ const parsed = markdownDocument(text, original);
44
+ const name = `${spec.name}:${String(parsed.metadata.name)}`;
45
+ // The native skill loader reads the retained plugin manifest and adds its
46
+ // namespace itself. Keep every source file, including frontmatter, intact.
47
+ if (spec.manualInvocationSkills.includes(original)) {
48
+ const metadataFile = path.join(path.dirname(target), "agents", "openai.yaml");
49
+ const existing = await readFile(metadataFile, "utf8").catch(() => "");
50
+ const document = parseDocument(existing || "{}\n");
51
+ document.setIn(["policy", "allow_implicit_invocation"], false);
52
+ await mkdir(path.dirname(metadataFile), { recursive: true, mode: 0o700 });
53
+ const eol = existing.includes("\r\n") ? "\r\n" : "\n";
54
+ await writeFile(metadataFile, document.toString().replace(/\r?\n/g, eol), { mode: 0o600 });
55
+ }
56
+ view.skills.push({
57
+ name,
58
+ path: target,
59
+ sourceName: String(parsed.metadata.name),
60
+ sourcePath: original,
61
+ userInvocable: parsed.metadata["user-invocable"] !== false,
62
+ });
63
+ view.extraRoots.push(path.dirname(path.dirname(target)));
64
+ }
65
+ view.extraRoots = [...new Set(view.extraRoots)];
66
+ return view;
67
+ }
68
+ async function canonical(file) {
69
+ const resolved = await realpath(file).catch(() => path.resolve(file));
70
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
71
+ }
72
+ export async function registerSkillViews(client, views) {
73
+ if (!views.length)
74
+ return;
75
+ await client.request("skills/extraRoots/set", {
76
+ extraRoots: [...new Set(views.flatMap((view) => view.extraRoots))],
77
+ });
78
+ }
79
+ export async function verifySkillViews(views, loaded) {
80
+ const expected = new Set();
81
+ for (const view of views) {
82
+ for (const skill of view.skills) {
83
+ const target = await canonical(skill.path);
84
+ expected.add(target);
85
+ const matches = loaded.filter((candidate) => candidate.name === skill.name && candidate.enabled);
86
+ if (matches.length !== 1 ||
87
+ (await canonical(matches[0].path)) !== target)
88
+ fail("SKILL_VIEW_NOT_READY", "compatibility", "A required skill is missing, disabled, or conflicts with another native skill.", { component: skill.name });
89
+ }
90
+ }
91
+ for (const skill of loaded) {
92
+ const target = await canonical(skill.path);
93
+ for (const view of views) {
94
+ const root = await canonical(view.root);
95
+ const relative = path.relative(root, target);
96
+ if (!relative.startsWith(`..${path.sep}`) &&
97
+ relative !== ".." &&
98
+ !path.isAbsolute(relative) &&
99
+ !expected.has(target))
100
+ fail("SKILL_SOURCE_SCOPE_UNVERIFIED", "compatibility", "Native discovery found an extra execution skill outside the validated package selection.", { component: view.pluginName });
101
+ }
102
+ }
103
+ }
104
+ //# sourceMappingURL=skill-views.js.map
@@ -0,0 +1,46 @@
1
+ import { parse } from "smol-toml";
2
+ import { fail } from "../diagnostics.js";
3
+ import { isRecord } from "../validation.js";
4
+ export function tomlValue(value) {
5
+ if (typeof value === "string") {
6
+ if (!value.isWellFormed())
7
+ fail("CONFIG_ENCODING_INVALID", "configuration", "Configuration strings must contain valid Unicode.", {}, 5);
8
+ return JSON.stringify(value).replace(/[\u007f]/g, "\\u007f");
9
+ }
10
+ if (typeof value === "boolean" ||
11
+ (typeof value === "number" && Number.isFinite(value)))
12
+ return String(value);
13
+ if (Array.isArray(value))
14
+ return `[${value.map(tomlValue).join(",")}]`;
15
+ if (isRecord(value))
16
+ return `{${Object.entries(value)
17
+ .map(([key, item]) => `${tomlValue(key)}=${tomlValue(item)}`)
18
+ .join(",")}}`;
19
+ fail("CONFIG_ENCODING_INVALID", "configuration", "Only strings, finite numbers, booleans, arrays, and tables can be encoded as overrides.", {}, 5);
20
+ }
21
+ export function configArguments(overrides) {
22
+ // Encode the whole top-level table so names containing dots never become paths.
23
+ return Object.entries(overrides).flatMap(([key, value]) => [
24
+ "-c",
25
+ `${key}=${tomlValue(value)}`,
26
+ ]);
27
+ }
28
+ export function parseOverride(value) {
29
+ try {
30
+ const equals = value.indexOf("=");
31
+ if (equals <= 0 || value.includes("\0"))
32
+ throw new Error("Invalid override.");
33
+ // Codex treats an unquoted value that is not valid TOML as a literal string.
34
+ parse(`${value.slice(0, equals)}=true`);
35
+ try {
36
+ return parse(value);
37
+ }
38
+ catch {
39
+ return parse(`${value.slice(0, equals)}=${tomlValue(value.slice(equals + 1))}`);
40
+ }
41
+ }
42
+ catch {
43
+ fail("CODEX_ARGUMENT_INVALID", "arguments", "Configuration overrides must use valid TOML key=value syntax.", { field: "--config" }, 2);
44
+ }
45
+ }
46
+ //# sourceMappingURL=toml.js.map
@@ -0,0 +1,118 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { resolveCommand } from "../runtime/resolve-command.js";
3
+ import { createLauncher, createSession } from "../runtime/session.js";
4
+ import { SessionServer } from "../runtime/ipc.js";
5
+ import { exitCode, observeSignals, startProcess } from "../runtime/process.js";
6
+ import { agencyArguments, agencyEnvironment, resolveProfileMode, validateAgencyOptions, } from "../agency/invocation.js";
7
+ import { AgencyxError, fail, formatDiagnostic } from "../diagnostics.js";
8
+ import { isRecord } from "../validation.js";
9
+ import { parseCodexOptions } from "../codex/options.js";
10
+ import { formatPlan } from "../diagnostics/plan.js";
11
+ export async function runCodex(options, env = process.env, cwd = process.cwd()) {
12
+ validateAgencyOptions(options.agencyArgs);
13
+ parseCodexOptions(options.codexArgs, cwd);
14
+ const [agencyPath, codexPath] = await Promise.all([
15
+ resolveCommand("agency", env.AGENCYX_AGENCY_PATH, cwd, env),
16
+ resolveCommand("codex", env.AGENCYX_CODEX_PATH, cwd, env),
17
+ ]);
18
+ const agencyEnv = agencyEnvironment(env);
19
+ const profileMode = await resolveProfileMode(agencyPath, options.agencyArgs, cwd, agencyEnv);
20
+ if (profileMode === "unknown")
21
+ fail("PROFILE_ISOLATION_UNKNOWN", "compatibility", "The effective Agency profile mode could not be established. Select --profile or --profile-only explicitly.");
22
+ const session = await createSession();
23
+ let server;
24
+ let removeSignals;
25
+ try {
26
+ const context = {
27
+ sessionId: session.id,
28
+ sessionRoot: session.root,
29
+ cwd,
30
+ codexPath,
31
+ agencyPath,
32
+ profileMode,
33
+ options,
34
+ };
35
+ server = await SessionServer.listen(session, context);
36
+ const launcher = await createLauncher(session, fileURLToPath(new URL("../cli.js", import.meta.url)), process.execPath, cwd);
37
+ const running = startProcess(agencyPath, agencyArguments(launcher.path, options.agencyArgs), {
38
+ cwd,
39
+ env: { ...agencyEnv, ...launcher.env },
40
+ stdio: options.dryRun ? ["inherit", "ignore", "inherit"] : "inherit",
41
+ });
42
+ const channel = server;
43
+ channel.on("execution-lost", () => {
44
+ void running.stop();
45
+ });
46
+ removeSignals = observeSignals((signal) => {
47
+ if (channel.executionConnected)
48
+ channel.abort(signal);
49
+ else
50
+ void running.stop(signal);
51
+ }, Boolean(process.stdin.isTTY), () => !channel.engineStarted);
52
+ const agencyResult = await running.completion;
53
+ if (options.dryRun && isRecord(channel.plan)) {
54
+ const report = channel.plan;
55
+ let code = channel.result
56
+ ? exitCode(channel.result)
57
+ : (channel.failureExitCode ?? exitCode(agencyResult));
58
+ if (code === 0 && exitCode(agencyResult) !== 0) {
59
+ const diagnostic = {
60
+ code: "AGENCY_CLEANUP_FAILED",
61
+ stage: "cleanup",
62
+ reason: "The plan was prepared, but Agency reported a cleanup failure.",
63
+ };
64
+ report.errors = [
65
+ ...(Array.isArray(report.errors) ? report.errors : []),
66
+ diagnostic,
67
+ ];
68
+ report.ready = false;
69
+ code = 1;
70
+ }
71
+ process.stdout.write(`${options.json ? JSON.stringify(report, null, 2) : formatPlan(report)}\n`);
72
+ return code;
73
+ }
74
+ if (channel.result && exitCode(channel.result) !== 0)
75
+ return exitCode(channel.result);
76
+ if (channel.failure)
77
+ throw new AgencyxError(channel.failure, channel.failureExitCode ?? 4);
78
+ if (!channel.result) {
79
+ if (exitCode(agencyResult) !== 0) {
80
+ if (options.dryRun)
81
+ fail("AGENCY_PREPARATION_FAILED", "agency", "Agency failed before a launch plan could be produced.", {}, exitCode(agencyResult));
82
+ return exitCode(agencyResult);
83
+ }
84
+ fail("HANDOFF_NOT_EXECUTED", "handoff", "Agency did not invoke the session bridge. The hidden engine-path override may no longer be compatible.", {}, 6);
85
+ }
86
+ if (exitCode(agencyResult) !== 0)
87
+ fail("AGENCY_CLEANUP_FAILED", "cleanup", "Codex succeeded, but Agency reported a preparation or cleanup failure.", {}, 1);
88
+ return 0;
89
+ }
90
+ finally {
91
+ removeSignals?.();
92
+ let cleanupFailed = false;
93
+ try {
94
+ await server?.close();
95
+ }
96
+ catch {
97
+ cleanupFailed = true;
98
+ }
99
+ try {
100
+ await session.dispose();
101
+ }
102
+ catch {
103
+ cleanupFailed = true;
104
+ }
105
+ if (cleanupFailed) {
106
+ const diagnostic = {
107
+ code: "SESSION_CLEANUP_FAILED",
108
+ stage: "cleanup",
109
+ reason: "The owned session resources could not be fully removed.",
110
+ };
111
+ if (server?.result && exitCode(server.result) !== 0)
112
+ process.stderr.write(`${formatDiagnostic(diagnostic)}\n`);
113
+ else
114
+ throw new AgencyxError(diagnostic, 1);
115
+ }
116
+ }
117
+ }
118
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1,58 @@
1
+ import { resolveCommand } from "../runtime/resolve-command.js";
2
+ import { captureProcess } from "../runtime/process.js";
3
+ import { asDiagnostic, formatDiagnostic } from "../diagnostics.js";
4
+ import { inspectCodex } from "../codex/inspection.js";
5
+ import { parseCodexOptions } from "../codex/options.js";
6
+ import { engineEnvironment } from "../runtime/session.js";
7
+ export async function doctor(json, env = process.env, cwd = process.cwd()) {
8
+ const errors = [];
9
+ const dependencies = {};
10
+ let capabilities;
11
+ for (const name of ["agency", "codex"]) {
12
+ try {
13
+ const executable = await resolveCommand(name, env[name === "agency" ? "AGENCYX_AGENCY_PATH" : "AGENCYX_CODEX_PATH"], cwd, env);
14
+ const result = await captureProcess(executable, ["--version"], {
15
+ cwd,
16
+ env: engineEnvironment(env),
17
+ timeoutMs: 5_000,
18
+ });
19
+ const version = result.stdout.match(/\b\d+\.\d+\.\d+(?:\.\d+)?(?:-[\w.]+)?/)?.[0];
20
+ dependencies[name] = { path: executable, version: version ?? "unknown" };
21
+ if (result.code !== 0)
22
+ errors.push({
23
+ code: "DEPENDENCY_PROBE_FAILED",
24
+ stage: "discovery",
25
+ component: name,
26
+ reason: "The executable version probe failed.",
27
+ });
28
+ }
29
+ catch (error) {
30
+ errors.push(asDiagnostic(error));
31
+ }
32
+ }
33
+ if (dependencies.codex) {
34
+ try {
35
+ const inspection = await inspectCodex(dependencies.codex.path, parseCodexOptions([], cwd), cwd, engineEnvironment(env));
36
+ capabilities = inspection.capabilities;
37
+ }
38
+ catch (error) {
39
+ errors.push(asDiagnostic(error));
40
+ }
41
+ }
42
+ const report = {
43
+ schemaVersion: 1,
44
+ ready: errors.length === 0,
45
+ node: process.version,
46
+ dependencies,
47
+ capabilities,
48
+ errors,
49
+ notes: [
50
+ "No model turn, business tool call, or plugin installation was performed.",
51
+ "Use codex --dry-run for Agency profile-specific component readiness.",
52
+ "Original Agency launcher and terminal behavior require platform-specific E2E acceptance.",
53
+ ],
54
+ };
55
+ process.stdout.write(`${json ? JSON.stringify(report, null, 2) : [`Doctor: ${report.ready ? "ready" : "blocked"}`, `Node: ${process.version}`, ...Object.entries(dependencies).map(([name, value]) => `${name}: ${value.version} (${value.path})`), ...errors.map(formatDiagnostic), ...report.notes].join("\n")}\n`);
56
+ return report.ready ? 0 : 3;
57
+ }
58
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,14 @@
1
+ import { resolveCommand } from "../runtime/resolve-command.js";
2
+ import { exitCode, observeSignals, runInteractive, } from "../runtime/process.js";
3
+ export async function passthrough(args, env = process.env, cwd = process.cwd()) {
4
+ const agency = await resolveCommand("agency", env.AGENCYX_AGENCY_PATH, cwd, env);
5
+ const abort = new AbortController();
6
+ const removeSignals = observeSignals((signal) => abort.abort(signal));
7
+ try {
8
+ return exitCode(await runInteractive(agency, args, { cwd, env, signal: abort.signal }));
9
+ }
10
+ finally {
11
+ removeSignals();
12
+ }
13
+ }
14
+ //# sourceMappingURL=passthrough.js.map
@@ -0,0 +1,87 @@
1
+ import { formatDiagnostic } from "../diagnostics.js";
2
+ import { isRecord } from "../validation.js";
3
+ export function diagnosticPlan(plan) {
4
+ // Whitelist the public shape. Opaque argv, headers, env values, instructions,
5
+ // and RPC credentials never enter the diagnostic object.
6
+ return {
7
+ schemaVersion: 1,
8
+ ready: plan.errors.length === 0,
9
+ strategy: plan.strategy,
10
+ reason: plan.reason,
11
+ cwd: plan.cwd,
12
+ profileMode: plan.profileMode,
13
+ mcp: {
14
+ selected: plan.mcpNames,
15
+ reused: plan.reusedMcpNames,
16
+ disabled: plan.disabledMcpNames,
17
+ aliases: plan.serverAliases,
18
+ servers: plan.mcpNames.map((name) => {
19
+ const definition = plan.expectedMcps?.[name];
20
+ if (!isRecord(definition))
21
+ return { name, state: "unresolved" };
22
+ const server = definition;
23
+ return {
24
+ name,
25
+ state: "planned",
26
+ transport: server.command !== undefined ? "stdio" : "streamable-http",
27
+ enabled: server.enabled !== false,
28
+ required: server.required === true,
29
+ allowedTools: server.enabled_tools ?? "all",
30
+ deniedTools: server.disabled_tools ?? [],
31
+ startupTimeoutSeconds: server.startup_timeout_sec ?? 10,
32
+ toolTimeoutSeconds: server.tool_timeout_sec ?? 60,
33
+ };
34
+ }),
35
+ },
36
+ plugins: plan.pluginIds,
37
+ agentToolsPolicy: plan.agentToolsPolicy ?? "strict",
38
+ agents: (plan.agents ?? []).map((agent) => ({
39
+ name: agent.name,
40
+ plugin: agent.pluginName,
41
+ sourcePath: agent.sourcePath,
42
+ toolAccess: "inherited-from-codex",
43
+ sourceToolsApplied: false,
44
+ declaredTools: agent.declaredTools,
45
+ })),
46
+ sessionHooks: (plan.sessionHooks ?? []).map((hook) => ({
47
+ plugin: hook.pluginName,
48
+ sourcePath: hook.sourcePath,
49
+ event: "SessionStart",
50
+ })),
51
+ adaptedPlugins: (plan.skillViews ?? []).map((view) => ({
52
+ name: view.pluginName,
53
+ skills: view.skills.map((skill) => skill.name),
54
+ nonUserInvocableSkills: view.skills
55
+ .filter((skill) => skill.userInvocable === false)
56
+ .map((skill) => skill.name),
57
+ })),
58
+ hasAdditionalDeveloperInstructions: plan.developerInstructions !== undefined,
59
+ findings: plan.findings,
60
+ errors: plan.errors,
61
+ notes: [
62
+ "Agency preparation can read remote configuration, prepare files, or use its existing authentication.",
63
+ "Dry runs do not create a model turn or execute lifecycle hooks.",
64
+ ],
65
+ };
66
+ }
67
+ export function formatPlan(value) {
68
+ // This formatter consumes only our diagnosticPlan output, never raw config.
69
+ const plan = value;
70
+ return [
71
+ `Launch plan: ${plan.ready ? "ready" : "blocked"}`,
72
+ `Backend: ${plan.strategy}`,
73
+ `Agency profile mode: ${plan.profileMode}`,
74
+ `MCP servers: ${plan.mcp.selected.join(", ") || "(none)"}`,
75
+ `Reused MCP servers: ${plan.mcp.reused.join(", ") || "(none)"}`,
76
+ `Disabled MCP servers: ${plan.mcp.disabled.join(", ") || "(none)"}`,
77
+ `Native plugins: ${plan.plugins.join(", ") || "(none)"}`,
78
+ `Native agents: ${plan.agents.map((agent) => agent.name).join(", ") || "(none)"}`,
79
+ `Agent tool policy: ${plan.agentToolsPolicy}${plan.agentToolsPolicy === "inherit" ? " (source tools allow-lists are not applied)" : ""}`,
80
+ `Session hooks: ${plan.sessionHooks.length}`,
81
+ `Additional developer instructions: ${plan.hasAdditionalDeveloperInstructions ? "yes (body withheld)" : "no"}`,
82
+ ...Object.entries(plan.mcp.aliases).map(([name, alias]) => `MCP alias: ${name} -> ${alias}`),
83
+ ...plan.errors.map(formatDiagnostic),
84
+ "No model turn or lifecycle hook was started by this dry run.",
85
+ ].join("\n");
86
+ }
87
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1,39 @@
1
+ export class AgencyxError extends Error {
2
+ diagnostic;
3
+ exitCode;
4
+ constructor(diagnostic, exitCode = 4) {
5
+ super(diagnostic.reason);
6
+ this.diagnostic = diagnostic;
7
+ this.exitCode = exitCode;
8
+ this.name = "AgencyxError";
9
+ }
10
+ }
11
+ export function fail(code, stage, reason, details = {}, exitCode = 4) {
12
+ throw new AgencyxError({ code, stage, reason, ...details }, exitCode);
13
+ }
14
+ export function asDiagnostic(error) {
15
+ if (error instanceof AgencyxError)
16
+ return error.diagnostic;
17
+ // Raw process, parser, and transport errors can contain credentials or instructions.
18
+ return {
19
+ code: "INTERNAL_ERROR",
20
+ stage: "codex",
21
+ reason: "An unexpected error occurred. No raw process output was included in this diagnostic.",
22
+ };
23
+ }
24
+ export function errorExitCode(error) {
25
+ return error instanceof AgencyxError ? error.exitCode : 1;
26
+ }
27
+ export function formatDiagnostic(diagnostic) {
28
+ const details = [
29
+ diagnostic.component && `Component: ${diagnostic.component}`,
30
+ diagnostic.field && `Field: ${diagnostic.field}`,
31
+ diagnostic.path && `File: ${diagnostic.path}`,
32
+ ].filter(Boolean);
33
+ return [
34
+ `${diagnostic.code} [${diagnostic.stage}]`,
35
+ ...details,
36
+ diagnostic.reason,
37
+ ].join("\n");
38
+ }
39
+ //# sourceMappingURL=diagnostics.js.map
package/dist/model.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=model.js.map