@nyxa/automation 0.1.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.
Files changed (48) hide show
  1. package/README.md +559 -0
  2. package/dist/cancellation.d.ts +1 -0
  3. package/dist/cancellation.js +5 -0
  4. package/dist/claude-code.d.ts +19 -0
  5. package/dist/claude-code.js +182 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +487 -0
  8. package/dist/codex-output.d.ts +6 -0
  9. package/dist/codex-output.js +167 -0
  10. package/dist/codex.d.ts +17 -0
  11. package/dist/codex.js +228 -0
  12. package/dist/doctor.d.ts +17 -0
  13. package/dist/doctor.js +138 -0
  14. package/dist/errors.d.ts +30 -0
  15. package/dist/errors.js +51 -0
  16. package/dist/events.d.ts +55 -0
  17. package/dist/events.js +51 -0
  18. package/dist/filesystem.d.ts +2 -0
  19. package/dist/filesystem.js +13 -0
  20. package/dist/harness-process.d.ts +39 -0
  21. package/dist/harness-process.js +359 -0
  22. package/dist/harness-prompt.d.ts +2 -0
  23. package/dist/harness-prompt.js +14 -0
  24. package/dist/harness.d.ts +44 -0
  25. package/dist/harness.js +68 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.js +9 -0
  28. package/dist/init.d.ts +1 -0
  29. package/dist/init.js +305 -0
  30. package/dist/json.d.ts +4 -0
  31. package/dist/json.js +1 -0
  32. package/dist/kimi-code.d.ts +16 -0
  33. package/dist/kimi-code.js +299 -0
  34. package/dist/opencode.d.ts +15 -0
  35. package/dist/opencode.js +164 -0
  36. package/dist/run-coordination.d.ts +10 -0
  37. package/dist/run-coordination.js +96 -0
  38. package/dist/run-journal.d.ts +14 -0
  39. package/dist/run-journal.js +129 -0
  40. package/dist/schema.d.ts +167 -0
  41. package/dist/schema.js +516 -0
  42. package/dist/standard-input.d.ts +1 -0
  43. package/dist/standard-input.js +7 -0
  44. package/dist/type-utils.d.ts +1 -0
  45. package/dist/type-utils.js +1 -0
  46. package/dist/workflow.d.ts +160 -0
  47. package/dist/workflow.js +530 -0
  48. package/package.json +51 -0
