@oneharness/sdk 0.6.9 → 0.6.10

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.
@@ -1,3 +1,46 @@
1
+ /**
2
+ * One control request the run handled, recorded in the report so a consumer
3
+ * can tell an interrupted turn from one that simply ended.
4
+ *
5
+ * A sum type discriminated by `outcome`, so a record can never say "served,
6
+ * and here is the refusal reason": the reason exists exactly when there was a
7
+ * refusal. Serialized flat — `{"outcome":"served","verb":…,"at":…}` /
8
+ * `{"outcome":"refused","verb":…,"at":…,"reason":…}`.
9
+ */
10
+ export type ControlEvent = {
11
+ /**
12
+ * When the request was handled.
13
+ */
14
+ at: string;
15
+ outcome: "served";
16
+ /**
17
+ * The verb requested.
18
+ */
19
+ verb: "interrupt";
20
+ [k: string]: unknown;
21
+ } | {
22
+ at: UtcInstant;
23
+ outcome: "refused";
24
+ reason: ControlReason;
25
+ verb: ControlVerb;
26
+ [k: string]: unknown;
27
+ };
28
+ /**
29
+ * An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.
30
+ */
31
+ export type UtcInstant = string;
32
+ /**
33
+ * Why a control request could not be served. Distinct reasons because a
34
+ * supervisor reacts differently to each: `unsupported` is permanent for the
35
+ * harness, `not_running` means the dispatch is gone, `no_active_turn` means
36
+ * the run is alive but between turns.
37
+ */
38
+ export type ControlReason = "unsupported" | "no_active_turn" | "not_running";
39
+ /**
40
+ * The control verb a supervisor sends. Only `interrupt` exists today; the
41
+ * frame's `v` is what leaves room for `steer` later.
42
+ */
43
+ export type ControlVerb = "interrupt";
1
44
  export type ToolCallStatus = "completed" | "failed" | "timeout" | "interrupted";
2
45
  /**
3
46
  * How a normalized tool interval was obtained.
@@ -79,6 +122,14 @@ export interface RunReport {
79
122
  * project last); empty under `--no-config` or when none exist.
80
123
  */
81
124
  config_files: string[];
125
+ /**
126
+ * Out-of-band turn control for this run (`--control`), or `null` when none
127
+ * was requested — which is every ordinary run. It records the socket a
128
+ * separate `oneharness interrupt` process could address, the harness
129
+ * mechanism behind it, and each request served, so a consumer can tell an
130
+ * interrupted turn from one that simply ended.
131
+ */
132
+ control: ControlReport | null;
82
133
  dry_run: boolean;
83
134
  /**
84
135
  * Fallback-mode metadata when this run drove the selected harnesses in
@@ -189,6 +240,25 @@ export interface BatchReport {
189
240
  strategy: "speed" | "min-tokens";
190
241
  [k: string]: unknown;
191
242
  }
243
+ /**
244
+ * The run report's `control` block: where the socket lived, which mechanism
245
+ * backed it, and every request served over the run's lifetime.
246
+ */
247
+ export interface ControlReport {
248
+ /**
249
+ * Every control request served, in order.
250
+ */
251
+ interrupts: ControlEvent[];
252
+ /**
253
+ * The harness mechanism backing it.
254
+ */
255
+ mechanism: "claude-control-request" | "codex-app-server" | "opencode-http" | "acp-cancel" | "crush-http";
256
+ /**
257
+ * Absolute path of the socket this run listened on.
258
+ */
259
+ socket: string;
260
+ [k: string]: unknown;
261
+ }
192
262
  /**
193
263
  * Metadata for a fallback run (harnesses tried in priority order until one
194
264
  * runs). Present on [`RunReport::fallback`] only in that mode. The per-harness
@@ -1,3 +1,13 @@
1
+ /**
2
+ * How a harness accepts an out-of-band interrupt for an in-flight turn.
3
+ *
4
+ * Registry data on [`crate::domain::harness::HarnessSpec::control`], sourced
5
+ * from a live interrupt against the real CLI — never guessed. `None` there
6
+ * means `oneharness interrupt` is a loud usage error for the harness, never a
7
+ * silent no-op: a supervisor that is told "ok" while the turn keeps running is
8
+ * worse off than one told the lever does not exist.
9
+ */
10
+ export type ControlShape = "claude-control-request" | "codex-app-server" | "opencode-http" | "acp-cancel" | "crush-http";
1
11
  /**
2
12
  * The unified approval mode, from least to most autonomy. A harness may not
3
13
  * support every value (see [`crate::domain::harness::HarnessSpec::mode`]); the
@@ -19,6 +29,13 @@ export interface ListReport {
19
29
  [k: string]: unknown;
20
30
  }
21
31
  export interface HarnessInfo {
32
+ /**
33
+ * How `run --control` + `oneharness interrupt` abort an in-flight turn on
34
+ * this harness (the mechanism id), or `null` when it has no out-of-band
35
+ * turn control at all — in which case `--control` is a loud usage error for
36
+ * it, never a socket that reports success while the turn keeps running.
37
+ */
38
+ control: ControlShape | null;
22
39
  default_bin: string;
