@harness-control/protocol 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 (49) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +16 -0
  3. package/dist/bin/conformance.d.ts +3 -0
  4. package/dist/bin/conformance.js +11 -0
  5. package/dist/bin/generate-json-schema.d.ts +3 -0
  6. package/dist/bin/generate-json-schema.js +12 -0
  7. package/dist/conformance.d.ts +42 -0
  8. package/dist/conformance.js +217 -0
  9. package/dist/index.d.ts +6761 -0
  10. package/dist/index.js +1931 -0
  11. package/dist/json-schema.d.ts +19 -0
  12. package/dist/json-schema.js +128 -0
  13. package/dist/pairing.d.ts +59 -0
  14. package/dist/pairing.js +42 -0
  15. package/dist/session-reducer.d.ts +36 -0
  16. package/dist/session-reducer.js +96 -0
  17. package/fixtures/conformance/invalid/arbitrary-event-family.json +13 -0
  18. package/fixtures/conformance/invalid/local-action-lease-binding-mismatch.json +51 -0
  19. package/fixtures/conformance/invalid/local-action-missing-turn-id.json +21 -0
  20. package/fixtures/conformance/invalid/local-action-patch-missing-base-hash.json +53 -0
  21. package/fixtures/conformance/invalid/local-action-shell-missing-approval.json +56 -0
  22. package/fixtures/conformance/invalid/mcp-stdio-attachment.json +27 -0
  23. package/fixtures/conformance/invalid/session-snapshot-unsafe-omission.json +26 -0
  24. package/fixtures/conformance/invalid/terminal-turn-missing-final-output.json +16 -0
  25. package/fixtures/conformance/invalid/unknown-payload-field.json +14 -0
  26. package/fixtures/conformance/invalid/unknown-top-level-field.json +14 -0
  27. package/fixtures/conformance/valid/command-ack.json +11 -0
  28. package/fixtures/conformance/valid/extension-event.json +18 -0
  29. package/fixtures/conformance/valid/host-hello.json +22 -0
  30. package/fixtures/conformance/valid/host-replay-unavailable.json +15 -0
  31. package/fixtures/conformance/valid/local-action-dev-server-start.json +67 -0
  32. package/fixtures/conformance/valid/local-action-dev-server-stop.json +54 -0
  33. package/fixtures/conformance/valid/local-action-error.json +40 -0
  34. package/fixtures/conformance/valid/local-action-filesystem-list.json +54 -0
  35. package/fixtures/conformance/valid/local-action-filesystem-patch.json +55 -0
  36. package/fixtures/conformance/valid/local-action-filesystem-read.json +53 -0
  37. package/fixtures/conformance/valid/local-action-filesystem-write.json +55 -0
  38. package/fixtures/conformance/valid/local-action-git-diff.json +53 -0
  39. package/fixtures/conformance/valid/local-action-git-status.json +53 -0
  40. package/fixtures/conformance/valid/local-action-response.json +49 -0
  41. package/fixtures/conformance/valid/local-action-shell-exec.json +63 -0
  42. package/fixtures/conformance/valid/provider-event.json +18 -0
  43. package/fixtures/conformance/valid/session-snapshot-request.json +9 -0
  44. package/fixtures/conformance/valid/session-snapshot.json +28 -0
  45. package/fixtures/conformance/valid/session-start-with-leases.json +80 -0
  46. package/fixtures/conformance/valid/session-start-with-runner-stdio-profile.json +27 -0
  47. package/fixtures/conformance/valid/turn-completed.json +30 -0
  48. package/package.json +61 -0
  49. package/schemas/hcp-message.schema.json +18667 -0
