@oneharness/sdk 0.3.24 → 0.4.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.
@@ -6,8 +6,11 @@ import type { HistoryList, HistorySessionSummary } from "./history-list.js";
6
6
  import type { HistoryListOptions } from "./history-list-options.js";
7
7
  import type { HistoryLookup, HistoryLookupByLast, HistoryLookupBySession } from "./history-lookup.js";
8
8
  import type { HistoryRecords } from "./history-records.js";
9
- import type { PermissionMode, RunOptions } from "./options.js";
9
+ import type { HistoryStreamEnvelope } from "./history-stream-envelope.js";
10
+ import type { HistoryWatchOptions } from "./history-watch-options.js";
11
+ import type { HistoryLabels, PermissionMode, RunOptions } from "./options.js";
10
12
  import type { HarnessInfo, ListReport, ModeInfo } from "./registry.js";
13
+ import type { RunStreamEnvelope } from "./run-stream-envelope.js";
11
14
  export type BatchStrategy = BatchReport["strategy"];
12
15
  export type ModeHeadless = ModeInfo["headless"];
13
16
  export type SessionPhase = SessionReport["phase"];
@@ -20,6 +23,7 @@ export declare const FailureKindSchema: z.ZodType<FailureKind>;
20
23
  export declare const FallThroughSchema: z.ZodType<FallThrough>;
21
24
  export declare const FallbackReportSchema: z.ZodType<FallbackReport>;
22
25
  export declare const HarnessInfoSchema: z.ZodType<HarnessInfo>;
26
+ export declare const HistoryLabelsSchema: z.ZodType<HistoryLabels>;
23
27
  export declare const HistoryListSchema: z.ZodType<HistoryList>;
24
28
  export declare const HistoryListOptionsSchema: z.ZodType<HistoryListOptions>;
25
29
  export declare const HistoryLookupSchema: z.ZodType<HistoryLookup>;
@@ -28,6 +32,8 @@ export declare const HistoryLookupBySessionSchema: z.ZodType<HistoryLookupBySess
28
32
  export declare const HistoryRecordSchema: z.ZodType<HistoryRecord>;
29
33
  export declare const HistoryRecordsSchema: z.ZodType<HistoryRecords>;
30
34
  export declare const HistorySessionSummarySchema: z.ZodType<HistorySessionSummary>;
35
+ export declare const HistoryStreamEnvelopeSchema: z.ZodType<HistoryStreamEnvelope>;
36
+ export declare const HistoryWatchOptionsSchema: z.ZodType<HistoryWatchOptions>;
31
37
  export declare const ListReportSchema: z.ZodType<ListReport>;
32
38
  export declare const ModeHeadlessSchema: z.ZodType<ModeHeadless>;
33
39
  export declare const ModeInfoSchema: z.ZodType<ModeInfo>;
@@ -36,6 +42,7 @@ export declare const PermissionModeSchema: z.ZodType<PermissionMode>;
36
42
  export declare const RunOptionsSchema: z.ZodType<RunOptions>;
37
43
  export declare const RunReportSchema: z.ZodType<RunReport>;
38
44
  export declare const RunResultSchema: z.ZodType<RunResult>;
45
+ export declare const RunStreamEnvelopeSchema: z.ZodType<RunStreamEnvelope>;
39
46
  export declare const SessionPhaseSchema: z.ZodType<SessionPhase>;
40
47
  export declare const SessionReportSchema: z.ZodType<SessionReport>;
41
48
  export declare const StatusSchema: z.ZodType<Status>;
@@ -70,6 +70,19 @@ export const HarnessInfoSchema = z.looseObject({
70
70
  supports_system_file: z.boolean().refine((value) => value !== undefined, { message: "Required" }),
71
71
  sync_file: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
72
72
  });
