@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,55 @@
1
+ import { AutomationError, type AutomationErrorDetails } from "./errors.js";
2
+ import type { RunJournalContent } from "./run-journal.js";
3
+ export interface AutomationEventBase {
4
+ readonly sequence: number;
5
+ readonly timestamp: string;
6
+ readonly workflowId: string;
7
+ }
8
+ export interface WorkflowStartedEvent extends AutomationEventBase {
9
+ readonly type: "workflow.started";
10
+ }
11
+ export interface WorkflowCompletedEvent extends AutomationEventBase {
12
+ readonly type: "workflow.completed";
13
+ readonly result: unknown;
14
+ }
15
+ export interface RunEventBase extends AutomationEventBase {
16
+ readonly harness: string | undefined;
17
+ readonly runId: string;
18
+ readonly runName: string | undefined;
19
+ readonly sessionId: string;
20
+ readonly sessionName: string | undefined;
21
+ }
22
+ export interface RunStartedEvent extends RunEventBase {
23
+ readonly type: "run.started";
24
+ }
25
+ export interface RunNativeEvent extends RunEventBase {
26
+ readonly type: "run.native";
27
+ readonly harness: string;
28
+ readonly native: {
29
+ readonly portable: false;
30
+ readonly event: unknown;
31
+ };
32
+ }
33
+ export interface RunCompletedEvent extends RunEventBase {
34
+ readonly type: "run.completed";
35
+ readonly harness: string;
36
+ }
37
+ export interface RunFailedEvent extends RunEventBase {
38
+ readonly type: "run.failed";
39
+ readonly error: AutomationErrorDetails;
40
+ }
41
+ export interface WorkflowFailedEvent extends AutomationEventBase {
42
+ readonly type: "workflow.failed";
43
+ readonly error: AutomationErrorDetails;
44
+ }
45
+ export type AutomationEvent = RunCompletedEvent | RunFailedEvent | RunNativeEvent | RunStartedEvent | WorkflowCompletedEvent | WorkflowFailedEvent | WorkflowStartedEvent;
46
+ export type AutomationEventObserver = (event: AutomationEvent) => PromiseLike<void> | void;
47
+ type PendingAutomationEvent = AutomationEvent extends infer TEvent ? TEvent extends AutomationEvent ? Omit<TEvent, "sequence" | "timestamp"> : never : never;
48
+ export type AutomationEventSink = (event: AutomationEvent, content: RunJournalContent | undefined) => PromiseLike<void> | void;
49
+ export declare class AutomationEventDispatcher {
50
+ #private;
51
+ constructor(observer: AutomationEventObserver | undefined, sink?: AutomationEventSink, observerFailure?: (error: AutomationError) => void);
52
+ get failed(): boolean;
53
+ emit(event: PendingAutomationEvent, content?: RunJournalContent): Promise<void>;
54
+ }
55
+ export {};
package/dist/events.js ADDED
@@ -0,0 +1,51 @@
1
+ import { AutomationError } from "./errors.js";
2
+ export class AutomationEventDispatcher {
3
+ #observerFailure;
4
+ #observer;
5
+ #sink;
6
+ #delivery = Promise.resolve();
7
+ #sequence = 0;
8
+ #failure;
9
+ constructor(observer, sink, observerFailure) {
10
+ this.#observer = observer;
11
+ this.#sink = sink;
12
+ this.#observerFailure = observerFailure;
13
+ }
14
+ get failed() {
15
+ return this.#failure !== undefined;
16
+ }
17
+ async emit(event, content) {
18
+ if (this.#observer === undefined && this.#sink === undefined) {
19
+ return;
20
+ }
21
+ const observableEvent = Object.freeze({
22
+ ...event,
23
+ sequence: this.#sequence,
24
+ timestamp: new Date().toISOString(),
25
+ });
26
+ this.#sequence += 1;
27
+ const delivery = this.#delivery.then(async () => {
28
+ if (this.#failure !== undefined) {
29
+ throw this.#failure;
30
+ }
31
+ const sinkDelivery = Promise.resolve(this.#sink?.(observableEvent, content));
32
+ try {
33
+ await this.#observer?.(observableEvent);
34
+ }
35
+ catch (cause) {
36
+ this.#failure = new AutomationError("EVENT_OBSERVER_FAILED", "The Automation Event observer failed.", {
37
+ cause,
38
+ runId: "runId" in event ? event.runId : undefined,
39
+ sessionId: "sessionId" in event ? event.sessionId : undefined,
40
+ workflowId: event.workflowId,
41
+ });
42
+ this.#observerFailure?.(this.#failure);
43
+ await sinkDelivery;
44
+ throw this.#failure;
45
+ }
46
+ await sinkDelivery;
47
+ });
48
+ this.#delivery = delivery.catch(() => undefined);
49
+ await delivery;
50
+ }
51
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isMissingFileError(error: unknown): boolean;
2
+ export declare function isWithinDirectory(parent: string, candidate: string): boolean;
@@ -0,0 +1,13 @@
1
+ import path from "node:path";
2
+ export function isMissingFileError(error) {
3
+ return (error instanceof Error &&
4
+ "code" in error &&
5
+ (error.code === "ENOENT" || error.code === "ENOTDIR"));
6
+ }
7
+ export function isWithinDirectory(parent, candidate) {
8
+ const relative = path.relative(parent, candidate);
9
+ return (relative === "" ||
10
+ (relative !== ".." &&
11
+ !relative.startsWith(`..${path.sep}`) &&
12
+ !path.isAbsolute(relative)));
13
+ }
@@ -0,0 +1,39 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import { AutomationError } from "./errors.js";
3
+ export interface HarnessProcessResult {
4
+ readonly exitCode: number | null;
5
+ readonly stderr: string;
6
+ readonly stdout: string;
7
+ }
8
+ export interface HarnessProcessOptions {
9
+ readonly environment?: NodeJS.ProcessEnv;
10
+ readonly forceSignal?: AbortSignal;
11
+ readonly onStdoutLine?: (line: string) => Promise<void>;
12
+ readonly signal?: AbortSignal;
13
+ }
14
+ export interface HarnessProtocolProcess {
15
+ readonly input: Writable;
16
+ readonly output: Readable;
17
+ onInterrupt(handler: () => PromiseLike<void> | void): void;
18
+ }
19
+ export interface HarnessProtocolProcessResult<TValue> {
20
+ readonly exitCode: number | null;
21
+ readonly stderr: string;
22
+ readonly value: TValue;
23
+ }
24
+ export interface HarnessPreflightOptions {
25
+ readonly harnessName: string;
26
+ readonly minimumVersion: readonly [number, number, number];
27
+ readonly unsupportedVersionMessage: string;
28
+ readonly versionPattern: RegExp;
29
+ }
30
+ export interface HarnessExecutableInspection {
31
+ readonly executable: string;
32
+ readonly version: string;
33
+ }
34
+ export declare function createHarnessPreflight(options: HarnessPreflightOptions): (executable: string, environment?: NodeJS.ProcessEnv, processOptions?: Pick<HarnessProcessOptions, "forceSignal" | "signal">) => Promise<string>;
35
+ export declare function inspectHarnessExecutable(executable: string, options: HarnessPreflightOptions, environment?: NodeJS.ProcessEnv, processOptions?: Pick<HarnessProcessOptions, "forceSignal" | "signal">): Promise<HarnessExecutableInspection>;
36
+ export declare function runHarnessProcess(executable: string, arguments_: readonly string[], workingDirectory: string, harnessName: string, options?: HarnessProcessOptions): Promise<HarnessProcessResult>;
37
+ export declare function runHarnessProtocolProcess<TValue>(executable: string, arguments_: readonly string[], workingDirectory: string, harnessName: string, interact: (process: HarnessProtocolProcess) => Promise<TValue>, options?: Pick<HarnessProcessOptions, "environment" | "forceSignal" | "signal">): Promise<HarnessProtocolProcessResult<TValue>>;
38
+ export declare function harnessProcessFailureCause(stderr: string): Error | undefined;
39
+ export declare function harnessProcessExitError(harnessName: string, result: HarnessProcessResult): AutomationError;
@@ -0,0 +1,359 @@
1
+ import { spawn, } from "node:child_process";
2
+ import { constants } from "node:fs";
3
+ import { access } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { StringDecoder } from "node:string_decoder";
6
+ import { throwIfAborted } from "./cancellation.js";
7
+ import { AutomationError } from "./errors.js";
8
+ export function createHarnessPreflight(options) {
9
+ const cache = new Map();
10
+ return async (executable, environment = process.env, processOptions = {}) => {
11
+ const resolvedExecutable = await resolveHarnessExecutable(executable, options.harnessName, environment);
12
+ const cachedExecutable = cache.get(resolvedExecutable);
13
+ if (cachedExecutable !== undefined) {
14
+ return cachedExecutable;
15
+ }
16
+ const inspection = await inspectResolvedHarness(resolvedExecutable, options, environment, processOptions);
17
+ cache.set(resolvedExecutable, inspection.executable);
18
+ return inspection.executable;
19
+ };
20
+ }
21
+ export async function inspectHarnessExecutable(executable, options, environment = process.env, processOptions = {}) {
22
+ const resolvedExecutable = await resolveHarnessExecutable(executable, options.harnessName, environment);
23
+ return inspectResolvedHarness(resolvedExecutable, options, environment, processOptions);
24
+ }
25
+ async function inspectResolvedHarness(resolvedExecutable, options, environment, processOptions) {
26
+ const versionProcess = await runHarnessProcess(resolvedExecutable, ["--version"], process.cwd(), options.harnessName, { environment, ...processOptions });
27
+ if (versionProcess.exitCode !== 0) {
28
+ throw new AutomationError("HARNESS_PROCESS_FAILED", `${options.harnessName} version check exited with status ${String(versionProcess.exitCode)}.`, { cause: harnessProcessFailureCause(versionProcess.stderr) });
29
+ }
30
+ const version = parseHarnessVersion(versionProcess.stdout, options.versionPattern);
31
+ if (version === undefined ||
32
+ !supportsMinimumVersion(version.parts, options.minimumVersion)) {
33
+ throw new AutomationError("HARNESS_VERSION_UNSUPPORTED", version === undefined
34
+ ? options.unsupportedVersionMessage
35
+ : `${options.unsupportedVersionMessage} Found ${version.display}.`);
36
+ }
37
+ return {
38
+ executable: resolvedExecutable,
39
+ version: version.display,
40
+ };
41
+ }
42
+ async function resolveHarnessExecutable(executable, harnessName, environment) {
43
+ const candidates = executable.includes(path.sep)
44
+ ? [path.resolve(executable)]
45
+ : (environment.PATH ?? "")
46
+ .split(path.delimiter)
47
+ .filter((entry) => entry.length > 0)
48
+ .map((entry) => path.join(entry, executable));
49
+ let resolutionCause;
50
+ for (const candidate of candidates) {
51
+ try {
52
+ await access(candidate, constants.X_OK);
53
+ return candidate;
54
+ }
55
+ catch (cause) {
56
+ resolutionCause = cause;
57
+ }
58
+ }
59
+ throw new AutomationError("HARNESS_NOT_FOUND", `Could not resolve the ${harnessName} executable: ${executable}`, { cause: resolutionCause });
60
+ }
61
+ export async function runHarnessProcess(executable, arguments_, workingDirectory, harnessName, options = {}) {
62
+ throwIfAborted(options.signal);
63
+ const lifecycle = createHarnessProcessLifecycle(executable, arguments_, workingDirectory, harnessName, "ignore", options);
64
+ const stdout = consumeStandardOutput(lifecycle.child.stdout, options.onStdoutLine);
65
+ try {
66
+ const [exitCode, stdoutValue, stderrValue] = await Promise.all([
67
+ lifecycle.completion,
68
+ stdout,
69
+ lifecycle.stderr,
70
+ ]);
71
+ lifecycle.throwIfInterrupted();
72
+ return { exitCode, stderr: stderrValue, stdout: stdoutValue };
73
+ }
74
+ catch (cause) {
75
+ lifecycle.terminate();
76
+ await Promise.allSettled([
77
+ lifecycle.completion,
78
+ stdout,
79
+ lifecycle.stderr,
80
+ ]);
81
+ lifecycle.forceTerminate();
82
+ await lifecycle.waitForTreeTerminations();
83
+ lifecycle.throwIfInterrupted();
84
+ throw cause;
85
+ }
86
+ finally {
87
+ lifecycle.dispose();
88
+ }
89
+ }
90
+ export async function runHarnessProtocolProcess(executable, arguments_, workingDirectory, harnessName, interact, options = {}) {
91
+ throwIfAborted(options.signal);
92
+ const lifecycle = createHarnessProcessLifecycle(executable, arguments_, workingDirectory, harnessName, "pipe", options);
93
+ const inputStream = lifecycle.child.stdin;
94
+ let interruptHandler;
95
+ lifecycle.setInterruptHandler(() => interruptHandler?.());
96
+ const interaction = Promise.resolve().then(() => interact({
97
+ input: inputStream,
98
+ output: lifecycle.child.stdout,
99
+ onInterrupt(handler) {
100
+ interruptHandler = handler;
101
+ },
102
+ }));
103
+ try {
104
+ const value = await interaction;
105
+ await closeWritableStream(inputStream);
106
+ const [exitCode, stderr] = await Promise.all([
107
+ lifecycle.completion,
108
+ lifecycle.stderr,
109
+ ]);
110
+ lifecycle.throwIfInterrupted();
111
+ return { exitCode, stderr, value };
112
+ }
113
+ catch (cause) {
114
+ lifecycle.terminate();
115
+ const [, processOutcome, stderrOutcome] = await Promise.allSettled([
116
+ interaction,
117
+ lifecycle.completion,
118
+ lifecycle.stderr,
119
+ ]);
120
+ lifecycle.forceTerminate();
121
+ await lifecycle.waitForTreeTerminations();
122
+ lifecycle.throwIfInterrupted();
123
+ if (processOutcome?.status === "rejected") {
124
+ throw processOutcome.reason;
125
+ }
126
+ if (processOutcome?.status === "fulfilled" &&
127
+ processOutcome.value !== null &&
128
+ processOutcome.value !== 0) {
129
+ throw harnessProcessExitError(harnessName, {
130
+ exitCode: processOutcome.value,
131
+ stderr: stderrOutcome?.status === "fulfilled" ? stderrOutcome.value : "",
132
+ stdout: "",
133
+ });
134
+ }
135
+ throw cause;
136
+ }
137
+ finally {
138
+ lifecycle.dispose();
139
+ }
140
+ }
141
+ function createHarnessProcessLifecycle(executable, arguments_, workingDirectory, harnessName, input, options) {
142
+ const detached = process.platform !== "win32";
143
+ const child = spawn(executable, arguments_, {
144
+ cwd: workingDirectory,
145
+ detached,
146
+ env: options.environment,
147
+ stdio: [input, "pipe", "pipe"],
148
+ });
149
+ let interruption;
150
+ let interruptHandler;
151
+ let forceTimer;
152
+ const treeTerminations = [];
153
+ const terminate = (signal) => {
154
+ if (child.pid === undefined) {
155
+ return;
156
+ }
157
+ if (process.platform === "win32") {
158
+ const taskkill = spawn("taskkill", [
159
+ "/PID",
160
+ String(child.pid),
161
+ "/T",
162
+ ...(signal === "SIGKILL" ? ["/F"] : []),
163
+ ], { stdio: "ignore", windowsHide: true });
164
+ treeTerminations.push(new Promise((resolve) => {
165
+ taskkill.once("error", () => {
166
+ if (child.exitCode === null && child.signalCode === null) {
167
+ child.kill(signal);
168
+ }
169
+ resolve();
170
+ });
171
+ taskkill.once("close", () => resolve());
172
+ }));
173
+ return;
174
+ }
175
+ try {
176
+ process.kill(-child.pid, signal);
177
+ }
178
+ catch (cause) {
179
+ if (!isMissingProcess(cause)) {
180
+ throw cause;
181
+ }
182
+ }
183
+ };
184
+ const scheduleForcedTermination = () => {
185
+ if (forceTimer !== undefined) {
186
+ return;
187
+ }
188
+ forceTimer = setTimeout(() => terminate("SIGKILL"), 5_000);
189
+ forceTimer.unref();
190
+ };
191
+ const interrupt = () => {
192
+ if (child.exitCode !== null || child.signalCode !== null) {
193
+ return;
194
+ }
195
+ interruption = options.signal?.reason;
196
+ scheduleForcedTermination();
197
+ if (interruptHandler === undefined) {
198
+ terminate("SIGTERM");
199
+ return;
200
+ }
201
+ void Promise.resolve().then(interruptHandler).then(() => terminate("SIGTERM"), () => terminate("SIGTERM"));
202
+ };
203
+ const force = () => {
204
+ if (child.exitCode === null && child.signalCode === null) {
205
+ interruption ??= options.forceSignal?.reason ?? options.signal?.reason;
206
+ terminate("SIGKILL");
207
+ }
208
+ };
209
+ options.signal?.addEventListener("abort", interrupt, { once: true });
210
+ options.forceSignal?.addEventListener("abort", force, { once: true });
211
+ if (options.signal?.aborted === true) {
212
+ interrupt();
213
+ }
214
+ if (options.forceSignal?.aborted === true) {
215
+ force();
216
+ }
217
+ const completion = new Promise((resolve, reject) => {
218
+ child.once("error", (cause) => {
219
+ reject(new AutomationError("HARNESS_PROCESS_FAILED", `Could not start the ${harnessName} process.`, { cause }));
220
+ });
221
+ child.once("close", resolve);
222
+ });
223
+ const stderr = consumeStream(child.stderr);
224
+ return {
225
+ child,
226
+ completion,
227
+ dispose() {
228
+ if (forceTimer !== undefined) {
229
+ clearTimeout(forceTimer);
230
+ }
231
+ options.signal?.removeEventListener("abort", interrupt);
232
+ options.forceSignal?.removeEventListener("abort", force);
233
+ },
234
+ forceTerminate() {
235
+ terminate("SIGKILL");
236
+ },
237
+ setInterruptHandler(handler) {
238
+ interruptHandler = handler;
239
+ },
240
+ terminate() {
241
+ if (child.exitCode === null && child.signalCode === null) {
242
+ terminate("SIGTERM");
243
+ scheduleForcedTermination();
244
+ }
245
+ },
246
+ throwIfInterrupted() {
247
+ if (interruption !== undefined) {
248
+ throw interruption;
249
+ }
250
+ },
251
+ async waitForTreeTerminations() {
252
+ await Promise.allSettled(treeTerminations);
253
+ },
254
+ stderr,
255
+ };
256
+ }
257
+ async function closeWritableStream(stream) {
258
+ if (stream.destroyed || stream.writableEnded) {
259
+ return;
260
+ }
261
+ await new Promise((resolve, reject) => {
262
+ stream.end((error) => {
263
+ if (error === undefined || error === null) {
264
+ resolve();
265
+ return;
266
+ }
267
+ reject(error);
268
+ });
269
+ });
270
+ }
271
+ function isMissingProcess(cause) {
272
+ return (typeof cause === "object" &&
273
+ cause !== null &&
274
+ cause.code === "ESRCH");
275
+ }
276
+ async function consumeStandardOutput(stream, onLine) {
277
+ const chunks = [];
278
+ const decoder = new StringDecoder("utf8");
279
+ let pending = "";
280
+ for await (const chunk of stream) {
281
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
282
+ chunks.push(buffer);
283
+ if (onLine === undefined) {
284
+ continue;
285
+ }
286
+ pending += decoder.write(buffer);
287
+ let newline = pending.indexOf("\n");
288
+ while (newline !== -1) {
289
+ const line = pending.slice(0, newline).replace(/\r$/u, "");
290
+ pending = pending.slice(newline + 1);
291
+ await onLine(line);
292
+ newline = pending.indexOf("\n");
293
+ }
294
+ }
295
+ pending += decoder.end();
296
+ if (onLine !== undefined && pending.length > 0) {
297
+ await onLine(pending.replace(/\r$/u, ""));
298
+ }
299
+ return Buffer.concat(chunks).toString("utf8");
300
+ }
301
+ async function consumeStream(stream) {
302
+ const chunks = [];
303
+ for await (const chunk of stream) {
304
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
305
+ }
306
+ return Buffer.concat(chunks).toString("utf8");
307
+ }
308
+ function supportsMinimumVersion(version, minimum) {
309
+ const numericVersion = version.slice(0, 3);
310
+ for (const [index, minimumPart] of minimum.entries()) {
311
+ const difference = (numericVersion[index] ?? 0) - minimumPart;
312
+ if (difference !== 0) {
313
+ return difference > 0;
314
+ }
315
+ }
316
+ return version[3] === undefined;
317
+ }
318
+ function parseHarnessVersion(output, pattern) {
319
+ const match = pattern.exec(output);
320
+ if (match === null ||
321
+ match[1] === undefined ||
322
+ match[2] === undefined ||
323
+ match[3] === undefined) {
324
+ return undefined;
325
+ }
326
+ const parts = [
327
+ Number(match[1]),
328
+ Number(match[2]),
329
+ Number(match[3]),
330
+ match[4],
331
+ ];
332
+ return {
333
+ display: `${String(parts[0])}.${String(parts[1])}.${String(parts[2])}${parts[3] === undefined ? "" : `-${parts[3]}`}`,
334
+ parts,
335
+ };
336
+ }
337
+ export function harnessProcessFailureCause(stderr) {
338
+ const diagnostic = stderr.trim();
339
+ return diagnostic.length === 0 ? undefined : new Error(diagnostic);
340
+ }
341
+ export function harnessProcessExitError(harnessName, result) {
342
+ const diagnostic = result.stderr;
343
+ const nativeCause = harnessProcessFailureCause(diagnostic);
344
+ if (isAuthenticationFailure(diagnostic)) {
345
+ return new AutomationError("HARNESS_NOT_AUTHENTICATED", `${harnessName} is not authenticated.`, { cause: nativeCause });
346
+ }
347
+ if (isModelEffortCapabilityFailure(diagnostic)) {
348
+ return new AutomationError("HARNESS_CAPABILITY_UNSUPPORTED", `${harnessName} does not support the requested model and effort combination.`, { cause: nativeCause });
349
+ }
350
+ return new AutomationError("HARNESS_PROCESS_FAILED", `${harnessName} exited with status ${String(result.exitCode)}.`, { cause: harnessProcessFailureCause(result.stderr) });
351
+ }
352
+ function isAuthenticationFailure(diagnostic) {
353
+ return /\b(?:authentication required|not authenticated|not logged in|unauthenticated|unauthorized)\b|\bplease (?:run )?(?:\/?login|log in)\b/iu.test(diagnostic);
354
+ }
355
+ function isModelEffortCapabilityFailure(diagnostic) {
356
+ return (/\bmodel\b/iu.test(diagnostic) &&
357
+ /\b(?:effort|reasoning|variant)\b/iu.test(diagnostic) &&
358
+ /\b(?:invalid|not support(?:ed)?|unavailable|unsupported)\b/iu.test(diagnostic));
359
+ }
@@ -0,0 +1,2 @@
1
+ import type { HarnessRunRequest } from "./harness.js";
2
+ export declare function promptWithOutputContract(request: Pick<HarnessRunRequest, "output" | "prompt">): string;
@@ -0,0 +1,14 @@
1
+ export function promptWithOutputContract(request) {
2
+ if (request.output === undefined) {
3
+ return request.prompt;
4
+ }
5
+ return [
6
+ request.prompt,
7
+ "",
8
+ "<automation_output_contract>",
9
+ "Return exactly one JSON value and no Markdown, commentary, or code fences.",
10
+ "The JSON value must satisfy this JSON Schema:",
11
+ JSON.stringify(request.output.toJSONSchema()),
12
+ "</automation_output_contract>",
13
+ ].join("\n");
14
+ }
@@ -0,0 +1,44 @@
1
+ import type { InferSchema, Schema } from "./schema.js";
2
+ declare const harnessBrand: unique symbol;
3
+ export interface Harness {
4
+ readonly [harnessBrand]: true;
5
+ }
6
+ export type AccessPolicy = "read" | "write-workspace" | "full";
7
+ export type ApprovalPolicy = "deny" | "auto";
8
+ export type EnvironmentOverrides = Readonly<Record<string, string | undefined>>;
9
+ export interface HarnessRunRequest {
10
+ readonly access: AccessPolicy;
11
+ readonly approval: ApprovalPolicy;
12
+ readonly emitNativeEvent: (event: unknown) => Promise<void>;
13
+ readonly environment: NodeJS.ProcessEnv;
14
+ readonly prompt: string;
15
+ readonly runId: string;
16
+ readonly signal: AbortSignal;
17
+ readonly forceSignal?: AbortSignal;
18
+ readonly nativeSessionId?: string;
19
+ readonly sessionReady?: (nativeSessionId: string) => void;
20
+ readonly start: () => Promise<void>;
21
+ readonly workingDirectory: string;
22
+ readonly output?: Schema<any, boolean>;
23
+ }
24
+ export declare function harnessProcessControls(request: HarnessRunRequest): {
25
+ readonly forceSignal?: AbortSignal;
26
+ readonly signal: AbortSignal;
27
+ };
28
+ export interface HarnessRunResult {
29
+ readonly nativeSessionId: string;
30
+ readonly value: unknown;
31
+ }
32
+ export declare function harnessName(harness: Harness | undefined): string | undefined;
33
+ interface HarnessAdapter {
34
+ readonly name: string;
35
+ run(request: HarnessRunRequest): Promise<HarnessRunResult>;
36
+ }
37
+ export declare function createHarness(adapter: HarnessAdapter): Harness;
38
+ export declare function runHarness<TOutputSchema extends Schema<any, boolean>>(harness: Harness | undefined, request: HarnessRunRequest & {
39
+ readonly output: TOutputSchema;
40
+ }): Promise<InferSchema<TOutputSchema>>;
41
+ export declare function runHarness(harness: Harness | undefined, request: HarnessRunRequest & {
42
+ readonly output?: undefined;
43
+ }): Promise<string>;
44
+ export {};
@@ -0,0 +1,68 @@
1
+ import { AutomationError, contextualizeAutomationError, } from "./errors.js";
2
+ const harnessBrand = Symbol.for("@nyxa/automation/harness");
3
+ const harnessAdapter = Symbol.for("@nyxa/automation/harness-adapter");
4
+ const headlessRunInstruction = [
5
+ "<automation_headless_run>",
6
+ "This Run is headless. Do not ask the user questions or invoke tools that request interactive input.",
7
+ "If more information is required, express that need only in the final response and, when one is declared, according to the Output Contract.",
8
+ "</automation_headless_run>",
9
+ ].join("\n");
10
+ export function harnessProcessControls(request) {
11
+ return {
12
+ ...(request.forceSignal === undefined
13
+ ? {}
14
+ : { forceSignal: request.forceSignal }),
15
+ signal: request.signal,
16
+ };
17
+ }
18
+ export function harnessName(harness) {
19
+ return isInternalHarness(harness) ? harness[harnessAdapter].name : undefined;
20
+ }
21
+ export function createHarness(adapter) {
22
+ return Object.freeze({
23
+ [harnessBrand]: true,
24
+ [harnessAdapter]: adapter,
25
+ });
26
+ }
27
+ export function runHarness(harness, request) {
28
+ if (!isInternalHarness(harness)) {
29
+ throw new AutomationError("HARNESS_REQUIRED", "A Run requires an explicit Harness or a Workflow default Harness.", { runId: request.runId });
30
+ }
31
+ return runAndValidate(harness[harnessAdapter], request);
32
+ }
33
+ async function runAndValidate(adapter, request) {
34
+ let result;
35
+ try {
36
+ result = await adapter.run({
37
+ ...request,
38
+ prompt: `${request.prompt}\n\n${headlessRunInstruction}`,
39
+ });
40
+ }
41
+ catch (cause) {
42
+ throw contextualizeAutomationError(cause, "HARNESS_PROCESS_FAILED", `The ${adapter.name} Run failed unexpectedly.`, {
43
+ harness: adapter.name,
44
+ runId: request.runId,
45
+ });
46
+ }
47
+ request.sessionReady?.(result.nativeSessionId);
48
+ if (request.output === undefined) {
49
+ return result.value;
50
+ }
51
+ const parsedOutput = request.output.safeParse(result.value);
52
+ if (!parsedOutput.success) {
53
+ throw new AutomationError("RUN_OUTPUT_INVALID", `The ${adapter.name} Run output does not satisfy its contract.`, {
54
+ cause: parsedOutput.error,
55
+ harness: adapter.name,
56
+ runId: request.runId,
57
+ });
58
+ }
59
+ return parsedOutput.data;
60
+ }
61
+ function isInternalHarness(value) {
62
+ if (typeof value !== "object" || value === null) {
63
+ return false;
64
+ }
65
+ const candidate = value;
66
+ return (candidate[harnessBrand] === true &&
67
+ typeof candidate[harnessAdapter]?.run === "function");
68
+ }
@@ -0,0 +1,10 @@
1
+ export { codex, type CodexEffort, type CodexOptions, } from "./codex.js";
2
+ export { claudeCode, type ClaudeCodeEffort, type ClaudeCodeOptions, } from "./claude-code.js";
3
+ export { kimiCode, type KimiCodeOptions } from "./kimi-code.js";
4
+ export { opencode, type OpenCodeOptions } from "./opencode.js";
5
+ export { AutomationError, isAutomationError, type AutomationErrorCode, type AutomationErrorDetails, type AutomationErrorOptions, } from "./errors.js";
6
+ export { type AutomationEvent, type AutomationEventBase, type AutomationEventObserver, type RunCompletedEvent, type RunEventBase, type RunFailedEvent, type RunNativeEvent, type RunStartedEvent, type WorkflowCompletedEvent, type WorkflowFailedEvent, type WorkflowStartedEvent, } from "./events.js";
7
+ export { type InferSchema, type JSONSchema, type SafeParseResult, type Schema, type SchemaIssueCode, type SchemaIssue, type SchemaPathSegment, SchemaValidationError, schema, } from "./schema.js";
8
+ export { type AccessPolicy, type ApprovalPolicy, type EnvironmentOverrides, type Harness, } from "./harness.js";
9
+ export { defineWorkflow, executeWorkflow, type ExecuteWorkflowOptions, type ExecuteWorkflowWithoutInputOptions, type JsonPrimitive, type JsonValue, type RunOptions, type Session, type SessionOptions, type SessionRunOptions, type StructuredSessionRunOptions, type StructuredRunOptions, type WorkflowDefinition, type WorkflowDefinitionOptions, type WorkflowContext, type WorkflowResult, } from "./workflow.js";
10
+ export type { RunJournalMode } from "./run-journal.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { codex, } from "./codex.js";
2
+ export { claudeCode, } from "./claude-code.js";
3
+ export { kimiCode } from "./kimi-code.js";
4
+ export { opencode } from "./opencode.js";
5
+ export { AutomationError, isAutomationError, } from "./errors.js";
6
+ export {} from "./events.js";
7
+ export { SchemaValidationError, schema, } from "./schema.js";
8
+ export {} from "./harness.js";
9
+ export { defineWorkflow, executeWorkflow, } from "./workflow.js";