@@ -0,0 +1,167 @@
1
+ import { AutomationError } from "./errors.js";
2
+ export function prepareCodexOutput(schema) {
3
+ const lowered = lowerSchema(schema);
4
+ if (schema.type === "object") {
5
+ return lowered;
6
+ }
7
+ return {
8
+ schema: {
9
+ type: "object",
10
+ properties: { value: lowered.schema },
11
+ required: ["value"],
12
+ additionalProperties: false,
13
+ },
14
+ normalize(value) {
15
+ return isExactWrapper(value, "value")
16
+ ? lowered.normalize(value.value)
17
+ : value;
18
+ },
19
+ };
20
+ }
21
+ function lowerSchema(schema, path = []) {
22
+ if (schema.type === "object") {
23
+ return lowerObjectSchema(schema, path);
24
+ }
25
+ if (Array.isArray(schema.anyOf)) {
26
+ return isNullableSchema(schema.anyOf)
27
+ ? lowerNullableSchema(schema, path)
28
+ : lowerUnionSchema(schema, path);
29
+ }
30
+ if (schema.type === "array") {
31
+ const item = lowerSchema(asJsonSchema(schema.items), [...path, "*"]);
32
+ return {
33
+ schema: { ...schema, items: item.schema },
34
+ normalize(value) {
35
+ return Array.isArray(value)
36
+ ? value.map((entry) => item.normalize(entry))
37
+ : value;
38
+ },
39
+ };
40
+ }
41
+ return { schema, normalize: (value) => value };
42
+ }
43
+ function lowerNullableSchema(schema, path) {
44
+ const alternatives = schema.anyOf;
45
+ const value = lowerSchema(asJsonSchema(alternatives[0]), path);
46
+ const nullSchema = asJsonSchema(alternatives[1]);
47
+ return {
48
+ schema: { ...schema, anyOf: [value.schema, nullSchema] },
49
+ normalize(nativeValue) {
50
+ return nativeValue === null ? null : value.normalize(nativeValue);
51
+ },
52
+ };
53
+ }
54
+ function lowerUnionSchema(schema, path) {
55
+ const { anyOf, ...annotations } = schema;
56
+ const alternatives = anyOf.map((candidate) => lowerSchema(asJsonSchema(candidate), path));
57
+ return {
58
+ schema: {
59
+ ...annotations,
60
+ anyOf: alternatives.map((alternative, index) => {
61
+ const key = variantKey(index);
62
+ return {
63
+ type: "object",
64
+ properties: { [key]: alternative.schema },
65
+ required: [key],
66
+ additionalProperties: false,
67
+ };
68
+ }),
69
+ },
70
+ normalize(value) {
71
+ if (!isRecord(value) || Object.keys(value).length !== 1) {
72
+ return value;
73
+ }
74
+ for (const [index, alternative] of alternatives.entries()) {
75
+ const key = variantKey(index);
76
+ if (Object.hasOwn(value, key)) {
77
+ return alternative.normalize(value[key]);
78
+ }
79
+ }
80
+ return value;
81
+ },
82
+ };
83
+ }
84
+ function lowerObjectSchema(schema, path) {
85
+ const properties = asSchemaProperties(schema.properties);
86
+ const portableRequired = new Set(asStringArray(schema.required));
87
+ const loweredEntries = Object.entries(properties).map(([key, property]) => {
88
+ const optional = !portableRequired.has(key);
89
+ if (optional && acceptsNull(property)) {
90
+ const propertyPath = [...path, key].join(".");
91
+ throw new AutomationError("OUTPUT_SCHEMA_UNSUPPORTED", `Codex cannot distinguish an absent optional property from null at ${propertyPath}.`);
92
+ }
93
+ const lowered = lowerSchema(property, [...path, key]);
94
+ const transported = optional
95
+ ? makeNullable(lowered.schema)
96
+ : lowered.schema;
97
+ return { key, lowered, optional, transported };
98
+ });
99
+ const loweredProperties = Object.fromEntries(loweredEntries.map((entry) => [entry.key, entry.transported]));
100
+ return {
101
+ schema: {
102
+ ...schema,
103
+ properties: loweredProperties,
104
+ required: Object.keys(properties),
105
+ },
106
+ normalize(value) {
107
+ if (!isRecord(value)) {
108
+ return value;
109
+ }
110
+ const normalized = { ...value };
111
+ for (const entry of loweredEntries) {
112
+ if (entry.optional && normalized[entry.key] === null) {
113
+ delete normalized[entry.key];
114
+ }
115
+ else if (Object.hasOwn(normalized, entry.key)) {
116
+ normalized[entry.key] = entry.lowered.normalize(normalized[entry.key]);
117
+ }
118
+ }
119
+ return normalized;
120
+ },
121
+ };
122
+ }
123
+ function makeNullable(schema) {
124
+ const { description, ...undocumentedSchema } = schema;
125
+ return {
126
+ anyOf: [undocumentedSchema, { type: "null" }],
127
+ ...(description === undefined ? {} : { description }),
128
+ };
129
+ }
130
+ function acceptsNull(schema) {
131
+ return (schema.type === "null" ||
132
+ schema.const === null ||
133
+ (Array.isArray(schema.enum) && schema.enum.includes(null)) ||
134
+ (Array.isArray(schema.anyOf) &&
135
+ schema.anyOf.some((candidate) => acceptsNull(asJsonSchema(candidate)))));
136
+ }
137
+ function isNullableSchema(anyOf) {
138
+ return anyOf.length === 2 && asJsonSchema(anyOf[1]).type === "null";
139
+ }
140
+ function variantKey(index) {
141
+ return `variant_${String(index)}`;
142
+ }
143
+ function isExactWrapper(value, key) {
144
+ return (isRecord(value) &&
145
+ Object.keys(value).length === 1 &&
146
+ Object.hasOwn(value, key));
147
+ }
148
+ function asJsonSchema(value) {
149
+ return isRecord(value) ? value : {};
150
+ }
151
+ function asSchemaProperties(value) {
152
+ if (!isRecord(value)) {
153
+ return {};
154
+ }
155
+ return Object.fromEntries(Object.entries(value).map(([key, property]) => [
156
+ key,
157
+ asJsonSchema(property),
158
+ ]));
159
+ }
160
+ function asStringArray(value) {
161
+ return Array.isArray(value)
162
+ ? value.filter((entry) => typeof entry === "string")
163
+ : [];
164
+ }
165
+ function isRecord(value) {
166
+ return typeof value === "object" && value !== null && !Array.isArray(value);
167
+ }
@@ -0,0 +1,17 @@
1
+ import { type Harness } from "./harness.js";
2
+ import type { NoExtraKeys } from "./type-utils.js";
3
+ export declare const CODEX_HARNESS_PREFLIGHT: {
4
+ readonly harnessName: "Codex";
5
+ readonly minimumVersion: readonly [0, 144, 0];
6
+ readonly unsupportedVersionMessage: "Codex CLI 0.144.0 or later is required.";
7
+ readonly versionPattern: RegExp;
8
+ };
9
+ export type CodexEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
10
+ export interface CodexOptions {
11
+ readonly executable?: string;
12
+ readonly model?: string;
13
+ readonly effort?: CodexEffort;
14
+ readonly profile?: string;
15
+ }
16
+ export declare function codex(): Harness;
17
+ export declare function codex<const TOptions extends CodexOptions>(options: TOptions & NoExtraKeys<TOptions, CodexOptions>): Harness;
package/dist/codex.js ADDED
@@ -0,0 +1,228 @@
1
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { prepareCodexOutput } from "./codex-output.js";
5
+ import { AutomationError } from "./errors.js";
6
+ import { createHarnessPreflight, harnessProcessExitError, harnessProcessFailureCause, runHarnessProcess, } from "./harness-process.js";
7
+ import { createHarness, harnessProcessControls, } from "./harness.js";
8
+ export const CODEX_HARNESS_PREFLIGHT = {
9
+ harnessName: "Codex",
10
+ minimumVersion: [0, 144, 0],
11
+ unsupportedVersionMessage: "Codex CLI 0.144.0 or later is required.",
12
+ versionPattern: /\bcodex(?:-cli)?\s+(\d+)\.(\d+)\.(\d+)(?:-([0-9a-z.-]+))?(?:\+[0-9a-z.-]+)?(?:\s|$)/iu,
13
+ };
14
+ const preflightCodex = createHarnessPreflight(CODEX_HARNESS_PREFLIGHT);
15
+ export function codex(options = {}) {
16
+ const configuredOptions = Object.freeze({ ...options });
17
+ return createHarness({
18
+ name: "Codex",
19
+ async run(request) {
20
+ if (request.approval === "auto") {
21
+ throw new AutomationError("HARNESS_CAPABILITY_UNSUPPORTED", "Codex does not provide non-interactive automatic approvals.");
22
+ }
23
+ const preparedOutput = request.output === undefined
24
+ ? undefined
25
+ : prepareCodexOutput(request.output.toJSONSchema());
26
+ const executable = await preflightCodex(configuredOptions.executable ?? "codex", request.environment, harnessProcessControls(request));
27
+ return executeCodex(executable, configuredOptions, request, preparedOutput);
28
+ },
29
+ });
30
+ }
31
+ async function executeCodex(executable, options, request, preparedOutput) {
32
+ if (preparedOutput !== undefined) {
33
+ return withOutputSchema(preparedOutput, request.sessionReady, (schemaPath) => executeCodexProcess(executable, options, request, schemaPath));
34
+ }
35
+ return executeCodexProcess(executable, options, request);
36
+ }
37
+ async function executeCodexProcess(executable, options, request, outputSchemaPath) {
38
+ const arguments_ = request.nativeSessionId === undefined
39
+ ? [
40
+ "exec",
41
+ "--json",
42
+ "--sandbox",
43
+ codexSandbox(request.access),
44
+ "-c",
45
+ 'approval_policy="never"',
46
+ ]
47
+ : [
48
+ "exec",
49
+ "resume",
50
+ "--json",
51
+ "-c",
52
+ `sandbox_mode=${JSON.stringify(codexSandbox(request.access))}`,
53
+ "-c",
54
+ 'approval_policy="never"',
55
+ ];
56
+ if (options.model !== undefined) {
57
+ arguments_.push("--model", options.model);
58
+ }
59
+ if (options.effort !== undefined) {
60
+ arguments_.push("-c", `model_reasoning_effort=${JSON.stringify(options.effort)}`);
61
+ }
62
+ if (options.profile !== undefined && request.nativeSessionId === undefined) {
63
+ arguments_.push("--profile", options.profile);
64
+ }
65
+ if (outputSchemaPath !== undefined) {
66
+ arguments_.push("--output-schema", outputSchemaPath);
67
+ }
68
+ if (request.nativeSessionId !== undefined) {
69
+ arguments_.push(request.nativeSessionId);
70
+ }
71
+ arguments_.push(request.prompt);
72
+ await request.start();
73
+ let finalMessage;
74
+ let nativeSessionId = request.nativeSessionId;
75
+ let turnCompleted = false;
76
+ const result = await runHarnessProcess(executable, arguments_, request.workingDirectory, "Codex", {
77
+ environment: request.environment,
78
+ ...harnessProcessControls(request),
79
+ async onStdoutLine(line) {
80
+ if (line.trim().length === 0) {
81
+ return;
82
+ }
83
+ const event = parseCodexEvent(line);
84
+ await request.emitNativeEvent(event);
85
+ if (turnCompleted) {
86
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex emitted an event after the terminal turn event.");
87
+ }
88
+ const eventSessionId = codexSessionId(event);
89
+ if (eventSessionId !== undefined) {
90
+ if (nativeSessionId !== undefined &&
91
+ eventSessionId !== nativeSessionId) {
92
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex resumed a different Session than requested.");
93
+ }
94
+ nativeSessionId = eventSessionId;
95
+ }
96
+ if (isAgentMessage(event)) {
97
+ finalMessage = event.item.text;
98
+ }
99
+ if (isTurnCompleted(event)) {
100
+ turnCompleted = true;
101
+ }
102
+ },
103
+ });
104
+ if (result.exitCode !== 0) {
105
+ throw harnessProcessExitError("Codex", result);
106
+ }
107
+ if (nativeSessionId === undefined) {
108
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex completed without a Session identifier.");
109
+ }
110
+ if (!turnCompleted) {
111
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex completed without a terminal turn event.");
112
+ }
113
+ if (finalMessage === undefined) {
114
+ if (outputSchemaPath !== undefined) {
115
+ request.sessionReady?.(nativeSessionId);
116
+ throw new AutomationError("RUN_OUTPUT_INVALID", "The Codex Run completed without structured output.", { cause: new TypeError("Missing final Codex agent message.") });
117
+ }
118
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex completed without a final agent message.");
119
+ }
120
+ if (outputSchemaPath === undefined) {
121
+ return { nativeSessionId, value: finalMessage };
122
+ }
123
+ try {
124
+ return {
125
+ nativeSessionId,
126
+ value: JSON.parse(finalMessage),
127
+ };
128
+ }
129
+ catch (cause) {
130
+ request.sessionReady?.(nativeSessionId);
131
+ throw new AutomationError("RUN_OUTPUT_INVALID", "The Codex Run returned malformed structured output.", { cause });
132
+ }
133
+ }
134
+ function codexSandbox(access) {
135
+ switch (access) {
136
+ case "read":
137
+ return "read-only";
138
+ case "write-workspace":
139
+ return "workspace-write";
140
+ case "full":
141
+ return "danger-full-access";
142
+ }
143
+ }
144
+ async function withOutputSchema(preparedOutput, sessionReady, execute) {
145
+ let directory;
146
+ try {
147
+ directory = await mkdtemp(path.join(os.tmpdir(), "automation-codex-"));
148
+ }
149
+ catch (cause) {
150
+ throw new AutomationError("HARNESS_PROCESS_FAILED", "Could not prepare the Codex structured output schema.", { cause });
151
+ }
152
+ const schemaPath = path.join(directory, "output-schema.json");
153
+ let failure;
154
+ let nativeValue;
155
+ try {
156
+ try {
157
+ await writeFile(schemaPath, `${JSON.stringify(preparedOutput.schema)}\n`, "utf8");
158
+ }
159
+ catch (cause) {
160
+ throw new AutomationError("HARNESS_PROCESS_FAILED", "Could not prepare the Codex structured output schema.", { cause });
161
+ }
162
+ nativeValue = await execute(schemaPath);
163
+ }
164
+ catch (cause) {
165
+ failure = cause;
166
+ }
167
+ try {
168
+ await rm(directory, { force: true, recursive: true });
169
+ }
170
+ catch (cleanupCause) {
171
+ if (failure === undefined) {
172
+ failure = new AutomationError("HARNESS_PROCESS_FAILED", "Could not clean up the Codex structured output schema.", { cause: cleanupCause });
173
+ }
174
+ }
175
+ if (failure !== undefined) {
176
+ if (nativeValue !== undefined) {
177
+ sessionReady?.(nativeValue.nativeSessionId);
178
+ }
179
+ throw failure;
180
+ }
181
+ if (nativeValue === undefined) {
182
+ throw new AutomationError("HARNESS_PROCESS_FAILED", "Codex completed without returning a Run result.");
183
+ }
184
+ try {
185
+ return {
186
+ nativeSessionId: nativeValue.nativeSessionId,
187
+ value: preparedOutput.normalize(nativeValue.value),
188
+ };
189
+ }
190
+ catch (cause) {
191
+ sessionReady?.(nativeValue.nativeSessionId);
192
+ throw cause;
193
+ }
194
+ }
195
+ function parseCodexEvent(line) {
196
+ try {
197
+ return JSON.parse(line);
198
+ }
199
+ catch (cause) {
200
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex emitted malformed JSONL.", { cause });
201
+ }
202
+ }
203
+ function codexSessionId(event) {
204
+ if (typeof event !== "object" ||
205
+ event === null ||
206
+ event.type !== "thread.started") {
207
+ return undefined;
208
+ }
209
+ const threadId = event.thread_id;
210
+ if (typeof threadId !== "string" || threadId.length === 0) {
211
+ throw new AutomationError("HARNESS_PROTOCOL_ERROR", "Codex emitted an invalid Session identifier.");
212
+ }
213
+ return threadId;
214
+ }
215
+ function isTurnCompleted(event) {
216
+ return (typeof event === "object" &&
217
+ event !== null &&
218
+ event.type === "turn.completed");
219
+ }
220
+ function isAgentMessage(event) {
221
+ if (typeof event !== "object" || event === null) {
222
+ return false;
223
+ }
224
+ const candidate = event;
225
+ return (candidate.type === "item.completed" &&
226
+ candidate.item?.type === "agent_message" &&
227
+ typeof candidate.item.text === "string");
228
+ }
@@ -0,0 +1,17 @@
1
+ export type DoctorHarness = "claude-code" | "codex" | "kimi-code" | "opencode";
2
+ export interface DoctorOptions {
3
+ readonly environment?: NodeJS.ProcessEnv;
4
+ readonly harness?: DoctorHarness;
5
+ readonly invocationDirectory?: string;
6
+ readonly localPackage?: DoctorLocalPackage;
7
+ readonly nodeVersion?: string;
8
+ }
9
+ export interface DoctorLocalPackage {
10
+ readonly binaryAvailable: boolean;
11
+ readonly version: string;
12
+ }
13
+ export interface DoctorResult {
14
+ readonly output: string;
15
+ readonly ready: boolean;
16
+ }
17
+ export declare function diagnoseEnvironment(options?: DoctorOptions): Promise<DoctorResult>;
package/dist/doctor.js ADDED
@@ -0,0 +1,138 @@
1
+ import { CLAUDE_CODE_HARNESS_PREFLIGHT } from "./claude-code.js";
2
+ import { CODEX_HARNESS_PREFLIGHT } from "./codex.js";
3
+ import { isAutomationError } from "./errors.js";
4
+ import { inspectHarnessExecutable, runHarnessProcess, } from "./harness-process.js";
5
+ import { KIMI_CODE_HARNESS_PREFLIGHT, verifyKimiCodeAuthentication, } from "./kimi-code.js";
6
+ import { OPENCODE_HARNESS_PREFLIGHT } from "./opencode.js";
7
+ export async function diagnoseEnvironment(options = {}) {
8
+ const environment = options.environment ?? process.env;
9
+ const invocationDirectory = options.invocationDirectory ?? process.cwd();
10
+ const nodeVersion = options.nodeVersion ?? process.versions.node;
11
+ const nodeReady = nodeMajorVersion(nodeVersion) >= 22;
12
+ const localPackageReady = options.localPackage?.binaryAvailable === true;
13
+ const harnesses = options.harness === undefined
14
+ ? HARNESS_DEFINITIONS
15
+ : HARNESS_DEFINITIONS.filter((definition) => definition.id === options.harness);
16
+ const harnessDiagnoses = await Promise.all(harnesses.map(async (definition) => ({
17
+ definition,
18
+ diagnosis: await diagnoseHarness(definition, environment, invocationDirectory),
19
+ })));
20
+ const anyHarnessReady = harnessDiagnoses.some(({ diagnosis }) => diagnosis.ready);
21
+ const diagnostics = [
22
+ diagnostic(nodeReady, `Node.js ${nodeVersion} ${nodeReady ? "meets" : "does not meet"} the requirement: doctor requires 22 or later.`),
23
+ diagnostic(localPackageReady, options.localPackage === undefined
24
+ ? "Project-local @nyxa/automation is not installed."
25
+ : localPackageReady
26
+ ? `Project-local @nyxa/automation ${options.localPackage.version} is installed.`
27
+ : `Project-local @nyxa/automation ${options.localPackage.version} is installed, but its automation binary is missing.`),
28
+ ...harnessDiagnoses.map(({ diagnosis }) => harnessDiagnostic(diagnosis, anyHarnessReady)),
29
+ ];
30
+ return {
31
+ output: `${diagnostics.join("\n")}\n`,
32
+ ready: nodeReady && localPackageReady && anyHarnessReady,
33
+ };
34
+ }
35
+ function nodeMajorVersion(version) {
36
+ const [major] = version.split(".", 1);
37
+ return Number(major);
38
+ }
39
+ const HARNESS_DEFINITIONS = [
40
+ {
41
+ authenticationArguments: ["login", "status"],
42
+ executable: "codex",
43
+ id: "codex",
44
+ preflight: CODEX_HARNESS_PREFLIGHT,
45
+ },
46
+ {
47
+ authenticationArguments: ["auth", "status"],
48
+ executable: "claude",
49
+ id: "claude-code",
50
+ preflight: CLAUDE_CODE_HARNESS_PREFLIGHT,
51
+ },
52
+ {
53
+ executable: "kimi",
54
+ id: "kimi-code",
55
+ preflight: KIMI_CODE_HARNESS_PREFLIGHT,
56
+ },
57
+ {
58
+ authenticationArguments: ["auth", "list"],
59
+ executable: "opencode",
60
+ id: "opencode",
61
+ preflight: OPENCODE_HARNESS_PREFLIGHT,
62
+ },
63
+ ];
64
+ async function diagnoseHarness(definition, environment, invocationDirectory) {
65
+ try {
66
+ const inspection = await inspectHarnessExecutable(definition.executable, definition.preflight, environment);
67
+ if (definition.id === "kimi-code") {
68
+ await verifyKimiCodeAuthentication(inspection.executable, invocationDirectory, environment);
69
+ return {
70
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed and authenticated.`,
71
+ ready: true,
72
+ };
73
+ }
74
+ const authentication = await runHarnessProcess(inspection.executable, definition.authenticationArguments ?? [], invocationDirectory, definition.preflight.harnessName, { environment });
75
+ if (definition.id === "opencode") {
76
+ if (authentication.exitCode === 0 &&
77
+ hasOpenCodeAuthentication(authentication.stdout)) {
78
+ return {
79
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed and authenticated.`,
80
+ ready: true,
81
+ };
82
+ }
83
+ return {
84
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed, but authentication could not be verified.`,
85
+ ready: false,
86
+ };
87
+ }
88
+ if (authentication.exitCode === 0) {
89
+ return {
90
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed and authenticated.`,
91
+ ready: true,
92
+ };
93
+ }
94
+ if (authentication.exitCode === 1) {
95
+ return {
96
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed but not authenticated.`,
97
+ ready: false,
98
+ };
99
+ }
100
+ return {
101
+ message: `${definition.preflight.harnessName} ${inspection.version} is installed, but authentication could not be verified.`,
102
+ ready: false,
103
+ };
104
+ }
105
+ catch (error) {
106
+ if (isAutomationError(error) && error.code === "HARNESS_NOT_FOUND") {
107
+ return {
108
+ message: `${definition.preflight.harnessName} executable was not found.`,
109
+ ready: false,
110
+ };
111
+ }
112
+ if (isAutomationError(error) &&
113
+ error.code === "HARNESS_VERSION_UNSUPPORTED") {
114
+ return {
115
+ message: error.message,
116
+ ready: false,
117
+ };
118
+ }
119
+ if (isAutomationError(error)) {
120
+ return { message: error.message, ready: false };
121
+ }
122
+ return {
123
+ message: `${definition.preflight.harnessName} could not be inspected safely.`,
124
+ ready: false,
125
+ };
126
+ }
127
+ }
128
+ function hasOpenCodeAuthentication(output) {
129
+ const plainOutput = output.replace(/\u001B\[[0-?]*[ -/]*[@-~]/gu, "");
130
+ const authenticationCounts = plainOutput.matchAll(/\b(\d+)\s+(?:credentials?|environment variables?)\b/giu);
131
+ return [...authenticationCounts].some((match) => Number(match[1]) > 0);
132
+ }
133
+ function harnessDiagnostic(diagnosis, anyHarnessReady) {
134
+ return `[${diagnosis.ready ? "ok" : anyHarnessReady ? "warn" : "fail"}] ${diagnosis.message}`;
135
+ }
136
+ function diagnostic(success, message) {
137
+ return `[${success ? "ok" : "fail"}] ${message}`;
138
+ }
@@ -0,0 +1,30 @@
1
+ export type AutomationErrorCode = "EVENT_OBSERVER_FAILED" | "HARNESS_NOT_FOUND" | "HARNESS_CAPABILITY_UNSUPPORTED" | "HARNESS_NOT_AUTHENTICATED" | "HARNESS_PROCESS_FAILED" | "HARNESS_PROTOCOL_ERROR" | "HARNESS_REQUIRED" | "HARNESS_VERSION_UNSUPPORTED" | "OUTPUT_SCHEMA_UNSUPPORTED" | "RUN_CANCELLED" | "RUN_CWD_INVALID" | "RUN_JOURNAL_UNAVAILABLE" | "RUN_OUTPUT_INVALID" | "RUN_POLICY_INVALID" | "RUN_TIMED_OUT" | "SESSION_BUSY" | "SESSION_UNAVAILABLE" | "WORKFLOW_MODULE_LOAD_FAILED" | "WORKFLOW_INPUT_INVALID" | "WORKFLOW_INPUT_UNEXPECTED" | "WORKFLOW_EXECUTION_FAILED" | "WORKFLOW_OUTPUT_INVALID" | "WORKSPACE_CONCURRENCY_CONFLICT";
2
+ export interface AutomationErrorDetails {
3
+ readonly code: AutomationErrorCode;
4
+ readonly harness: string | undefined;
5
+ readonly message: string;
6
+ readonly runId: string | undefined;
7
+ readonly sessionId: string | undefined;
8
+ readonly workflowId: string | undefined;
9
+ }
10
+ export interface AutomationErrorOptions extends ErrorOptions {
11
+ readonly harness?: string | undefined;
12
+ readonly runId?: string | undefined;
13
+ readonly sessionId?: string | undefined;
14
+ readonly workflowId?: string | undefined;
15
+ }
16
+ declare const automationErrorBrand: unique symbol;
17
+ export declare class AutomationError extends Error {
18
+ readonly [automationErrorBrand] = true;
19
+ readonly code: AutomationErrorCode;
20
+ readonly harness: string | undefined;
21
+ readonly runId: string | undefined;
22
+ readonly sessionId: string | undefined;
23
+ readonly workflowId: string | undefined;
24
+ constructor(code: AutomationErrorCode, message: string, options?: AutomationErrorOptions);
25
+ toJSON(): AutomationErrorDetails;
26
+ }
27
+ export declare function isAutomationError(value: unknown): value is AutomationError;
28
+ export declare function automationErrorDetails(error: AutomationError): AutomationErrorDetails;
29
+ export declare function contextualizeAutomationError(cause: unknown, fallbackCode: AutomationErrorCode, fallbackMessage: string, context: AutomationErrorOptions): AutomationError;
30
+ export {};
package/dist/errors.js ADDED
@@ -0,0 +1,51 @@
1
+ const automationErrorBrand = Symbol.for("@nyxa/automation/error");
2
+ export class AutomationError extends Error {
3
+ [automationErrorBrand] = true;
4
+ code;
5
+ harness;
6
+ runId;
7
+ sessionId;
8
+ workflowId;
9
+ constructor(code, message, options) {
10
+ super(message, options);
11
+ this.name = "AutomationError";
12
+ this.code = code;
13
+ this.harness = options?.harness;
14
+ this.runId = options?.runId;
15
+ this.sessionId = options?.sessionId;
16
+ this.workflowId = options?.workflowId;
17
+ }
18
+ toJSON() {
19
+ return automationErrorDetails(this);
20
+ }
21
+ }
22
+ export function isAutomationError(value) {
23
+ return (typeof value === "object" &&
24
+ value !== null &&
25
+ value[automationErrorBrand] === true);
26
+ }
27
+ export function automationErrorDetails(error) {
28
+ return {
29
+ code: error.code,
30
+ harness: error.harness,
31
+ message: error.message,
32
+ runId: error.runId,
33
+ sessionId: error.sessionId,
34
+ workflowId: error.workflowId,
35
+ };
36
+ }
37
+ export function contextualizeAutomationError(cause, fallbackCode, fallbackMessage, context) {
38
+ if (!isAutomationError(cause)) {
39
+ return new AutomationError(fallbackCode, fallbackMessage, {
40
+ ...context,
41
+ cause,
42
+ });
43
+ }
44
+ return new AutomationError(cause.code, cause.message, {
45
+ cause: cause.cause,
46
+ harness: cause.harness ?? context.harness,
47
+ runId: cause.runId ?? context.runId,
48
+ sessionId: cause.sessionId ?? context.sessionId,
49
+ workflowId: cause.workflowId ?? context.workflowId,
50
+ });
51
+ }