23
40
  display: string;
24
41
  /**
@@ -20,6 +20,49 @@ export type ToolCallStatus = "completed" | "failed" | "timeout" | "interrupted";
20
20
  * How a normalized tool interval was obtained.
21
21
  */
22
22
  export type TimingSource = "provider_measured" | "stdout_observed";
23
+ /**
24
+ * One control request the run handled, recorded in the report so a consumer
25
+ * can tell an interrupted turn from one that simply ended.
26
+ *
27
+ * A sum type discriminated by `outcome`, so a record can never say "served,
28
+ * and here is the refusal reason": the reason exists exactly when there was a
29
+ * refusal. Serialized flat — `{"outcome":"served","verb":…,"at":…}` /
30
+ * `{"outcome":"refused","verb":…,"at":…,"reason":…}`.
31
+ */
32
+ export type ControlEvent = {
33
+ /**
34
+ * When the request was handled.
35
+ */
36
+ at: string;
37
+ outcome: "served";
38
+ /**
39
+ * The verb requested.
40
+ */
41
+ verb: "interrupt";
42
+ [k: string]: unknown;
43
+ } | {
44
+ at: UtcInstant;
45
+ outcome: "refused";
46
+ reason: ControlReason;
47
+ verb: ControlVerb;
48
+ [k: string]: unknown;
49
+ };
50
+ /**
51
+ * An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.
52
+ */
53
+ export type UtcInstant = string;
54
+ /**
55
+ * Why a control request could not be served. Distinct reasons because a
56
+ * supervisor reacts differently to each: `unsupported` is permanent for the
57
+ * harness, `not_running` means the dispatch is gone, `no_active_turn` means
58
+ * the run is alive but between turns.
59
+ */
60
+ export type ControlReason = "unsupported" | "no_active_turn" | "not_running";
61
+ /**
62
+ * The control verb a supervisor sends. Only `interrupt` exists today; the
63
+ * frame's `v` is what leaves room for `steer` later.
64
+ */
65
+ export type ControlVerb = "interrupt";
23
66
  /**
24
67
  * The normalized, closed set of failure reasons oneharness can classify from a
25
68
  * harness's output. It is the single source for the `failure_kind` contract
@@ -153,6 +196,14 @@ export interface RunReport {
153
196
  * project last); empty under `--no-config` or when none exist.
154
197
  */
155
198
  config_files: string[];
199
+ /**
200
+ * Out-of-band turn control for this run (`--control`), or `null` when none
201
+ * was requested — which is every ordinary run. It records the socket a
202
+ * separate `oneharness interrupt` process could address, the harness
203
+ * mechanism behind it, and each request served, so a consumer can tell an
204
+ * interrupted turn from one that simply ended.
205
+ */
206
+ control: ControlReport | null;
156
207
  dry_run: boolean;
