xo-harness 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.
- package/README.md +32 -0
- package/dist/harness.d.ts +1 -0
- package/dist/harness.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/internal/harness/event-stream.d.ts +7 -0
- package/dist/internal/harness/event-stream.js +42 -0
- package/dist/internal/harness/index.d.ts +7 -0
- package/dist/internal/harness/index.js +7 -0
- package/dist/internal/harness/message.d.ts +26 -0
- package/dist/internal/harness/message.js +192 -0
- package/dist/internal/harness/report.d.ts +51 -0
- package/dist/internal/harness/report.js +115 -0
- package/dist/internal/harness/shadow.d.ts +28 -0
- package/dist/internal/harness/shadow.js +118 -0
- package/dist/internal/harness/socket-bridge.d.ts +65 -0
- package/dist/internal/harness/socket-bridge.js +227 -0
- package/dist/internal/harness/task-supervisor.d.ts +20 -0
- package/dist/internal/harness/task-supervisor.js +180 -0
- package/dist/internal/harness/tool-runtime.d.ts +20 -0
- package/dist/internal/harness/tool-runtime.js +93 -0
- package/dist/internal/harness/tools.d.ts +32 -0
- package/dist/internal/harness/tools.js +30 -0
- package/dist/internal/harness/voice-session.d.ts +33 -0
- package/dist/internal/harness/voice-session.js +268 -0
- package/dist/internal/harness/xo.d.ts +25 -0
- package/dist/internal/harness/xo.js +24 -0
- package/dist/internal/protocol/async-queue.d.ts +8 -0
- package/dist/internal/protocol/async-queue.js +34 -0
- package/dist/internal/protocol/audio.d.ts +12 -0
- package/dist/internal/protocol/audio.js +24 -0
- package/dist/internal/protocol/events.d.ts +350 -0
- package/dist/internal/protocol/events.js +167 -0
- package/dist/internal/protocol/index.d.ts +6 -0
- package/dist/internal/protocol/index.js +6 -0
- package/dist/internal/protocol/parts.d.ts +219 -0
- package/dist/internal/protocol/parts.js +105 -0
- package/dist/internal/protocol/provider.d.ts +118 -0
- package/dist/internal/protocol/provider.js +77 -0
- package/dist/internal/protocol/tools.d.ts +62 -0
- package/dist/internal/protocol/tools.js +47 -0
- package/dist/internal/provider/contract.d.ts +24 -0
- package/dist/internal/provider/contract.js +1 -0
- package/dist/internal/provider/grok-voice.d.ts +45 -0
- package/dist/internal/provider/grok-voice.js +77 -0
- package/dist/internal/provider/index.d.ts +6 -0
- package/dist/internal/provider/index.js +9 -0
- package/dist/internal/provider/node-socket.d.ts +3 -0
- package/dist/internal/provider/node-socket.js +74 -0
- package/dist/internal/provider/openai-realtime.d.ts +43 -0
- package/dist/internal/provider/openai-realtime.js +76 -0
- package/dist/internal/provider/realtime-session.d.ts +30 -0
- package/dist/internal/provider/realtime-session.js +461 -0
- package/dist/internal/provider/realtime-socket.d.ts +42 -0
- package/dist/internal/provider/realtime-socket.js +30 -0
- package/dist/internal/provider/workers-socket.d.ts +11 -0
- package/dist/internal/provider/workers-socket.js +108 -0
- package/dist/internal/provider/workers.d.ts +6 -0
- package/dist/internal/provider/workers.js +11 -0
- package/dist/internal/provider-fake/index.d.ts +46 -0
- package/dist/internal/provider-fake/index.js +112 -0
- package/dist/internal/provider-fake/replay-voice-provider.d.ts +47 -0
- package/dist/internal/provider-fake/replay-voice-provider.js +133 -0
- package/dist/internal/provider-fake/scripted-voice-provider.d.ts +61 -0
- package/dist/internal/provider-fake/scripted-voice-provider.js +185 -0
- package/dist/internal/storage/append-tail.d.ts +12 -0
- package/dist/internal/storage/append-tail.js +19 -0
- package/dist/internal/storage/event-store.d.ts +19 -0
- package/dist/internal/storage/event-store.js +1 -0
- package/dist/internal/storage/index.d.ts +4 -0
- package/dist/internal/storage/index.js +4 -0
- package/dist/internal/storage/jsonl-event-store.d.ts +13 -0
- package/dist/internal/storage/jsonl-event-store.js +105 -0
- package/dist/internal/storage/memory-event-store.d.ts +8 -0
- package/dist/internal/storage/memory-event-store.js +22 -0
- package/dist/protocol.d.ts +1 -0
- package/dist/protocol.js +1 -0
- package/dist/provider-workers.d.ts +1 -0
- package/dist/provider-workers.js +1 -0
- package/dist/provider.d.ts +1 -0
- package/dist/provider.js +1 -0
- package/dist/storage.d.ts +1 -0
- package/dist/storage.js +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { appendFile, mkdir, open, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { HarnessEventSchema, parseHarnessEvent, stringifyHarnessEvent, } from "../protocol/index.js";
|
|
4
|
+
import { SessionAppendTails } from "./append-tail.js";
|
|
5
|
+
const safeSessionId = /^[A-Za-z0-9_-]+$/;
|
|
6
|
+
/**
|
|
7
|
+
* Local JSONL persistence for one harness process. Cross-process writer fencing and
|
|
8
|
+
* directory fsync for newly published session files are deferred to the resume slice.
|
|
9
|
+
*/
|
|
10
|
+
export class JsonlEventStore {
|
|
11
|
+
#rootDirectory;
|
|
12
|
+
#tails = new SessionAppendTails();
|
|
13
|
+
#nextSequence = new Map();
|
|
14
|
+
constructor(rootDirectory) {
|
|
15
|
+
this.#rootDirectory = rootDirectory;
|
|
16
|
+
}
|
|
17
|
+
async append(event) {
|
|
18
|
+
this.#assertSessionId(event.sessionId);
|
|
19
|
+
return this.#tails.run(event.sessionId, async () => {
|
|
20
|
+
const sequence = await this.#takeSequence(event.sessionId);
|
|
21
|
+
const stored = HarnessEventSchema.parse({ ...event, sequence });
|
|
22
|
+
await mkdir(this.#rootDirectory, { recursive: true });
|
|
23
|
+
await appendFile(this.#sessionPath(event.sessionId), `${stringifyHarnessEvent(stored)}\n`, "utf8");
|
|
24
|
+
return stored;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
flush(sessionId) {
|
|
28
|
+
this.#assertSessionId(sessionId);
|
|
29
|
+
return this.#tails.run(sessionId, async () => {
|
|
30
|
+
try {
|
|
31
|
+
const file = await open(this.#sessionPath(sessionId), "r");
|
|
32
|
+
try {
|
|
33
|
+
await file.sync();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await file.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === "ENOENT")
|
|
41
|
+
return;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async list(sessionId) {
|
|
47
|
+
this.#assertSessionId(sessionId);
|
|
48
|
+
await this.#tails.settled(sessionId);
|
|
49
|
+
let contents;
|
|
50
|
+
try {
|
|
51
|
+
contents = await readFile(this.#sessionPath(sessionId), "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === "ENOENT")
|
|
55
|
+
return [];
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return parseCompleteLines(contents);
|
|
59
|
+
}
|
|
60
|
+
async #takeSequence(sessionId) {
|
|
61
|
+
const known = this.#nextSequence.get(sessionId);
|
|
62
|
+
if (known !== undefined) {
|
|
63
|
+
this.#nextSequence.set(sessionId, known + 1);
|
|
64
|
+
return known;
|
|
65
|
+
}
|
|
66
|
+
const events = await this.#repairAndRead(sessionId);
|
|
67
|
+
const next = (events.at(-1)?.sequence ?? 0) + 1;
|
|
68
|
+
this.#nextSequence.set(sessionId, next + 1);
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
async #repairAndRead(sessionId) {
|
|
72
|
+
let contents;
|
|
73
|
+
try {
|
|
74
|
+
contents = await readFile(this.#sessionPath(sessionId), "utf8");
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code === "ENOENT")
|
|
78
|
+
return [];
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
if (contents.length > 0 && !contents.endsWith("\n")) {
|
|
82
|
+
const lastNewline = contents.lastIndexOf("\n");
|
|
83
|
+
contents = lastNewline === -1 ? "" : contents.slice(0, lastNewline + 1);
|
|
84
|
+
await writeFile(this.#sessionPath(sessionId), contents, "utf8");
|
|
85
|
+
}
|
|
86
|
+
return parseCompleteLines(contents);
|
|
87
|
+
}
|
|
88
|
+
#sessionPath(sessionId) {
|
|
89
|
+
return path.join(this.#rootDirectory, `${sessionId}.jsonl`);
|
|
90
|
+
}
|
|
91
|
+
#assertSessionId(sessionId) {
|
|
92
|
+
if (!safeSessionId.test(sessionId)) {
|
|
93
|
+
throw new Error(`Unsafe session id: ${sessionId}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function parseCompleteLines(contents) {
|
|
98
|
+
const completeContents = contents.endsWith("\n")
|
|
99
|
+
? contents
|
|
100
|
+
: contents.slice(0, Math.max(0, contents.lastIndexOf("\n") + 1));
|
|
101
|
+
return completeContents
|
|
102
|
+
.split("\n")
|
|
103
|
+
.filter((line) => line.length > 0)
|
|
104
|
+
.map(parseHarnessEvent);
|
|
105
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type HarnessEvent, type UnsequencedHarnessEvent } from "../protocol/index.js";
|
|
2
|
+
import type { EventStore } from "./event-store.js";
|
|
3
|
+
export declare class MemoryEventStore implements EventStore {
|
|
4
|
+
#private;
|
|
5
|
+
append(event: UnsequencedHarnessEvent): Promise<HarnessEvent>;
|
|
6
|
+
flush(sessionId: string): Promise<void>;
|
|
7
|
+
list(sessionId: string): Promise<readonly HarnessEvent[]>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HarnessEventSchema } from "../protocol/index.js";
|
|
2
|
+
import { SessionAppendTails } from "./append-tail.js";
|
|
3
|
+
export class MemoryEventStore {
|
|
4
|
+
#events = new Map();
|
|
5
|
+
#tails = new SessionAppendTails();
|
|
6
|
+
append(event) {
|
|
7
|
+
return this.#tails.run(event.sessionId, async () => {
|
|
8
|
+
const sessionEvents = this.#events.get(event.sessionId) ?? [];
|
|
9
|
+
const stored = HarnessEventSchema.parse({ ...event, sequence: sessionEvents.length + 1 });
|
|
10
|
+
sessionEvents.push(stored);
|
|
11
|
+
this.#events.set(event.sessionId, sessionEvents);
|
|
12
|
+
return stored;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
flush(sessionId) {
|
|
16
|
+
return this.#tails.run(sessionId, async () => undefined);
|
|
17
|
+
}
|
|
18
|
+
async list(sessionId) {
|
|
19
|
+
await this.#tails.settled(sessionId);
|
|
20
|
+
return [...(this.#events.get(sessionId) ?? [])];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/protocol/index.js";
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/protocol/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider/workers.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider/workers.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider/index.js";
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/storage/index.js";
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/storage/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider-fake/index.js";
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./internal/provider-fake/index.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xo-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A TypeScript-first agent harness for continuous, fully duplex voice models.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./dist/provider.js",
|
|
8
|
+
"./dist/provider-workers.js"
|
|
9
|
+
],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./harness": {
|
|
19
|
+
"types": "./dist/harness.d.ts",
|
|
20
|
+
"default": "./dist/harness.js"
|
|
21
|
+
},
|
|
22
|
+
"./protocol": {
|
|
23
|
+
"types": "./dist/protocol.d.ts",
|
|
24
|
+
"default": "./dist/protocol.js"
|
|
25
|
+
},
|
|
26
|
+
"./provider": {
|
|
27
|
+
"types": "./dist/provider.d.ts",
|
|
28
|
+
"default": "./dist/provider.js"
|
|
29
|
+
},
|
|
30
|
+
"./provider/workers": {
|
|
31
|
+
"types": "./dist/provider-workers.d.ts",
|
|
32
|
+
"default": "./dist/provider-workers.js"
|
|
33
|
+
},
|
|
34
|
+
"./storage": {
|
|
35
|
+
"types": "./dist/storage.d.ts",
|
|
36
|
+
"default": "./dist/storage.js"
|
|
37
|
+
},
|
|
38
|
+
"./testing": {
|
|
39
|
+
"types": "./dist/testing.d.ts",
|
|
40
|
+
"default": "./dist/testing.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "node ../../scripts/build-package.mjs"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"ws": "8.21.1",
|
|
53
|
+
"zod": "4.4.3"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public",
|
|
57
|
+
"registry": "https://registry.npmjs.org/"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/zvadaadam/XO.git",
|
|
62
|
+
"directory": "packages/xo"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/zvadaadam/XO#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/zvadaadam/XO/issues"
|
|
67
|
+
},
|
|
68
|
+
"keywords": [
|
|
69
|
+
"agent",
|
|
70
|
+
"ai",
|
|
71
|
+
"voice",
|
|
72
|
+
"realtime",
|
|
73
|
+
"harness",
|
|
74
|
+
"tools"
|
|
75
|
+
]
|
|
76
|
+
}
|