73
+ export const HistoryLabelsSchema = z.record(z
74
+ .string()
75
+ .regex(new RegExp("^[A-Za-z0-9]", "u"))
76
+ .refine((value) => [...value].length <= 64, { message: "Too long: expected at most 64 characters" })
77
+ .refine((value) => !new RegExp("[^A-Za-z0-9._-]", "u").test(value), {
78
+ message: "Invalid string: must not contain [^A-Za-z0-9._-]",
79
+ }), z
80
+ .string()
81
+ .min(1)
82
+ .refine((value) => [...value].length <= 256, { message: "Too long: expected at most 256 characters" })
83
+ .refine((value) => !new RegExp("[\\u0000-\\u001f\\u007f-\\u009f]", "u").test(value), {
84
+ message: "Invalid string: must not contain [\\u0000-\\u001f\\u007f-\\u009f]",
85
+ }));
73
86
  export const HistoryListSchema = z.array(z.lazy(() => HistorySessionSummarySchema));
74
87
  export const HistoryListOptionsSchema = z.strictObject({
75
88
  allProjects: z.boolean().optional(),
@@ -107,6 +120,13 @@ export const HistoryRecordSchema = z.looseObject({
107
120
  .union([z.lazy(() => FailureKindSchema), z.null()])
108
121
  .refine((value) => value !== undefined, { message: "Required" }),
109
122
  harness: z.string().refine((value) => value !== undefined, { message: "Required" }),
123
+ history_id: z
124
+ .string()
125
+ .min(36)
126
+ .regex(new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", "u"))
127
+ .refine((value) => [...value].length <= 36, { message: "Too long: expected at most 36 characters" })
128
+ .refine((value) => value !== undefined, { message: "Required" }),
129
+ labels: z.lazy(() => HistoryLabelsSchema).optional(),
110
130
  model: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
111
131
  name: z.string().refine((value) => value !== undefined, { message: "Required" }),
112
132
  permission_mode: z.lazy(() => PermissionModeSchema).refine((value) => value !== undefined, { message: "Required" }),
@@ -125,6 +145,7 @@ export const HistoryRecordsSchema = z.array(z.lazy(() => HistoryRecordSchema));
125
145
  export const HistorySessionSummarySchema = z.looseObject({
126
146
  harnesses: z.array(z.string()).refine((value) => value !== undefined, { message: "Required" }),
127
147
  id: z.string().refine((value) => value !== undefined, { message: "Required" }),
148
+ labels: z.lazy(() => HistoryLabelsSchema).optional(),
128
149
  name: z.string().refine((value) => value !== undefined, { message: "Required" }),
129
150
  path: z.string().refine((value) => value !== undefined, { message: "Required" }),
130
151
  project: z.string().refine((value) => value !== undefined, { message: "Required" }),
@@ -134,6 +155,22 @@ export const HistorySessionSummarySchema = z.looseObject({
134
155
  .refine((value) => value !== undefined, { message: "Required" }),
135
156
  started: z.string().refine((value) => value !== undefined, { message: "Required" }),
136
157
  });
158
+ export const HistoryStreamEnvelopeSchema = z.looseObject({
159
+ record: z.lazy(() => HistoryRecordSchema).refine((value) => value !== undefined, { message: "Required" }),
160
+ type: z.literal("record").refine((value) => value !== undefined, { message: "Required" }),
161
+ });
162
+ export const HistoryWatchOptionsSchema = z.strictObject({
163
+ after: z
164
+ .string()
165
+ .min(36)
166
+ .regex(new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", "u"))
167
+ .refine((value) => [...value].length <= 36, { message: "Too long: expected at most 36 characters" })
168
+ .optional(),
169
+ allProjects: z.boolean().optional(),
170
+ historyDir: z.string().optional(),
171
+ labels: z.lazy(() => HistoryLabelsSchema).optional(),
172
+ project: z.string().optional(),
173
+ });
137
174
  export const ListReportSchema = z.looseObject({
138
175
  harnesses: z.array(z.lazy(() => HarnessInfoSchema)).refine((value) => value !== undefined, { message: "Required" }),
139
176
  schema_version: z.string().refine((value) => value !== undefined, { message: "Required" }),
@@ -165,6 +202,7 @@ export const RunOptionsSchema = z.strictObject({
165
202
  harnesses: z.array(z.string()).optional(),
166
203
  history: z.boolean().optional(),
167
204
  historyDir: z.string().optional(),
205
+ historyLabels: z.lazy(() => HistoryLabelsSchema).optional(),
168
206
  historyName: z.string().optional(),
169
207
  mode: z.lazy(() => PermissionModeSchema).optional(),
170
208
  models: z.array(z.string()).optional(),
@@ -240,6 +278,16 @@ export const RunResultSchema = z.looseObject({
240
278
  usage: z.lazy(() => UsageSchema).refine((value) => value !== undefined, { message: "Required" }),
241
279
  usage_source: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
242
280
  });
281
+ export const RunStreamEnvelopeSchema = z.union([
282
+ z.looseObject({
283
+ event: z.lazy(() => ActionEventSchema).refine((value) => value !== undefined, { message: "Required" }),
284
+ type: z.literal("event").refine((value) => value !== undefined, { message: "Required" }),
285
+ }),
286
+ z.looseObject({
287
+ report: z.lazy(() => RunReportSchema).refine((value) => value !== undefined, { message: "Required" }),
288
+ type: z.literal("result").refine((value) => value !== undefined, { message: "Required" }),
289
+ }),
290
+ ]);
243
291
  export const SessionPhaseSchema = z.union([z.literal("create"), z.literal("continue")]);
244
292
  export const SessionReportSchema = z.looseObject({
245
293
  name: z.string().refine((value) => value !== undefined, { message: "Required" }),
package/dist/index.d.ts CHANGED
@@ -4,8 +4,11 @@ import type { HistoryRecord } from "./generated/history.js";
4
4
  import type { HistorySessionSummary } from "./generated/history-list.js";
5
5
  import type { HistoryListOptions } from "./generated/history-list-options.js";
6
6
  import type { HistoryLookup } from "./generated/history-lookup.js";
7
+ import type { HistoryStreamEnvelope } from "./generated/history-stream-envelope.js";
8
+ import type { HistoryWatchOptions } from "./generated/history-watch-options.js";
7
9
  import type { RunOptions } from "./generated/options.js";
8
10
  import type { HarnessInfo } from "./generated/registry.js";
11
+ import type { RunStreamEnvelope } from "./generated/run-stream-envelope.js";
9
12
  export type { ActionEvent, BatchReport, FailureKind, FallbackReport, FallThrough, OutputFormat, RunReport, RunResult, SessionReport, Status, Usage, } from "./generated/contracts.js";
10
13
  export type { DetectInfo, DetectReport } from "./generated/detection.js";
11
14
  export type { HistoryRecord } from "./generated/history.js";
@@ -13,7 +16,10 @@ export type { HistoryList, HistorySessionSummary, } from "./generated/history-li
13
16
  export type { HistoryListOptions } from "./generated/history-list-options.js";
14
17
  export type { HistoryLookup, HistoryLookupByLast, HistoryLookupBySession, } from "./generated/history-lookup.js";
15
18
  export type { HistoryRecords } from "./generated/history-records.js";
19
+ export type { HistoryStreamEnvelope } from "./generated/history-stream-envelope.js";
20
+ export type { HistoryWatchOptions } from "./generated/history-watch-options.js";
16
21
  export type { PermissionMode, RunOptions } from "./generated/options.js";
22
+ export type { RunStreamEnvelope } from "./generated/run-stream-envelope.js";
17
23
  export type Detection = DetectInfo;
18
24
  export type { HarnessInfo, ListReport, ModeInfo, } from "./generated/registry.js";
19
25
  export * from "./generated/zod.js";
@@ -22,12 +28,24 @@ export type OneHarnessOptions = {
22
28
  executableArgs?: readonly string[];
23
29
  env?: Readonly<Record<string, string>>;
24
30
  };
31
+ /** A non-zero exit from the oneharness subprocess. */
32
+ export declare class OneHarnessProcessError extends Error {
33
+ readonly exitCode: number | null;
34
+ readonly stderr: string;
35
+ constructor(message: string, exitCode: number | null, stderr: string);
36
+ }
37
+ /** A history lookup or watch cursor that the CLI could not resolve. */
38
+ export declare class HistoryNotFoundError extends OneHarnessProcessError {
39
+ constructor(message: string, exitCode: number | null, stderr: string);
40
+ }
25
41
  export declare class OneHarness {
26
42
  private readonly options;
27
43
  constructor(options?: OneHarnessOptions);
28
44
  run(options: RunOptions): Promise<RunReport>;
45
+ runStream(options: RunOptions): AsyncGenerator<RunStreamEnvelope>;
29
46
  list(): Promise<HarnessInfo[]>;
30
47
  detect(harnesses?: readonly string[]): Promise<Detection[]>;
31
48
  history(lookup: HistoryLookup): Promise<HistoryRecord[]>;
32
49
  historyList(options?: HistoryListOptions): Promise<HistorySessionSummary[]>;
50
+ historyWatch(options?: HistoryWatchOptions): AsyncGenerator<HistoryStreamEnvelope>;
33
51
  }
package/dist/index.js CHANGED
@@ -1,7 +1,26 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createRequire } from "node:module";
3
- import { DetectReportSchema, HistoryListOptionsSchema, HistoryListSchema, HistoryLookupSchema, HistoryRecordsSchema, ListReportSchema, RunOptionsSchema, RunReportSchema, } from "./generated/zod.js";
3
+ import { createInterface } from "node:readline";
4
+ import { DetectReportSchema, HistoryListOptionsSchema, HistoryListSchema, HistoryLookupSchema, HistoryRecordsSchema, HistoryStreamEnvelopeSchema, HistoryWatchOptionsSchema, ListReportSchema, RunOptionsSchema, RunReportSchema, RunStreamEnvelopeSchema, } from "./generated/zod.js";
4
5
  export * from "./generated/zod.js";
6
+ /** A non-zero exit from the oneharness subprocess. */
7
+ export class OneHarnessProcessError extends Error {
8
+ exitCode;
9
+ stderr;
10
+ constructor(message, exitCode, stderr) {
11
+ super(message);
12
+ this.exitCode = exitCode;
13
+ this.stderr = stderr;
14
+ this.name = "OneHarnessProcessError";
15
+ }
16
+ }
17
+ /** A history lookup or watch cursor that the CLI could not resolve. */
18
+ export class HistoryNotFoundError extends OneHarnessProcessError {
19
+ constructor(message, exitCode, stderr) {
20
+ super(message, exitCode, stderr);
21
+ this.name = "HistoryNotFoundError";
22
+ }
23
+ }
5
24
  function parseContract(schema, value, label) {
6
25
  const parsed = schema.safeParse(value);
7
26
  if (parsed.success)
@@ -44,22 +63,126 @@ async function invokeWith(options, args, cwd, acceptJsonOnNonzero = false) {
44
63
  child.on("error", reject);
45
64
  child.on("close", (code) => {
46
65
  if (code !== 0 && !acceptJsonOnNonzero)
47
- return reject(new Error(`oneharness exited ${code}: ${stderr.trim()}`));
66
+ return reject(processError(code, stderr));
48
67
  try {
49
68
  resolve(JSON.parse(stdout));
50
69
  }
51
70
  catch (error) {
52
71
  if (code !== 0)
53
- return reject(new Error(`oneharness exited ${code}: ${stderr.trim()}`));
72
+ return reject(processError(code, stderr));
54
73
  reject(new Error(`oneharness returned invalid JSON: ${String(error)}`));
55
74
  }
56
75
  });
57
76
  });
58
77
  }
78
+ function processError(code, stderr) {
79
+ return new OneHarnessProcessError(`oneharness exited ${code}: ${stderr.trim()}`, code, stderr);
80
+ }
81
+ function historyNotFound(error) {
82
+ return (error instanceof OneHarnessProcessError &&
83
+ (error.exitCode === 1 || error.stderr.includes("was not found")));
84
+ }
85
+ function asHistoryNotFound(error) {
86
+ return new HistoryNotFoundError(error.message, error.exitCode, error.stderr);
87
+ }
88
+ async function* invokeStreamWith(options, args, schema, label, cwd, historyStream = false) {
89
+ const bin = executable(options);
90
+ const child = spawn(bin.command, [...bin.prefix, ...args], {
91
+ cwd,
92
+ env: { ...process.env, ...options.env },
93
+ windowsHide: true,
94
+ });
95
+ let stderr = "";
96
+ let spawnError;
97
+ let closed = false;
98
+ child.stderr
99
+ .setEncoding("utf8")
100
+ .on("data", (chunk) => (stderr += chunk));
101
+ const completion = new Promise((resolve) => {
102
+ child.once("error", (error) => {
103
+ spawnError = error;
104
+ });
105
+ child.once("close", (code) => {
106
+ closed = true;
107
+ resolve(code);
108
+ });
109
+ });
110
+ const lines = createInterface({
111
+ input: child.stdout,
112
+ crlfDelay: Number.POSITIVE_INFINITY,
113
+ });
114
+ try {
115
+ for await (const line of lines) {
116
+ if (line.trim() === "")
117
+ continue;
118
+ let value;
119
+ try {
120
+ value = JSON.parse(line);
121
+ }
122
+ catch (error) {
123
+ throw new Error(`${label}: invalid JSON: ${String(error)}`);
124
+ }
125
+ yield parseContract(schema, value, label);
126
+ }
127
+ const code = await completion;
128
+ if (spawnError)
129
+ throw spawnError;
130
+ if (code !== 0) {
131
+ const error = processError(code, stderr);
132
+ if (historyStream && historyNotFound(error))
133
+ throw asHistoryNotFound(error);
134
+ throw error;
135
+ }
136
+ }
137
+ finally {
138
+ lines.close();
139
+ child.stdout.destroy();
140
+ if (!closed) {
141
+ child.kill();
142
+ await completion;
143
+ }
144
+ }
145
+ }
59
146
  function pushMany(args, flag, values) {
60
147
  for (const value of values ?? [])
61
148
  args.push(flag, value);
62
149
  }
150
+ function runArguments(input, stream) {
151
+ const args = ["run", "--prompt", input.prompt, "--compact"];
152
+ pushMany(args, "--harness", input.harnesses);
153
+ pushMany(args, "--model", input.models);
154
+ if (input.system !== undefined)
155
+ args.push("--system", input.system);
156
+ if (input.reasoning !== undefined)
157
+ args.push("--reasoning", input.reasoning);
158
+ if (input.resume !== undefined)
159
+ args.push("--resume", input.resume);
160
+ if (input.session !== undefined)
161
+ args.push("--session", input.session);
162
+ if (input.fork)
163
+ args.push("--fork");
164
+ if (input.mode)
165
+ args.push("--mode", input.mode);
166
+ if (input.timeoutSeconds !== undefined)
167
+ args.push("--timeout", String(input.timeoutSeconds));
168
+ if (input.events)
169
+ args.push("--events");
170
+ if (stream)
171
+ args.push("--stream");
172
+ if (input.history)
173
+ args.push("--history");
174
+ if (input.historyName !== undefined)
175
+ args.push("--history-name", input.historyName);
176
+ if (input.historyDir !== undefined)
177
+ args.push("--history-dir", input.historyDir);
178
+ for (const [key, value] of Object.entries(input.historyLabels ?? {}))
179
+ args.push("--history-label", `${key}=${value}`);
180
+ for (const [key, value] of Object.entries(input.env ?? {}))
181
+ args.push("--env", `${key}=${value}`);
182
+ for (const [key, value] of Object.entries(input.bins ?? {}))
183
+ args.push("--bin", `${key}=${value}`);
184
+ return args;
185
+ }
63
186
  export class OneHarness {
64
187
  options;
65
188
  constructor(options = {}) {
@@ -67,38 +190,14 @@ export class OneHarness {
67
190
  }
68
191
  async run(options) {
69
192
  const input = parseContract(RunOptionsSchema, options, "invalid oneharness run options");
70
- const args = ["run", "--prompt", input.prompt, "--compact"];
71
- pushMany(args, "--harness", input.harnesses);
72
- pushMany(args, "--model", input.models);
73
- if (input.system !== undefined)
74
- args.push("--system", input.system);
75
- if (input.reasoning !== undefined)
76
- args.push("--reasoning", input.reasoning);
77
- if (input.resume !== undefined)
78
- args.push("--resume", input.resume);
79
- if (input.session !== undefined)
80
- args.push("--session", input.session);
81
- if (input.fork)
82
- args.push("--fork");
83
- if (input.mode)
84
- args.push("--mode", input.mode);
85
- if (input.timeoutSeconds !== undefined)
86
- args.push("--timeout", String(input.timeoutSeconds));
87
- if (input.events)
88
- args.push("--events");
89
- if (input.history)
90
- args.push("--history");
91
- if (input.historyName !== undefined)
92
- args.push("--history-name", input.historyName);
93
- if (input.historyDir !== undefined)
94
- args.push("--history-dir", input.historyDir);
95
- for (const [key, value] of Object.entries(input.env ?? {}))
96
- args.push("--env", `${key}=${value}`);
97
- for (const [key, value] of Object.entries(input.bins ?? {}))
98
- args.push("--bin", `${key}=${value}`);
193
+ const args = runArguments(input, false);
99
194
  const value = await invokeWith(this.options, args, input.cwd, true);
100
195
  return parseContract(RunReportSchema, value, "invalid oneharness run contract");
101
196
  }
197
+ runStream(options) {
198
+ const input = parseContract(RunOptionsSchema, options, "invalid oneharness run options");
199
+ return invokeStreamWith(this.options, runArguments(input, true), RunStreamEnvelopeSchema, "invalid oneharness run stream contract", input.cwd);
200
+ }
102
201
  async list() {
103
202
  const value = await invokeWith(this.options, ["list", "--compact"]);
104
203
  return parseContract(ListReportSchema, value, "invalid oneharness list contract").harnesses;
@@ -128,7 +227,15 @@ export class OneHarness {
128
227
  args.push("--all-projects");
129
228
  if (input.historyDir)
130
229
  args.push("--history-dir", input.historyDir);
131
- const value = await invokeWith(this.options, args);
230
+ let value;
231
+ try {
232
+ value = await invokeWith(this.options, args);
233
+ }
234
+ catch (error) {
235
+ if (historyNotFound(error))
236
+ throw asHistoryNotFound(error);
237
+ throw error;
238
+ }
132
239
  return parseContract(HistoryRecordsSchema, value, "invalid history contract");
133
240
  }
134
241
  async historyList(options = {}) {
@@ -143,4 +250,19 @@ export class OneHarness {
143
250
  const value = await invokeWith(this.options, args);
144
251
  return parseContract(HistoryListSchema, value, "invalid history list contract");
145
252
  }
253
+ historyWatch(options = {}) {
254
+ const input = parseContract(HistoryWatchOptionsSchema, options, "invalid oneharness history watch options");
255
+ const args = ["history", "watch", "--format", "jsonl"];
256
+ if (input.after)
257
+ args.push("--after", input.after);
258
+ for (const [key, value] of Object.entries(input.labels ?? {}))
259
+ args.push("--label", `${key}=${value}`);
260
+ if (input.project)
261
+ args.push("--project", input.project);
262
+ if (input.allProjects)
263
+ args.push("--all-projects");
264
+ if (input.historyDir)
265
+ args.push("--history-dir", input.historyDir);
266
+ return invokeStreamWith(this.options, args, HistoryStreamEnvelopeSchema, "invalid oneharness history watch contract", undefined, true);
267
+ }
146
268
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneharness/sdk",
3
- "version": "0.3.24",
3
+ "version": "0.4.0",
4
4
  "description": "Typed Node.js SDK for oneharness",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "node": ">=20"
20
20
  },
21
21
  "dependencies": {
22
- "oneharness-cli": "0.3.24",
22
+ "oneharness-cli": "0.4.0",
23
23
  "zod": "^4.4.3"
24
24
  }
25
25
  }