157
208
  /**
158
209
  * Fallback-mode metadata when this run drove the selected harnesses in
@@ -263,6 +314,25 @@ export interface BatchReport {
263
314
  strategy: "speed" | "min-tokens";
264
315
  [k: string]: unknown;
265
316
  }
317
+ /**
318
+ * The run report's `control` block: where the socket lived, which mechanism
319
+ * backed it, and every request served over the run's lifetime.
320
+ */
321
+ export interface ControlReport {
322
+ /**
323
+ * Every control request served, in order.
324
+ */
325
+ interrupts: ControlEvent[];
326
+ /**
327
+ * The harness mechanism backing it.
328
+ */
329
+ mechanism: "claude-control-request" | "codex-app-server" | "opencode-http" | "acp-cancel" | "crush-http";
330
+ /**
331
+ * Absolute path of the socket this run listened on.
332
+ */
333
+ socket: string;
334
+ [k: string]: unknown;
335
+ }
266
336
  /**
267
337
  * Metadata for a fallback run (harnesses tried in priority order until one
268
338
  * runs). Present on [`RunReport::fallback`] only in that mode. The per-harness
@@ -27,6 +27,17 @@
27
27
  "type": "string"
28
28
  }
29
29
  },
30
+ "control": {
31
+ "description": "Out-of-band turn control for this run (`--control`), or `null` when none\nwas requested — which is every ordinary run. It records the socket a\nseparate `oneharness interrupt` process could address, the harness\nmechanism behind it, and each request served, so a consumer can tell an\ninterrupted turn from one that simply ended.",
32
+ "anyOf": [
33
+ {
34
+ "$ref": "#/$defs/ControlReport"
35
+ },
36
+ {
37
+ "type": "null"
38
+ }
39
+ ]
40
+ },
30
41
  "dry_run": {
31
42
  "type": "boolean"
32
43
  },
@@ -150,9 +161,14 @@
150
161
  "spy_file",
151
162
  "history_file",
152
163
  "config_files",
164
+ "control",
153
165
  "results"
154
166
  ],
155
167
  "$defs": {
168
+ "AbsolutePath": {
169
+ "description": "A filesystem path that is known to be absolute.\n\nControl addresses are handed to a *different process* — the socket path in\nthe report is how a supervisor finds the run — so a relative path is not a\nsmaller version of the right answer, it is a different file depending on who\nreads it. The type is the difference between \"we documented it as absolute\"\nand \"it is\".",
170
+ "type": "string"
171
+ },
156
172
  "ActionEvent": {
157
173
  "description": "One normalized action a harness took, harness-agnostic so a single consumer\nassertion works across harnesses. Every field is always serialized (null when\nabsent) so the shape is stable, mirroring the `usage` contract.",
158
174
  "type": "object",
@@ -289,6 +305,142 @@
289
305
  }
290
306
  ]
291
307
  },
308
+ "ControlEvent": {
309
+ "description": "One control request the run handled, recorded in the report so a consumer\ncan tell an interrupted turn from one that simply ended.\n\nA sum type discriminated by `outcome`, so a record can never say \"served,\nand here is the refusal reason\": the reason exists exactly when there was a\nrefusal. Serialized flat — `{\"outcome\":\"served\",\"verb\":…,\"at\":…}` /\n`{\"outcome\":\"refused\",\"verb\":…,\"at\":…,\"reason\":…}`.",
310
+ "oneOf": [
311
+ {
312
+ "description": "The mechanism accepted the request.",
313
+ "type": "object",
314
+ "properties": {
315
+ "at": {
316
+ "description": "When the request was handled.",
317
+ "$ref": "#/$defs/UtcInstant"
318
+ },
319
+ "outcome": {
320
+ "type": "string",
321
+ "const": "served"
322
+ },
323
+ "verb": {
324
+ "description": "The verb requested.",
325
+ "$ref": "#/$defs/ControlVerb"
326
+ }
327
+ },
328
+ "required": [
329
+ "outcome",
330
+ "verb",
331
+ "at"
332
+ ]
333
+ },
334
+ {
335
+ "description": "The request was refused, with why.",
336
+ "type": "object",
337
+ "properties": {
338
+ "at": {
339
+ "$ref": "#/$defs/UtcInstant"
340
+ },
341
+ "outcome": {
342
+ "type": "string",
343
+ "const": "refused"
344
+ },
345
+ "reason": {
346
+ "$ref": "#/$defs/ControlReason"
347
+ },
348
+ "verb": {
349
+ "$ref": "#/$defs/ControlVerb"
350
+ }
351
+ },
352
+ "required": [
353
+ "outcome",
354
+ "verb",
355
+ "at",
356
+ "reason"
357
+ ]
358
+ }
359
+ ]
360
+ },
361
+ "ControlReason": {
362
+ "description": "Why a control request could not be served. Distinct reasons because a\nsupervisor reacts differently to each: `unsupported` is permanent for the\nharness, `not_running` means the dispatch is gone, `no_active_turn` means\nthe run is alive but between turns.",
363
+ "oneOf": [
364
+ {
365
+ "description": "The harness declares no [`ControlShape`] — the lever does not exist.",
366
+ "type": "string",
367
+ "const": "unsupported"
368
+ },
369
+ {
370
+ "description": "The run is alive but no turn is in flight right now.",
371
+ "type": "string",
372
+ "const": "no_active_turn"
373
+ },
374
+ {
375
+ "description": "No run is listening on the socket (never started, already exited, or a\nstale socket file left by an abnormally-terminated dispatch).",
376
+ "type": "string",
377
+ "const": "not_running"
378
+ }
379
+ ]
380
+ },
381
+ "ControlReport": {
382
+ "description": "The run report's `control` block: where the socket lived, which mechanism\nbacked it, and every request served over the run's lifetime.",
383
+ "type": "object",
384
+ "properties": {
385
+ "interrupts": {
386
+ "description": "Every control request served, in order.",
387
+ "type": "array",
388
+ "items": {
389
+ "$ref": "#/$defs/ControlEvent"
390
+ }
391
+ },
392
+ "mechanism": {
393
+ "description": "The harness mechanism backing it.",
394
+ "$ref": "#/$defs/ControlShape"
395
+ },
396
+ "socket": {
397
+ "description": "Absolute path of the socket this run listened on.",
398
+ "$ref": "#/$defs/AbsolutePath"
399
+ }
400
+ },
401
+ "required": [
402
+ "socket",
403
+ "mechanism",
404
+ "interrupts"
405
+ ]
406
+ },
407
+ "ControlShape": {
408
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
409
+ "oneOf": [
410
+ {
411
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
412
+ "type": "string",
413
+ "const": "claude-control-request"
414
+ },
415
+ {
416
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
417
+ "type": "string",
418
+ "const": "codex-app-server"
419
+ },
420
+ {
421
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
422
+ "type": "string",
423
+ "const": "opencode-http"
424
+ },
425
+ {
426
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
427
+ "type": "string",
428
+ "const": "acp-cancel"
429
+ },
430
+ {
431
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
432
+ "type": "string",
433
+ "const": "crush-http"
434
+ }
435
+ ]
436
+ },
437
+ "ControlVerb": {
438
+ "description": "The control verb a supervisor sends. Only `interrupt` exists today; the\nframe's `v` is what leaves room for `steer` later.",
439
+ "type": "string",
440
+ "enum": [
441
+ "interrupt"
442
+ ]
443
+ },
292
444
  "ExecutionTelemetry": {
293
445
  "description": "Measured execution telemetry for one harness run: when the invocation ran,\nand — when the harness's transcript supports it — how its wall clock split\nbetween provider latency and tool work.\n\nCarried from the runner/parser to the history writer *and*, since report\nschema `0.5`, serialized on [`RunResult::telemetry`]. Exposing it there is\nwhat lets a consumer read the numbers off the run it just made instead of\nre-opening the history file the same run wrote.\n\nInternally tagged by `source`, so the variant is a value a consumer switches\non rather than a shape it has to sniff. Every variant states only what was\nactually measured — there is no variant meaning \"no telemetry\"; that is a\n`null` field.",
294
446
  "oneOf": [
@@ -907,6 +1059,10 @@
907
1059
  "cache_write_tokens",
908
1060
  "cost_usd"
909
1061
  ]
1062
+ },
1063
+ "UtcInstant": {
1064
+ "description": "An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.",
1065
+ "type": "string"
910
1066
  }
911
1067
  }
912
1068
  },
@@ -951,6 +1107,10 @@
951
1107
  }
952
1108
  ],
953
1109
  "$defs": {
1110
+ "AbsolutePath": {
1111
+ "description": "A filesystem path that is known to be absolute.\n\nControl addresses are handed to a *different process* — the socket path in\nthe report is how a supervisor finds the run — so a relative path is not a\nsmaller version of the right answer, it is a different file depending on who\nreads it. The type is the difference between \"we documented it as absolute\"\nand \"it is\".",
1112
+ "type": "string"
1113
+ },
954
1114
  "ActionEvent": {
955
1115
  "description": "One normalized action a harness took, harness-agnostic so a single consumer\nassertion works across harnesses. Every field is always serialized (null when\nabsent) so the shape is stable, mirroring the `usage` contract.",
956
1116
  "type": "object",
@@ -1087,6 +1247,142 @@
1087
1247
  }
1088
1248
  ]
1089
1249
  },
1250
+ "ControlEvent": {
1251
+ "description": "One control request the run handled, recorded in the report so a consumer\ncan tell an interrupted turn from one that simply ended.\n\nA sum type discriminated by `outcome`, so a record can never say \"served,\nand here is the refusal reason\": the reason exists exactly when there was a\nrefusal. Serialized flat — `{\"outcome\":\"served\",\"verb\":…,\"at\":…}` /\n`{\"outcome\":\"refused\",\"verb\":…,\"at\":…,\"reason\":…}`.",
1252
+ "oneOf": [
1253
+ {
1254
+ "description": "The mechanism accepted the request.",
1255
+ "type": "object",
1256
+ "properties": {
1257
+ "at": {
1258
+ "description": "When the request was handled.",
1259
+ "$ref": "#/$defs/UtcInstant"
1260
+ },
1261
+ "outcome": {
1262
+ "type": "string",
1263
+ "const": "served"
1264
+ },
1265
+ "verb": {
1266
+ "description": "The verb requested.",
1267
+ "$ref": "#/$defs/ControlVerb"
1268
+ }
1269
+ },
1270
+ "required": [
1271
+ "outcome",
1272
+ "verb",
1273
+ "at"
1274
+ ]
1275
+ },
1276
+ {
1277
+ "description": "The request was refused, with why.",
1278
+ "type": "object",
1279
+ "properties": {
1280
+ "at": {
1281
+ "$ref": "#/$defs/UtcInstant"
1282
+ },
1283
+ "outcome": {
1284
+ "type": "string",
1285
+ "const": "refused"
1286
+ },
1287
+ "reason": {
1288
+ "$ref": "#/$defs/ControlReason"
1289
+ },
1290
+ "verb": {
1291
+ "$ref": "#/$defs/ControlVerb"
1292
+ }
1293
+ },
1294
+ "required": [
1295
+ "outcome",
1296
+ "verb",
1297
+ "at",
1298
+ "reason"
1299
+ ]
1300
+ }
1301
+ ]
1302
+ },
1303
+ "ControlReason": {
1304
+ "description": "Why a control request could not be served. Distinct reasons because a\nsupervisor reacts differently to each: `unsupported` is permanent for the\nharness, `not_running` means the dispatch is gone, `no_active_turn` means\nthe run is alive but between turns.",
1305
+ "oneOf": [
1306
+ {
1307
+ "description": "The harness declares no [`ControlShape`] — the lever does not exist.",
1308
+ "type": "string",
1309
+ "const": "unsupported"
1310
+ },
1311
+ {
1312
+ "description": "The run is alive but no turn is in flight right now.",
1313
+ "type": "string",
1314
+ "const": "no_active_turn"
1315
+ },
1316
+ {
1317
+ "description": "No run is listening on the socket (never started, already exited, or a\nstale socket file left by an abnormally-terminated dispatch).",
1318
+ "type": "string",
1319
+ "const": "not_running"
1320
+ }
1321
+ ]
1322
+ },
1323
+ "ControlReport": {
1324
+ "description": "The run report's `control` block: where the socket lived, which mechanism\nbacked it, and every request served over the run's lifetime.",
1325
+ "type": "object",
1326
+ "properties": {
1327
+ "interrupts": {
1328
+ "description": "Every control request served, in order.",
1329
+ "type": "array",
1330
+ "items": {
1331
+ "$ref": "#/$defs/ControlEvent"
1332
+ }
1333
+ },
1334
+ "mechanism": {
1335
+ "description": "The harness mechanism backing it.",
1336
+ "$ref": "#/$defs/ControlShape"
1337
+ },
1338
+ "socket": {
1339
+ "description": "Absolute path of the socket this run listened on.",
1340
+ "$ref": "#/$defs/AbsolutePath"
1341
+ }
1342
+ },
1343
+ "required": [
1344
+ "socket",
1345
+ "mechanism",
1346
+ "interrupts"
1347
+ ]
1348
+ },
1349
+ "ControlShape": {
1350
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
1351
+ "oneOf": [
1352
+ {
1353
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
1354
+ "type": "string",
1355
+ "const": "claude-control-request"
1356
+ },
1357
+ {
1358
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
1359
+ "type": "string",
1360
+ "const": "codex-app-server"
1361
+ },
1362
+ {
1363
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
1364
+ "type": "string",
1365
+ "const": "opencode-http"
1366
+ },
1367
+ {
1368
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
1369
+ "type": "string",
1370
+ "const": "acp-cancel"
1371
+ },
1372
+ {
1373
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
1374
+ "type": "string",
1375
+ "const": "crush-http"
1376
+ }
1377
+ ]
1378
+ },
1379
+ "ControlVerb": {
1380
+ "description": "The control verb a supervisor sends. Only `interrupt` exists today; the\nframe's `v` is what leaves room for `steer` later.",
1381
+ "type": "string",
1382
+ "enum": [
1383
+ "interrupt"
1384
+ ]
1385
+ },
1090
1386
  "ExecutionTelemetry": {
1091
1387
  "description": "Measured execution telemetry for one harness run: when the invocation ran,\nand — when the harness's transcript supports it — how its wall clock split\nbetween provider latency and tool work.\n\nCarried from the runner/parser to the history writer *and*, since report\nschema `0.5`, serialized on [`RunResult::telemetry`]. Exposing it there is\nwhat lets a consumer read the numbers off the run it just made instead of\nre-opening the history file the same run wrote.\n\nInternally tagged by `source`, so the variant is a value a consumer switches\non rather than a shape it has to sniff. Every variant states only what was\nactually measured — there is no variant meaning \"no telemetry\"; that is a\n`null` field.",
1092
1388
  "oneOf": [
@@ -1338,6 +1634,17 @@
1338
1634
  "type": "string"
1339
1635
  }
1340
1636
  },
1637
+ "control": {
1638
+ "description": "Out-of-band turn control for this run (`--control`), or `null` when none\nwas requested — which is every ordinary run. It records the socket a\nseparate `oneharness interrupt` process could address, the harness\nmechanism behind it, and each request served, so a consumer can tell an\ninterrupted turn from one that simply ended.",
1639
+ "anyOf": [
1640
+ {
1641
+ "$ref": "#/$defs/ControlReport"
1642
+ },
1643
+ {
1644
+ "type": "null"
1645
+ }
1646
+ ]
1647
+ },
1341
1648
  "dry_run": {
1342
1649
  "type": "boolean"
1343
1650
  },
@@ -1461,6 +1768,7 @@
1461
1768
  "spy_file",
1462
1769
  "history_file",
1463
1770
  "config_files",
1771
+ "control",
1464
1772
  "results"
1465
1773
  ]
1466
1774
  },
@@ -1857,6 +2165,10 @@
1857
2165
  "cache_write_tokens",
1858
2166
  "cost_usd"
1859
2167
  ]
2168
+ },
2169
+ "UtcInstant": {
2170
+ "description": "An RFC 3339 timestamp in UTC (`Z`), in oneharness's canonical spelling.",
2171
+ "type": "string"
1860
2172
  }
1861
2173
  }
1862
2174
  },
@@ -11579,9 +11891,50 @@
11579
11891
  "harnesses"
11580
11892
  ],
11581
11893
  "$defs": {
11894
+ "ControlShape": {
11895
+ "description": "How a harness accepts an out-of-band interrupt for an in-flight turn.\n\nRegistry data on [`crate::domain::harness::HarnessSpec::control`], sourced\nfrom a live interrupt against the real CLI — never guessed. `None` there\nmeans `oneharness interrupt` is a loud usage error for the harness, never a\nsilent no-op: a supervisor that is told \"ok\" while the turn keeps running is\nworse off than one told the lever does not exist.",
11896
+ "oneOf": [
11897
+ {
11898
+ "description": "Claude Code: the control frame rides the run process's **own stdin**\n(`-p --input-format stream-json`), so no sidecar server is needed. A\n`{\"type\":\"control_request\",…,\"request\":{\"subtype\":\"interrupt\"}}` line\naborts the turn with a `result` document; the session survives and a\nlater turn continues it with full context. Verified live: a plain user\nmessage written mid-turn is *silently dropped*, so the control frame is\nthe only mechanism that works.",
11899
+ "type": "string",
11900
+ "const": "claude-control-request"
11901
+ },
11902
+ {
11903
+ "description": "Codex: `turn/interrupt {threadId,turnId}` over the `codex app-server`\nJSON-RPC stdio protocol, which oneharness spawns as the run's own child\nand drives the thread/turn lifecycle on — so the interrupt rides the same\nopen stdin and nothing is shared or pooled.",
11904
+ "type": "string",
11905
+ "const": "codex-app-server"
11906
+ },
11907
+ {
11908
+ "description": "OpenCode: `POST /api/session/{id}/interrupt` against `opencode serve`.",
11909
+ "type": "string",
11910
+ "const": "opencode-http"
11911
+ },
11912
+ {
11913
+ "description": "Goose and Copilot: the ACP `session/cancel` **notification** (no `id`)\nover their JSON-RPC stdio servers.",
11914
+ "type": "string",
11915
+ "const": "acp-cancel"
11916
+ },
11917
+ {
11918
+ "description": "Crush: `POST /v1/workspaces/{id}/agent/sessions/{sid}/cancel` against\n`crush server`.",
11919
+ "type": "string",
11920
+ "const": "crush-http"
11921
+ }
11922
+ ]
11923
+ },
11582
11924
  "HarnessInfo": {
11583
11925
  "type": "object",
11584
11926
  "properties": {
11927
+ "control": {
11928
+ "description": "How `run --control` + `oneharness interrupt` abort an in-flight turn on\nthis harness (the mechanism id), or `null` when it has no out-of-band\nturn control at all — in which case `--control` is a loud usage error for\nit, never a socket that reports success while the turn keeps running.",
11929
+ "anyOf": [
11930
+ {
11931
+ "$ref": "#/$defs/ControlShape"
11932
+ },
11933
+ {
11934
+ "type": "null"
11935
+ }
11936
+ ]
11937
+ },
11585
11938
  "default_bin": {
11586
11939
  "type": "string"
11587
11940
  },
@@ -11687,6 +12040,7 @@
11687
12040
  "supports_resume",
11688
12041
  "session_capable",
11689
12042
  "supports_fork",
12043
+ "control",
11690
12044
  "fork_reuses_cache",
11691
12045
  "modes",
11692
12046
  "supports_native_schema",
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { ActionEvent, BatchReport, ExecutionTelemetry, FailureKind, FallThrough, FallbackReport, OutputFormat, RunReport, RunResult, SessionReport, Status, TimingSource, ToolCallStatus, Usage } from "./contracts.js";
2
+ import type { ActionEvent, BatchReport, ControlEvent, ControlReason, ControlReport, ControlVerb, ExecutionTelemetry, FailureKind, FallThrough, FallbackReport, OutputFormat, RunReport, RunResult, SessionReport, Status, TimingSource, ToolCallStatus, Usage, UtcInstant } from "./contracts.js";
3
3
  import type { DetectInfo, DetectReport } from "./detection.js";
4
4
  import type { HistoryRecord } from "./history.js";
5
5
  import type { HistoryLine } from "./history-line.js";
@@ -12,12 +12,20 @@ import type { HistoryWatchOptions } from "./history-watch-options.js";
12
12
  import type { HistoryLabels, PermissionMode, RunOptions } from "./options.js";
13
13
  import type { HarnessInfo, ListReport, ModeInfo, VariantInfo } from "./registry.js";
14
14
  import type { RunStreamEnvelope } from "./run-stream-envelope.js";
15
+ export type AbsolutePath = ControlReport["socket"];
15
16
  export type BatchStrategy = BatchReport["strategy"];
17
+ export type ControlShape = ControlReport["mechanism"];
16
18
  export type ModeHeadless = ModeInfo["headless"];
17
19
  export type SessionPhase = SessionReport["phase"];
20
+ export declare const AbsolutePathSchema: z.ZodType<AbsolutePath>;
18
21
  export declare const ActionEventSchema: z.ZodType<ActionEvent>;
19
22
  export declare const BatchReportSchema: z.ZodType<BatchReport>;
20
23
  export declare const BatchStrategySchema: z.ZodType<BatchStrategy>;
24
+ export declare const ControlEventSchema: z.ZodType<ControlEvent>;
25
+ export declare const ControlReasonSchema: z.ZodType<ControlReason>;
26
+ export declare const ControlReportSchema: z.ZodType<ControlReport>;
27
+ export declare const ControlShapeSchema: z.ZodType<ControlShape>;
28
+ export declare const ControlVerbSchema: z.ZodType<ControlVerb>;
21
29
  export declare const DetectInfoSchema: z.ZodType<DetectInfo>;
22
30
  export declare const DetectReportSchema: z.ZodType<DetectReport>;
23
31
  export declare const ExecutionTelemetrySchema: z.ZodType<ExecutionTelemetry>;
@@ -53,4 +61,5 @@ export declare const StatusSchema: z.ZodType<Status>;
53
61
  export declare const TimingSourceSchema: z.ZodType<TimingSource>;
54
62
  export declare const ToolCallStatusSchema: z.ZodType<ToolCallStatus>;
55
63
  export declare const UsageSchema: z.ZodType<Usage>;
64
+ export declare const UtcInstantSchema: z.ZodType<UtcInstant>;
56
65
  export declare const VariantInfoSchema: z.ZodType<VariantInfo>;
@@ -1,5 +1,6 @@
1
1
  /* Generated from oneharness Rust JSON Schemas. Do not edit. */