@@ -0,0 +1,19 @@
1
+ export type JsonSchemaValue = boolean | JsonSchema;
2
+ export type JsonSchema = Record<string, unknown> & {
3
+ $id?: string;
4
+ $schema?: string;
5
+ additionalProperties?: JsonSchemaValue;
6
+ const?: string | number | boolean | null;
7
+ description?: string;
8
+ items?: JsonSchemaValue | JsonSchemaValue[];
9
+ oneOf?: JsonSchema[];
10
+ pattern?: string;
11
+ properties?: Record<string, JsonSchemaValue>;
12
+ required?: string[];
13
+ title?: string;
14
+ type?: string;
15
+ };
16
+ export declare const HCP_MESSAGE_JSON_SCHEMA_ID: string;
17
+ export declare function createHcpMessageJsonSchema(): JsonSchema;
18
+ export declare const hcpMessageJsonSchema: JsonSchema;
19
+ //# sourceMappingURL=json-schema.d.ts.map
@@ -0,0 +1,128 @@
1
+ import { z } from "zod";
2
+ import { HCP_VERSION, KNOWN_HCP_EVENT_TYPES, hcpExtensionEventDataSchema, hcpMessageSchema, knownHcpEventDataSchemas, } from "./index.js";
3
+ export const HCP_MESSAGE_JSON_SCHEMA_ID = `https://schemas.harness-control.local/${HCP_VERSION}/message.schema.json`;
4
+ function isJsonSchema(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function asJsonSchema(value, context) {
8
+ if (!isJsonSchema(value)) {
9
+ throw new TypeError(`${context} must be a JSON Schema object.`);
10
+ }
11
+ return value;
12
+ }
13
+ function getProperties(schema, context) {
14
+ if (schema.properties === undefined) {
15
+ throw new TypeError(`${context} must define properties.`);
16
+ }
17
+ return schema.properties;
18
+ }
19
+ function getObjectProperty(schema, propertyName, context) {
20
+ const properties = getProperties(schema, context);
21
+ return asJsonSchema(properties[propertyName], `${context}.${propertyName}`);
22
+ }
23
+ function cloneJsonSchema(schema) {
24
+ return asJsonSchema(structuredClone(schema), "cloned JSON Schema");
25
+ }
26
+ function toRootJsonSchema(schema) {
27
+ return asJsonSchema(z.toJSONSchema(schema), "generated JSON Schema");
28
+ }
29
+ function toEmbeddedJsonSchema(schema) {
30
+ const jsonSchema = cloneJsonSchema(toRootJsonSchema(schema));
31
+ delete jsonSchema.$schema;
32
+ delete jsonSchema.$id;
33
+ return jsonSchema;
34
+ }
35
+ function hasMessageTypeConst(schema, messageType) {
36
+ const typeSchema = getObjectProperty(schema, "type", "message schema");
37
+ return typeSchema.const === messageType;
38
+ }
39
+ function createEventTypeSchema(eventType) {
40
+ return {
41
+ type: "string",
42
+ const: eventType,
43
+ };
44
+ }
45
+ function createExtensionEventTypeSchema(prefix) {
46
+ return {
47
+ type: "string",
48
+ pattern: `^${prefix}\\..+$`,
49
+ };
50
+ }
51
+ function withRequiredProperty(schema, propertyName) {
52
+ const required = schema.required ?? [];
53
+ if (required.includes(propertyName)) {
54
+ return schema;
55
+ }
56
+ return {
57
+ ...schema,
58
+ required: [...required, propertyName],
59
+ };
60
+ }
61
+ function createHarnessEventPayloadSchema(basePayloadSchema, eventTypeSchema, dataSchema, turnIdRequired) {
62
+ const payloadSchema = cloneJsonSchema(basePayloadSchema);
63
+ const properties = getProperties(payloadSchema, "harness.event payload schema");
64
+ properties.event_type = eventTypeSchema;
65
+ properties.data = dataSchema;
66
+ return turnIdRequired ? withRequiredProperty(payloadSchema, "turn_id") : payloadSchema;
67
+ }
68
+ function createHarnessEventMessageSchema(baseMessageSchema, payloadSchema) {
69
+ const messageSchema = cloneJsonSchema(baseMessageSchema);
70
+ const properties = getProperties(messageSchema, "harness.event message schema");
71
+ properties.payload = payloadSchema;
72
+ return messageSchema;
73
+ }
74
+ function createHarnessEventMessageSchemas(baseMessageSchema) {
75
+ const basePayloadSchema = getObjectProperty(baseMessageSchema, "payload", "harness.event message schema");
76
+ const knownEventSchemas = KNOWN_HCP_EVENT_TYPES.map((eventType) => {
77
+ const dataSchema = toEmbeddedJsonSchema(knownHcpEventDataSchemas[eventType]);
78
+ const payloadSchema = createHarnessEventPayloadSchema(basePayloadSchema, createEventTypeSchema(eventType), dataSchema, eventType.startsWith("local_capability.action."));
79
+ return createHarnessEventMessageSchema(baseMessageSchema, payloadSchema);
80
+ });
81
+ const extensionEventDataSchema = toEmbeddedJsonSchema(hcpExtensionEventDataSchema);
82
+ const extensionEventSchemas = ["provider", "extension"].map((prefix) => {
83
+ const payloadSchema = createHarnessEventPayloadSchema(basePayloadSchema, createExtensionEventTypeSchema(prefix), extensionEventDataSchema, false);
84
+ return createHarnessEventMessageSchema(baseMessageSchema, payloadSchema);
85
+ });
86
+ return [...knownEventSchemas, ...extensionEventSchemas];
87
+ }
88
+ function patchStreamableHttpMcpUrlSchema(schema) {
89
+ const messageSchemas = schema.oneOf ?? [];
90
+ const sessionStartSchema = messageSchemas.find((messageSchema) => hasMessageTypeConst(messageSchema, "harness.session.start"));
91
+ if (sessionStartSchema === undefined) {
92
+ throw new TypeError("HCP message JSON Schema must contain harness.session.start.");
93
+ }
94
+ const payloadSchema = getObjectProperty(sessionStartSchema, "payload", "harness.session.start schema");
95
+ const mcpServersSchema = getObjectProperty(payloadSchema, "mcp_servers", "session start payload schema");
96
+ const itemSchema = asJsonSchema(mcpServersSchema.items, "mcp_servers items schema");
97
+ const attachmentSchemas = itemSchema.oneOf ?? [];
98
+ const streamableSchema = attachmentSchemas.find((candidate) => {
99
+ const transportValue = candidate.properties?.transport;
100
+ const transportSchema = typeof transportValue === "object" && transportValue !== null ? transportValue : undefined;
101
+ return transportSchema?.const === "streamable_http";
102
+ });
103
+ const urlOwner = streamableSchema ?? itemSchema;
104
+ const urlSchema = getObjectProperty(urlOwner, "url", "streamable HTTP MCP server attachment schema");
105
+ urlSchema.pattern = "^https?://";
106
+ }
107
+ export function createHcpMessageJsonSchema() {
108
+ const schema = cloneJsonSchema(toRootJsonSchema(hcpMessageSchema));
109
+ const messageSchemas = schema.oneOf ?? [];
110
+ if (messageSchemas.length === 0) {
111
+ throw new TypeError("HCP message JSON Schema must contain message variants.");
112
+ }
113
+ const expandedMessageSchemas = messageSchemas.flatMap((messageSchema) => {
114
+ if (hasMessageTypeConst(messageSchema, "harness.event")) {
115
+ return createHarnessEventMessageSchemas(messageSchema);
116
+ }
117
+ return [messageSchema];
118
+ });
119
+ schema.$id = HCP_MESSAGE_JSON_SCHEMA_ID;
120
+ schema.title = "HCP protocol message";
121
+ schema.description =
122
+ "Structural JSON Schema for known hcp.v0 protocol messages. Runtime parsers enforce additional cross-field invariants such as lease-to-attribution equality.";
123
+ schema.oneOf = expandedMessageSchemas;
124
+ patchStreamableHttpMcpUrlSchema(schema);
125
+ return schema;
126
+ }
127
+ export const hcpMessageJsonSchema = createHcpMessageJsonSchema();
128
+ //# sourceMappingURL=json-schema.js.map
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ export declare const pairingCreateRequestSchema: z.ZodObject<{
3
+ exchange_secret_hash: z.ZodString;
4
+ runner_id: z.ZodString;
5
+ host_id: z.ZodString;
6
+ protocol_version: z.ZodLiteral<"hcp.v0">;
7
+ }, z.core.$strict>;
8
+ export declare const pairingCodeResponseSchema: z.ZodObject<{
9
+ request_id: z.ZodString;
10
+ pairing_code: z.ZodString;
11
+ pairing_url: z.ZodString;
12
+ expires_at: z.ZodString;
13
+ poll_interval_seconds: z.ZodNumber;
14
+ }, z.core.$strict>;
15
+ export declare const pairingExchangeRequestSchema: z.ZodObject<{
16
+ request_id: z.ZodString;
17
+ exchange_secret: z.ZodString;
18
+ runner_id: z.ZodString;
19
+ host_id: z.ZodString;
20
+ protocol_version: z.ZodLiteral<"hcp.v0">;
21
+ }, z.core.$strict>;
22
+ export declare const runnerCredentialSchema: z.ZodObject<{
23
+ credential_id: z.ZodString;
24
+ credential_secret: z.ZodString;
25
+ runner_id: z.ZodString;
26
+ host_id: z.ZodString;
27
+ control_plane_url: z.ZodString;
28
+ issued_at: z.ZodString;
29
+ mcp_proof_secret: z.ZodString;
30
+ }, z.core.$strict>;
31
+ export declare const pairingExchangeResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
32
+ status: z.ZodLiteral<"pending">;
33
+ }, z.core.$strict>, z.ZodObject<{
34
+ status: z.ZodLiteral<"approved">;
35
+ control_plane_url: z.ZodString;
36
+ credential: z.ZodObject<{
37
+ credential_id: z.ZodString;
38
+ credential_secret: z.ZodString;
39
+ runner_id: z.ZodString;
40
+ host_id: z.ZodString;
41
+ control_plane_url: z.ZodString;
42
+ issued_at: z.ZodString;
43
+ mcp_proof_secret: z.ZodString;
44
+ }, z.core.$strict>;
45
+ }, z.core.$strict>], "status">;
46
+ export declare const connectionTokenRequestSchema: z.ZodObject<{
47
+ protocol_schema_sha256: z.ZodString;
48
+ credential_id: z.ZodString;
49
+ credential_secret: z.ZodString;
50
+ runner_id: z.ZodString;
51
+ host_id: z.ZodString;
52
+ protocol_version: z.ZodLiteral<"hcp.v0">;
53
+ }, z.core.$strict>;
54
+ export declare const connectionTokenResponseSchema: z.ZodObject<{
55
+ connection_token: z.ZodString;
56
+ expires_at: z.ZodString;
57
+ }, z.core.$strict>;
58
+ export type PairingCodeResponse = z.infer<typeof pairingCodeResponseSchema>;
59
+ //# sourceMappingURL=pairing.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ const identity = { runner_id: z.string().min(1).max(200), host_id: z.string().min(1).max(200), protocol_version: z.literal("hcp.v0") };
3
+ export const pairingCreateRequestSchema = z.object({
4
+ ...identity,
5
+ exchange_secret_hash: z.string().regex(/^[a-f0-9]{64}$/),
6
+ }).strict();
7
+ export const pairingCodeResponseSchema = z.object({
8
+ request_id: z.string().min(1),
9
+ pairing_code: z.string().min(1),
10
+ pairing_url: z.string().url(),
11
+ expires_at: z.string().datetime({ offset: true }),
12
+ poll_interval_seconds: z.number().int().min(1).max(30),
13
+ }).strict();
14
+ export const pairingExchangeRequestSchema = z.object({
15
+ ...identity,
16
+ request_id: z.string().min(1),
17
+ exchange_secret: z.string().min(32).max(256),
18
+ }).strict();
19
+ export const runnerCredentialSchema = z.object({
20
+ credential_id: z.string().min(1),
21
+ credential_secret: z.string().min(1),
22
+ runner_id: z.string().min(1),
23
+ host_id: z.string().min(1),
24
+ control_plane_url: z.string().url(),
25
+ issued_at: z.string().datetime({ offset: true }),
26
+ mcp_proof_secret: z.string().min(1),
27
+ }).strict();
28
+ export const pairingExchangeResponseSchema = z.discriminatedUnion("status", [
29
+ z.object({ status: z.literal("pending") }).strict(),
30
+ z.object({ status: z.literal("approved"), control_plane_url: z.string().url(), credential: runnerCredentialSchema }).strict(),
31
+ ]);
32
+ export const connectionTokenRequestSchema = z.object({
33
+ ...identity,
34
+ protocol_schema_sha256: z.string().regex(/^[a-f0-9]{64}$/),
35
+ credential_id: z.string().min(1),
36
+ credential_secret: z.string().min(1).max(256),
37
+ }).strict();
38
+ export const connectionTokenResponseSchema = z.object({
39
+ connection_token: z.string().min(1),
40
+ expires_at: z.string().datetime({ offset: true }),
41
+ }).strict();
42
+ //# sourceMappingURL=pairing.js.map
@@ -0,0 +1,36 @@
1
+ import type { HcpHarnessEventPayload, HcpSessionSnapshotPayload, HostResumeCursor } from "./index.js";
2
+ export type HcpEventApplyResult = {
3
+ outcome: "applied";
4
+ event: HcpHarnessEventPayload;
5
+ } | {
6
+ outcome: "duplicate";
7
+ event: HcpHarnessEventPayload;
8
+ } | {
9
+ outcome: "gap";
10
+ session_id: string;
11
+ expected_sequence: number;
12
+ received_sequence: number;
13
+ } | {
14
+ outcome: "conflict";
15
+ session_id: string;
16
+ sequence: number;
17
+ existing_event: HcpHarnessEventPayload;
18
+ received_event: HcpHarnessEventPayload;
19
+ };
20
+ export type HcpSnapshotApplyResult = {
21
+ outcome: "applied";
22
+ completeness: HcpSessionSnapshotPayload["completeness"];
23
+ applied_events: number;
24
+ duplicate_events: number;
25
+ tombstones: HcpSessionSnapshotPayload["tombstones"];
26
+ } | Extract<HcpEventApplyResult, {
27
+ outcome: "gap" | "conflict";
28
+ }>;
29
+ export declare class HcpSessionEventReducer {
30
+ #private;
31
+ applyEvent(event: HcpHarnessEventPayload): HcpEventApplyResult;
32
+ applySnapshot(snapshot: HcpSessionSnapshotPayload): HcpSnapshotApplyResult;
33
+ resumeCursor(): HostResumeCursor | undefined;
34
+ events(): HcpHarnessEventPayload[];
35
+ }
36
+ //# sourceMappingURL=session-reducer.d.ts.map
@@ -0,0 +1,96 @@
1
+ export class HcpSessionEventReducer {
2
+ #eventsBySession = new Map();
3
+ applyEvent(event) {
4
+ const sessionEvents = this.#eventsBySession.get(event.session_id) ?? new Map();
5
+ const existingEvent = sessionEvents.get(event.sequence);
6
+ if (existingEvent) {
7
+ return canonicalStringify(existingEvent) === canonicalStringify(event)
8
+ ? { outcome: "duplicate", event: existingEvent }
9
+ : {
10
+ outcome: "conflict",
11
+ session_id: event.session_id,
12
+ sequence: event.sequence,
13
+ existing_event: existingEvent,
14
+ received_event: event,
15
+ };
16
+ }
17
+ const expectedSequence = sessionEvents.size === 0 ? 1 : Math.max(...sessionEvents.keys()) + 1;
18
+ if (event.sequence !== expectedSequence) {
19
+ return {
20
+ outcome: "gap",
21
+ session_id: event.session_id,
22
+ expected_sequence: expectedSequence,
23
+ received_sequence: event.sequence,
24
+ };
25
+ }
26
+ sessionEvents.set(event.sequence, event);
27
+ this.#eventsBySession.set(event.session_id, sessionEvents);
28
+ return { outcome: "applied", event };
29
+ }
30
+ applySnapshot(snapshot) {
31
+ if (snapshot.completeness === "complete") {
32
+ const replacement = new Map();
33
+ for (const event of snapshot.events) {
34
+ replacement.set(event.sequence, event);
35
+ }
36
+ this.#eventsBySession.set(snapshot.session_id, replacement);
37
+ return {
38
+ outcome: "applied",
39
+ completeness: "complete",
40
+ applied_events: snapshot.events.length,
41
+ duplicate_events: 0,
42
+ tombstones: snapshot.tombstones,
43
+ };
44
+ }
45
+ let appliedEvents = 0;
46
+ let duplicateEvents = 0;
47
+ for (const event of snapshot.events) {
48
+ const result = this.applyEvent(event);
49
+ if (result.outcome === "gap" || result.outcome === "conflict") {
50
+ return result;
51
+ }
52
+ if (result.outcome === "applied") {
53
+ appliedEvents += 1;
54
+ }
55
+ else {
56
+ duplicateEvents += 1;
57
+ }
58
+ }
59
+ return {
60
+ outcome: "applied",
61
+ completeness: "partial",
62
+ applied_events: appliedEvents,
63
+ duplicate_events: duplicateEvents,
64
+ tombstones: snapshot.tombstones,
65
+ };
66
+ }
67
+ resumeCursor() {
68
+ const sessions = Array.from(this.#eventsBySession.entries())
69
+ .map(([sessionId, events]) => ({
70
+ session_id: sessionId,
71
+ last_event_sequence: Math.max(...events.keys()),
72
+ }))
73
+ .sort((left, right) => left.session_id.localeCompare(right.session_id));
74
+ return sessions.length > 0 ? { sessions } : undefined;
75
+ }
76
+ events() {
77
+ return Array.from(this.#eventsBySession.values())
78
+ .flatMap((events) => Array.from(events.values()))
79
+ .sort((left, right) => left.session_id === right.session_id
80
+ ? left.sequence - right.sequence
81
+ : left.session_id.localeCompare(right.session_id));
82
+ }
83
+ }
84
+ function canonicalStringify(value) {
85
+ if (value === null || typeof value !== "object") {
86
+ return JSON.stringify(value);
87
+ }
88
+ if (Array.isArray(value)) {
89
+ return `[${value.map((entry) => canonicalStringify(entry)).join(",")}]`;
90
+ }
91
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
92
+ return `{${entries
93
+ .map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalStringify(entryValue)}`)
94
+ .join(",")}}`;
95
+ }
96
+ //# sourceMappingURL=session-reducer.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "message-arbitrary-event-family",
3
+ "type": "harness.event",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "session_id": "session-1",
8
+ "sequence": 1,
9
+ "event_type": "custom.raw",
10
+ "created_at": "2026-01-01T00:00:01.000Z",
11
+ "data": {}
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "id": "message-local-action-lease-binding-mismatch",
3
+ "type": "local.action.request",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "request_id": "local-action-read-bad",
8
+ "action": "local.filesystem.read",
9
+ "issued_at": "2026-01-01T00:00:01.000Z",
10
+ "attribution": {
11
+ "session_id": "session-1",
12
+ "turn_id": "turn-1",
13
+ "workspace_id": "workspace-1",
14
+ "provider_instance_id": "provider-1",
15
+ "run_id": "run-1"
16
+ },
17
+ "lease": {
18
+ "lease_id": "local-lease-read",
19
+ "capability_id": "git",
20
+ "scope": "workspace_read",
21
+ "run_id": "run-1",
22
+ "hcp_session_id": "session-1",
23
+ "execution_host_id": "host-1",
24
+ "provider_instance_id": "provider-1",
25
+ "workspace_id": "workspace-1"
26
+ },
27
+ "sandbox": {
28
+ "mode": "workspace_write",
29
+ "workspace_root": "/tmp/workspace",
30
+ "cwd": "/tmp/workspace",
31
+ "requires_workspace_containment": true
32
+ },
33
+ "approval": {
34
+ "status": "not_required"
35
+ },
36
+ "output_limits": {
37
+ "content_bytes": 65536
38
+ },
39
+ "cancellation": {
40
+ "cancellable": false
41
+ },
42
+ "audit": {
43
+ "started_event_type": "local_capability.action.started",
44
+ "completed_event_type": "local_capability.action.completed",
45
+ "failed_event_type": "local_capability.action.failed"
46
+ },
47
+ "input": {
48
+ "path": "README.md"
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "message-local-action-missing-turn-id",
3
+ "type": "harness.event",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "session_id": "session-1",
8
+ "sequence": 1,
9
+ "event_type": "local_capability.action.started",
10
+ "created_at": "2026-01-01T00:00:01.000Z",
11
+ "data": {
12
+ "lease_id": "local-lease-1",
13
+ "run_id": "run-1",
14
+ "workspace_id": "workspace-1",
15
+ "provider_instance_id": "provider-1",
16
+ "capability_id": "filesystem",
17
+ "action": "read_file",
18
+ "status": "started"
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "id": "message-local-action-patch-missing-base-hash",
3
+ "type": "local.action.request",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "request_id": "local-action-patch-bad",
8
+ "action": "local.filesystem.patch",
9
+ "issued_at": "2026-01-01T00:00:01.000Z",
10
+ "attribution": {
11
+ "session_id": "session-1",
12
+ "turn_id": "turn-1",
13
+ "workspace_id": "workspace-1",
14
+ "provider_instance_id": "provider-1",
15
+ "run_id": "run-1"
16
+ },
17
+ "lease": {
18
+ "lease_id": "local-lease-patch",
19
+ "capability_id": "filesystem",
20
+ "scope": "workspace_write",
21
+ "run_id": "run-1",
22
+ "hcp_session_id": "session-1",
23
+ "execution_host_id": "host-1",
24
+ "provider_instance_id": "provider-1",
25
+ "workspace_id": "workspace-1"
26
+ },
27
+ "sandbox": {
28
+ "mode": "workspace_write",
29
+ "workspace_root": "/tmp/workspace",
30
+ "cwd": "/tmp/workspace",
31
+ "requires_workspace_containment": true
32
+ },
33
+ "approval": {
34
+ "status": "not_required"
35
+ },
36
+ "output_limits": {},
37
+ "cancellation": {
38
+ "cancellable": false
39
+ },
40
+ "audit": {
41
+ "started_event_type": "local_capability.action.started",
42
+ "completed_event_type": "local_capability.action.completed",
43
+ "failed_event_type": "local_capability.action.failed"
44
+ },
45
+ "input": {
46
+ "path": "README.md",
47
+ "patch": {
48
+ "format": "unified_diff",
49
+ "content": "--- a/README.md\n+++ b/README.md\n@@\n-old\n+new\n"
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "id": "message-local-action-shell-missing-approval",
3
+ "type": "local.action.request",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "request_id": "local-action-shell-bad",
8
+ "action": "local.shell.exec",
9
+ "issued_at": "2026-01-01T00:00:01.000Z",
10
+ "attribution": {
11
+ "session_id": "session-1",
12
+ "turn_id": "turn-1",
13
+ "workspace_id": "workspace-1",
14
+ "provider_instance_id": "provider-1",
15
+ "run_id": "run-1"
16
+ },
17
+ "lease": {
18
+ "lease_id": "local-lease-shell",
19
+ "capability_id": "shell",
20
+ "scope": "workspace",
21
+ "run_id": "run-1",
22
+ "hcp_session_id": "session-1",
23
+ "execution_host_id": "host-1",
24
+ "provider_instance_id": "provider-1",
25
+ "workspace_id": "workspace-1"
26
+ },
27
+ "sandbox": {
28
+ "mode": "workspace_write",
29
+ "workspace_root": "/tmp/workspace",
30
+ "cwd": "/tmp/workspace",
31
+ "requires_workspace_containment": true
32
+ },
33
+ "approval": {
34
+ "status": "not_required"
35
+ },
36
+ "output_limits": {
37
+ "stdout_bytes": 65536,
38
+ "stderr_bytes": 65536
39
+ },
40
+ "cancellation": {
41
+ "cancellable": true,
42
+ "timeout_ms": 60000
43
+ },
44
+ "audit": {
45
+ "started_event_type": "local_capability.action.started",
46
+ "completed_event_type": "local_capability.action.completed",
47
+ "failed_event_type": "local_capability.action.failed"
48
+ },
49
+ "input": {
50
+ "executable": "npm",
51
+ "argv": ["test"],
52
+ "cwd": "/tmp/workspace",
53
+ "use_shell": false
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "message-mcp-stdio-attachment",
3
+ "type": "harness.session.start",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "session_id": "session-1",
8
+ "workspace_id": "workspace-1",
9
+ "provider_instance_id": "provider-1",
10
+ "driver_kind": "codex",
11
+ "cwd": "/tmp/workspace",
12
+ "sandbox_mode": "workspace_write",
13
+ "approval_policy": "ask",
14
+ "continue_session": false,
15
+ "model_selection": {
16
+ "model": "gpt-5.3-codex"
17
+ },
18
+ "mcp_servers": [
19
+ {
20
+ "name": "bad",
21
+ "transport": "stdio",
22
+ "command": "node",
23
+ "args": ["server.js"]
24
+ }
25
+ ]
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "message-session-snapshot-partial",
3
+ "type": "harness.session.snapshot",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:01.000Z",
6
+ "payload": {
7
+ "command_id": "message-session-snapshot-request",
8
+ "session_id": "session-1",
9
+ "generated_at": "2026-01-01T00:00:01.000Z",
10
+ "completeness": "partial",
11
+ "omission_semantics": "replace",
12
+ "reason": "retention_gap",
13
+ "from_sequence": 2,
14
+ "through_sequence": 2,
15
+ "events": [
16
+ {
17
+ "session_id": "session-1",
18
+ "sequence": 2,
19
+ "event_type": "session.state.changed",
20
+ "created_at": "2026-01-01T00:00:00.500Z",
21
+ "data": { "state": "running" }
22
+ }
23
+ ],
24
+ "tombstones": []
25
+ }
26
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "message-terminal-turn-missing-final-output",
3
+ "type": "harness.event",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "session_id": "session-1",
8
+ "turn_id": "turn-1",
9
+ "sequence": 1,
10
+ "event_type": "turn.completed",
11
+ "created_at": "2026-01-01T00:00:01.000Z",
12
+ "data": {
13
+ "status": "accepted"
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "message-unknown-payload-field",
3
+ "type": "host.hello",
4
+ "version": "hcp.v0",
5
+ "sent_at": "2026-01-01T00:00:00.000Z",
6
+ "payload": {
7
+ "runner_id": "runner-local",
8
+ "host_id": "host-local",
9
+ "runner_version": "0.0.0",
10
+ "supported_protocol_versions": ["hcp.v0"],
11
+ "capabilities": [],
12
+ "unexpected": true
13
+ }
14
+ }