2
2
  import { z } from "zod";
3
+ export const AbsolutePathSchema = z.string();
3
4
  export const ActionEventSchema = z.looseObject({
4
5
  duration_ms: z.union([z.int().gte(0), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
5
6
  finished_at: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
@@ -27,6 +28,37 @@ export const BatchReportSchema = z.looseObject({
27
28
  strategy: z.lazy(() => BatchStrategySchema).refine((value) => value !== undefined, { message: "Required" }),
28
29
  });
29
30
  export const BatchStrategySchema = z.union([z.literal("speed"), z.literal("min-tokens")]);
31
+ export const ControlEventSchema = z.union([
32
+ z.looseObject({
33
+ at: z.lazy(() => UtcInstantSchema).refine((value) => value !== undefined, { message: "Required" }),
34
+ outcome: z.literal("served").refine((value) => value !== undefined, { message: "Required" }),
35
+ verb: z.lazy(() => ControlVerbSchema).refine((value) => value !== undefined, { message: "Required" }),
36
+ }),
37
+ z.looseObject({
38
+ at: z.lazy(() => UtcInstantSchema).refine((value) => value !== undefined, { message: "Required" }),
39
+ outcome: z.literal("refused").refine((value) => value !== undefined, { message: "Required" }),
40
+ reason: z.lazy(() => ControlReasonSchema).refine((value) => value !== undefined, { message: "Required" }),
41
+ verb: z.lazy(() => ControlVerbSchema).refine((value) => value !== undefined, { message: "Required" }),
42
+ }),
43
+ ]);
44
+ export const ControlReasonSchema = z.union([
45
+ z.literal("unsupported"),
46
+ z.literal("no_active_turn"),
47
+ z.literal("not_running"),
48
+ ]);
49
+ export const ControlReportSchema = z.looseObject({
50
+ interrupts: z.array(z.lazy(() => ControlEventSchema)).refine((value) => value !== undefined, { message: "Required" }),
51
+ mechanism: z.lazy(() => ControlShapeSchema).refine((value) => value !== undefined, { message: "Required" }),
52
+ socket: z.lazy(() => AbsolutePathSchema).refine((value) => value !== undefined, { message: "Required" }),
53
+ });
54
+ export const ControlShapeSchema = z.union([
55
+ z.literal("claude-control-request"),
56
+ z.literal("codex-app-server"),
57
+ z.literal("opencode-http"),
58
+ z.literal("acp-cancel"),
59
+ z.literal("crush-http"),
60
+ ]);
61
+ export const ControlVerbSchema = z.literal("interrupt");
30
62
  export const DetectInfoSchema = z.looseObject({
31
63
  available: z.boolean().refine((value) => value !== undefined, { message: "Required" }),
32
64
  bin: z.string().refine((value) => value !== undefined, { message: "Required" }),
@@ -93,6 +125,9 @@ export const FallbackReportSchema = z.looseObject({
93
125
  ran: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
94
126
  });
95
127
  export const HarnessInfoSchema = z.looseObject({
128
+ control: z
129
+ .union([z.lazy(() => ControlShapeSchema), z.null()])
130
+ .refine((value) => value !== undefined, { message: "Required" }),
96
131
  default_bin: z.string().refine((value) => value !== undefined, { message: "Required" }),
97
132
  display: z.string().refine((value) => value !== undefined, { message: "Required" }),
98
133
  example_command: z.array(z.string()).refine((value) => value !== undefined, { message: "Required" }),
@@ -1487,6 +1522,9 @@ export const RunReportSchema = z.looseObject({
1487
1522
  .refine((value) => value !== undefined, { message: "Required" }),
1488
1523
  bypass_permissions: z.boolean().refine((value) => value !== undefined, { message: "Required" }),
1489
1524
  config_files: z.array(z.string()).refine((value) => value !== undefined, { message: "Required" }),
1525
+ control: z
1526
+ .union([z.lazy(() => ControlReportSchema), z.null()])
1527
+ .refine((value) => value !== undefined, { message: "Required" }),
1490
1528
  dry_run: z.boolean().refine((value) => value !== undefined, { message: "Required" }),
1491
1529
  fallback: z
1492
1530
  .union([z.lazy(() => FallbackReportSchema), z.null()])
@@ -1595,6 +1633,7 @@ export const UsageSchema = z.looseObject({
1595
1633
  input_tokens: z.union([z.int().gte(0), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
1596
1634
  output_tokens: z.union([z.int().gte(0), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
1597
1635
  });
1636
+ export const UtcInstantSchema = z.string();
1598
1637
  export const VariantInfoSchema = z.looseObject({
1599
1638
  args: z.array(z.string()).refine((value) => value !== undefined, { message: "Required" }),
1600
1639
  bin: z.union([z.string(), z.null()]).refine((value) => value !== undefined, { message: "Required" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneharness/sdk",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
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.6.9",
22
+ "oneharness-cli": "0.6.10",
23
23
  "zod": "^4.4.3"
24
24
  }
25
25
  }