@harness-control/runner 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 (71) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +21 -0
  3. package/dist/audit/index.d.ts +18 -0
  4. package/dist/audit/index.js +28 -0
  5. package/dist/config/index.d.ts +179 -0
  6. package/dist/config/index.js +124 -0
  7. package/dist/connection/index.d.ts +2 -0
  8. package/dist/connection/index.js +2 -0
  9. package/dist/connection/runner-connection.d.ts +22 -0
  10. package/dist/connection/runner-connection.js +631 -0
  11. package/dist/harnesses/adapters/providers/claude-runtime.d.ts +5 -0
  12. package/dist/harnesses/adapters/providers/claude-runtime.js +186 -0
  13. package/dist/harnesses/adapters/providers/claude.d.ts +24 -0
  14. package/dist/harnesses/adapters/providers/claude.js +189 -0
  15. package/dist/harnesses/adapters/providers/cli-process.d.ts +44 -0
  16. package/dist/harnesses/adapters/providers/cli-process.js +195 -0
  17. package/dist/harnesses/adapters/providers/codex-models.d.ts +4 -0
  18. package/dist/harnesses/adapters/providers/codex-models.js +62 -0
  19. package/dist/harnesses/adapters/providers/codex-rpc.d.ts +21 -0
  20. package/dist/harnesses/adapters/providers/codex-rpc.js +114 -0
  21. package/dist/harnesses/adapters/providers/codex-runtime.d.ts +3 -0
  22. package/dist/harnesses/adapters/providers/codex-runtime.js +267 -0
  23. package/dist/harnesses/adapters/providers/codex.d.ts +22 -0
  24. package/dist/harnesses/adapters/providers/codex.js +161 -0
  25. package/dist/harnesses/adapters/providers/mock.d.ts +13 -0
  26. package/dist/harnesses/adapters/providers/mock.js +64 -0
  27. package/dist/harnesses/adapters/providers/native-process.d.ts +9 -0
  28. package/dist/harnesses/adapters/providers/native-process.js +41 -0
  29. package/dist/harnesses/adapters/providers/native-turn.d.ts +17 -0
  30. package/dist/harnesses/adapters/providers/native-turn.js +139 -0
  31. package/dist/harnesses/adapters/providers/opencode.d.ts +44 -0
  32. package/dist/harnesses/adapters/providers/opencode.js +416 -0
  33. package/dist/harnesses/adapters/providers/shared.d.ts +9 -0
  34. package/dist/harnesses/adapters/providers/shared.js +97 -0
  35. package/dist/harnesses/adapters/registry.d.ts +12 -0
  36. package/dist/harnesses/adapters/registry.js +47 -0
  37. package/dist/harnesses/adapters/types.d.ts +54 -0
  38. package/dist/harnesses/adapters/types.js +9 -0
  39. package/dist/harnesses/adapters.d.ts +8 -0
  40. package/dist/harnesses/adapters.js +8 -0
  41. package/dist/harnesses/index.d.ts +93 -0
  42. package/dist/harnesses/index.js +620 -0
  43. package/dist/host/provider-registry.d.ts +34 -0
  44. package/dist/host/provider-registry.js +162 -0
  45. package/dist/index.d.ts +3 -0
  46. package/dist/index.js +201 -0
  47. package/dist/local-actions/dispatcher.d.ts +28 -0
  48. package/dist/local-actions/dispatcher.js +407 -0
  49. package/dist/local-actions/executors.d.ts +159 -0
  50. package/dist/local-actions/executors.js +1103 -0
  51. package/dist/local-actions/index.d.ts +74 -0
  52. package/dist/local-actions/index.js +275 -0
  53. package/dist/logs/index.d.ts +6 -0
  54. package/dist/logs/index.js +9 -0
  55. package/dist/mcp/McpAttachmentClient.d.ts +111 -0
  56. package/dist/mcp/McpAttachmentClient.js +345 -0
  57. package/dist/mcp/McpProxyServer.d.ts +18 -0
  58. package/dist/mcp/McpProxyServer.js +188 -0
  59. package/dist/mcp/McpStdioProfileClient.d.ts +19 -0
  60. package/dist/mcp/McpStdioProfileClient.js +91 -0
  61. package/dist/mcp/index.d.ts +5 -0
  62. package/dist/mcp/index.js +5 -0
  63. package/dist/mcp/redaction.d.ts +3 -0
  64. package/dist/mcp/redaction.js +40 -0
  65. package/dist/pairing/index.d.ts +38 -0
  66. package/dist/pairing/index.js +180 -0
  67. package/dist/state/index.d.ts +76 -0
  68. package/dist/state/index.js +242 -0
  69. package/dist/workspaces/index.d.ts +13 -0
  70. package/dist/workspaces/index.js +110 -0
  71. package/package.json +76 -0
@@ -0,0 +1,162 @@
1
+ export class ProviderInstanceRegistry {
2
+ #config;
3
+ #driversByKind;
4
+ #driversByProviderId;
5
+ constructor(config, drivers = []) {
6
+ this.#config = config;
7
+ this.#driversByKind = new Map();
8
+ this.#driversByProviderId = new Map();
9
+ for (const driver of drivers) {
10
+ if (driver.provider_instance_id) {
11
+ this.#driversByProviderId.set(driver.provider_instance_id, driver);
12
+ }
13
+ else {
14
+ this.#driversByKind.set(driver.driver_kind, driver);
15
+ }
16
+ }
17
+ }
18
+ static fromConfig(config, drivers = []) {
19
+ return new ProviderInstanceRegistry(config, drivers);
20
+ }
21
+ snapshot(now = new Date()) {
22
+ return {
23
+ providers: this.#config.provider_instances.map((provider) => this.#snapshotProvider(provider, now)),
24
+ local_capabilities: this.#config.local_capabilities.map((capability) => ({
25
+ id: capability.id,
26
+ status: capability.status,
27
+ scopes: capability.scopes,
28
+ approval_required: capability.approval_required,
29
+ ...(capability.message ? { message: capability.message } : {}),
30
+ })),
31
+ workspaces: this.#config.workspaces.map((workspace) => ({
32
+ id: workspace.id,
33
+ path: workspace.path,
34
+ ...(workspace.display_name ? { display_name: workspace.display_name } : {}),
35
+ ...(workspace.git_remote ? { git_remote: workspace.git_remote } : {}),
36
+ })),
37
+ mcp_stdio_profiles: this.#config.mcp_stdio_profiles.map((profile) => ({
38
+ id: profile.id,
39
+ provider_instance_ids: profile.provider_instance_ids,
40
+ ...(profile.allowed_tools ? { allowed_tools: profile.allowed_tools } : {}),
41
+ ...(profile.denied_tools.length > 0 ? { denied_tools: profile.denied_tools } : {}),
42
+ })),
43
+ };
44
+ }
45
+ computeContinuationGroups() {
46
+ const groups = new Map();
47
+ for (const provider of this.#config.provider_instances) {
48
+ const key = computeContinuationGroupKey(provider);
49
+ const existing = groups.get(key);
50
+ if (existing) {
51
+ existing.provider_instance_ids.push(provider.id);
52
+ }
53
+ else {
54
+ groups.set(key, {
55
+ key,
56
+ driver_kind: provider.driver_kind,
57
+ provider_instance_ids: [provider.id],
58
+ });
59
+ }
60
+ }
61
+ return Array.from(groups.values());
62
+ }
63
+ #snapshotProvider(provider, now) {
64
+ const driver = this.#driversByProviderId.get(provider.id) ?? this.#driversByKind.get(provider.driver_kind);
65
+ const enabled = provider.enabled;
66
+ const installed = driver?.installed ?? false;
67
+ const available = enabled && Boolean(driver?.available);
68
+ const status = enabled
69
+ ? driver?.status ?? (available ? "ready" : "unavailable")
70
+ : "disabled";
71
+ const snapshot = {
72
+ ...(driver?.execution_capabilities ? { execution_capabilities: driver.execution_capabilities } : {}),
73
+ provider_instance_id: provider.id,
74
+ driver_kind: provider.driver_kind,
75
+ ...(provider.display_name ? { display_name: provider.display_name } : {}),
76
+ ...(provider.accent_color ? { accent_color: provider.accent_color } : {}),
77
+ enabled,
78
+ installed,
79
+ status,
80
+ availability: available ? "available" : "unavailable",
81
+ ...(driver?.version ? { version: driver.version } : {}),
82
+ ...(available ? {} : { message: this.#unavailableMessage(provider, driver) }),
83
+ checked_at: now.toISOString(),
84
+ continuation_group_key: computeContinuationGroupKey(provider),
85
+ auth: {
86
+ status: driver?.authStatus ?? (status === "unauthenticated" ? "unauthenticated" : "unknown"),
87
+ },
88
+ models: provider.models.length > 0 ? normalizeHarnessModels(provider.models) : driver?.models ?? [],
89
+ local_capabilities: provider.local_capabilities,
90
+ };
91
+ if (provider.hidden_models.length > 0) {
92
+ snapshot.hidden_models = provider.hidden_models;
93
+ }
94
+ if (provider.model_order.length > 0) {
95
+ snapshot.model_order = provider.model_order;
96
+ }
97
+ if (provider.favorite_models.length > 0) {
98
+ snapshot.favorite_models = provider.favorite_models;
99
+ }
100
+ return snapshot;
101
+ }
102
+ #unavailableMessage(provider, driver) {
103
+ if (!provider.enabled) {
104
+ return "Provider is disabled in runner config.";
105
+ }
106
+ if (!driver) {
107
+ return `Unsupported provider driver '${provider.driver_kind}'.`;
108
+ }
109
+ if (driver.message) {
110
+ return driver.message;
111
+ }
112
+ return "Provider is unavailable.";
113
+ }
114
+ }
115
+ export function computeContinuationGroupKey(provider) {
116
+ if (provider.continuation_group_key) {
117
+ return provider.continuation_group_key;
118
+ }
119
+ if (provider.driver_kind === "codex" && provider.home) {
120
+ return `codex:${provider.home}`;
121
+ }
122
+ if (provider.driver_kind === "claude" && provider.home) {
123
+ return `claude:${provider.home}`;
124
+ }
125
+ return `driver:${provider.driver_kind}`;
126
+ }
127
+ function normalizeHarnessModels(models) {
128
+ return models.map((model) => {
129
+ const result = {
130
+ id: model.id,
131
+ label: model.label,
132
+ capabilities: {
133
+ option_descriptors: model.capabilities.option_descriptors.map(normalizeHarnessOptionDescriptor),
134
+ },
135
+ };
136
+ if (model.is_default !== undefined) {
137
+ result.is_default = model.is_default;
138
+ }
139
+ return result;
140
+ });
141
+ }
142
+ function normalizeHarnessOptionDescriptor(option) {
143
+ const result = {
144
+ id: option.id,
145
+ label: option.label,
146
+ type: option.type,
147
+ };
148
+ if (option.values !== undefined) {
149
+ result.values = option.values;
150
+ }
151
+ if (option.default_value !== undefined) {
152
+ result.default_value = option.default_value;
153
+ }
154
+ if (option.current_value !== undefined) {
155
+ result.current_value = option.current_value;
156
+ }
157
+ if (option.prompt_injected_values !== undefined) {
158
+ result.prompt_injected_values = option.prompt_injected_values;
159
+ }
160
+ return result;
161
+ }
162
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: string[]): Promise<number>;
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { pathToFileURL } from "node:url";
4
+ import { writeFile } from "node:fs/promises";
5
+ import { hostname } from "node:os";
6
+ import { HCP_VERSION } from "@harness-control/protocol";
7
+ import { JsonlAuditLogger, defaultAuditLogPath } from "./audit/index.js";
8
+ import { loadRunnerConfig } from "./config/index.js";
9
+ import { RunnerConnection } from "./connection/index.js";
10
+ import { HarnessSessionManager } from "./harnesses/index.js";
11
+ import { consoleLogger } from "./logs/index.js";
12
+ import { createDevelopmentHmacProofSigner } from "./mcp/McpAttachmentClient.js";
13
+ import { JsonRunnerStateStore, defaultRunnerStatePath } from "./state/index.js";
14
+ import { defaultCredentialsPath, loadRunnerCredential, normalizeControlPlaneUrl, pairWithReferenceControlPlane, requestConnectionToken, writeRunnerCredentials, } from "./pairing/index.js";
15
+ const RUNNER_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
16
+ export async function main(argv = process.argv.slice(2)) {
17
+ const command = argv[0];
18
+ if (command === "version") {
19
+ console.log(`hcp-runner ${RUNNER_VERSION} (${HCP_VERSION})`);
20
+ return 0;
21
+ }
22
+ if (command === "pair") {
23
+ let pairOptions;
24
+ let controlPlaneUrl;
25
+ try {
26
+ pairOptions = parsePairOptions(argv.slice(1));
27
+ controlPlaneUrl = normalizeControlPlaneUrl(pairOptions.controlPlaneUrl);
28
+ }
29
+ catch (error) {
30
+ console.error(error instanceof Error ? error.message : "Invalid pair arguments.");
31
+ console.error("Usage: hcp-runner pair <control-plane-url> [--runner-id id] [--host-id id] [--out path] [--credentials-out path] [--offline]");
32
+ return 1;
33
+ }
34
+ if (pairOptions.offline) {
35
+ const config = {
36
+ runner_id: pairOptions.runnerId,
37
+ host_id: pairOptions.hostId,
38
+ control_plane_url: controlPlaneUrl,
39
+ workspaces: [],
40
+ provider_instances: [],
41
+ };
42
+ const serializedConfig = `${JSON.stringify(config, null, 2)}\n`;
43
+ if (pairOptions.outPath) {
44
+ await writeFile(pairOptions.outPath, serializedConfig, { mode: 0o600 });
45
+ console.log(`Wrote offline runner config to ${pairOptions.outPath}`);
46
+ }
47
+ else {
48
+ console.log(serializedConfig.trimEnd());
49
+ }
50
+ return 0;
51
+ }
52
+ const credentialsPath = pairOptions.credentialsPath ?? defaultCredentialsPath();
53
+ const pairing = await pairWithReferenceControlPlane({
54
+ controlPlaneUrl,
55
+ runnerId: pairOptions.runnerId,
56
+ hostId: pairOptions.hostId,
57
+ onPairingCode: (code) => { console.error(`Approve runner pairing at ${code.pairing_url} (code: ${code.pairing_code}). Waiting for approval…`); },
58
+ });
59
+ await writeRunnerCredentials(credentialsPath, pairing.credential);
60
+ const config = {
61
+ runner_id: pairOptions.runnerId,
62
+ host_id: pairOptions.hostId,
63
+ control_plane_url: pairing.controlPlaneUrl,
64
+ credentials_path: credentialsPath,
65
+ workspaces: [],
66
+ provider_instances: [],
67
+ };
68
+ const serializedConfig = `${JSON.stringify(config, null, 2)}\n`;
69
+ if (pairOptions.outPath) {
70
+ await writeFile(pairOptions.outPath, serializedConfig, { mode: 0o600 });
71
+ console.log(`Wrote paired runner config to ${pairOptions.outPath}`);
72
+ console.log(`Stored runner credentials at ${credentialsPath}`);
73
+ }
74
+ else {
75
+ console.log(serializedConfig.trimEnd());
76
+ }
77
+ return 0;
78
+ }
79
+ if (command === "run") {
80
+ const configPath = parseConfigPath(argv.slice(1));
81
+ if (!configPath) {
82
+ console.error("Usage: hcp-runner run --config <path>");
83
+ return 1;
84
+ }
85
+ const config = await loadRunnerConfig(configPath);
86
+ const credential = await loadRunnerCredential(config);
87
+ const stateStore = new JsonRunnerStateStore(config.state_path ?? defaultRunnerStatePath(config.runner_id));
88
+ const harnessSessions = new HarnessSessionManager(config, {
89
+ auditLogger: new JsonlAuditLogger(defaultAuditLogPath()),
90
+ stateStore,
91
+ ...(credential
92
+ ? { mcpProofSigner: createDevelopmentHmacProofSigner(credential.mcp_proof_secret) }
93
+ : {}),
94
+ });
95
+ const connection = new RunnerConnection({
96
+ config,
97
+ runnerVersion: RUNNER_VERSION,
98
+ configPath,
99
+ harnessSessions,
100
+ ...(credential
101
+ ? {
102
+ connectionTokenProvider: async () => requestConnectionToken(config, credential),
103
+ }
104
+ : {}),
105
+ onLog: (message) => {
106
+ consoleLogger.info(message);
107
+ },
108
+ });
109
+ await connection.connect();
110
+ consoleLogger.info(`Runner '${config.runner_id}' connected to ${config.control_plane_url}.`);
111
+ const close = async () => {
112
+ await connection.close();
113
+ };
114
+ process.once("SIGINT", () => {
115
+ close().then(() => process.exit(0));
116
+ });
117
+ process.once("SIGTERM", () => {
118
+ close().then(() => process.exit(0));
119
+ });
120
+ return new Promise(() => undefined);
121
+ }
122
+ console.log("Usage: hcp-runner <version|pair|run>");
123
+ return command ? 1 : 0;
124
+ }
125
+ function parseConfigPath(args) {
126
+ const configFlagIndex = args.indexOf("--config");
127
+ if (configFlagIndex === -1) {
128
+ return undefined;
129
+ }
130
+ return args[configFlagIndex + 1];
131
+ }
132
+ function parsePairOptions(args) {
133
+ const controlPlaneUrl = args[0];
134
+ if (!controlPlaneUrl) {
135
+ throw new Error("Missing control plane URL.");
136
+ }
137
+ const defaultHostId = hostname().replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
138
+ let runnerId = `runner-${defaultHostId}`;
139
+ let hostId = defaultHostId;
140
+ let outPath;
141
+ let credentialsPath;
142
+ let offline = false;
143
+ for (let index = 1; index < args.length; index += 1) {
144
+ const arg = args[index] ?? "";
145
+ if (arg === "--runner-id") {
146
+ const value = args[index + 1];
147
+ if (!value) {
148
+ throw new Error("--runner-id requires a value.");
149
+ }
150
+ runnerId = value;
151
+ index += 1;
152
+ continue;
153
+ }
154
+ if (arg === "--host-id") {
155
+ const value = args[index + 1];
156
+ if (!value) {
157
+ throw new Error("--host-id requires a value.");
158
+ }
159
+ hostId = value;
160
+ index += 1;
161
+ continue;
162
+ }
163
+ if (arg === "--out") {
164
+ const value = args[index + 1];
165
+ if (!value) {
166
+ throw new Error("--out requires a value.");
167
+ }
168
+ outPath = value;
169
+ index += 1;
170
+ continue;
171
+ }
172
+ if (arg === "--credentials-out") {
173
+ const value = args[index + 1];
174
+ if (!value) {
175
+ throw new Error("--credentials-out requires a value.");
176
+ }
177
+ credentialsPath = value;
178
+ index += 1;
179
+ continue;
180
+ }
181
+ if (arg === "--offline") {
182
+ offline = true;
183
+ continue;
184
+ }
185
+ throw new Error(`Unknown pair argument: ${arg}`);
186
+ }
187
+ return {
188
+ controlPlaneUrl,
189
+ runnerId,
190
+ hostId,
191
+ offline,
192
+ ...(outPath ? { outPath } : {}),
193
+ ...(credentialsPath ? { credentialsPath } : {}),
194
+ };
195
+ }
196
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
197
+ main().then((exitCode) => {
198
+ process.exitCode = exitCode;
199
+ });
200
+ }
201
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import type { HcpHarnessEventPayload, LocalActionErrorPayload, LocalActionRequestPayload, LocalActionResponsePayload } from "@harness-control/protocol";
2
+ import { LocalCapabilityExecutor, type LocalCapabilityExecutionContext, type LocalCapabilityExecutionEvent } from "./executors.js";
3
+ export type LocalActionContextResolver = (request: LocalActionRequestPayload) => Promise<LocalCapabilityExecutionContext>;
4
+ export type LocalActionEventEmitter = (sessionId: string, turnId: string, events: LocalCapabilityExecutionEvent[]) => HcpHarnessEventPayload[];
5
+ export type LocalActionDispatcherOptions = {
6
+ executor: LocalCapabilityExecutor;
7
+ resolveContext: LocalActionContextResolver;
8
+ emitEvents: LocalActionEventEmitter;
9
+ now?: () => Date;
10
+ };
11
+ export type LocalActionDispatchOutcome = {
12
+ type: "response";
13
+ payload: LocalActionResponsePayload;
14
+ events: HcpHarnessEventPayload[];
15
+ } | {
16
+ type: "error";
17
+ payload: LocalActionErrorPayload;
18
+ events: HcpHarnessEventPayload[];
19
+ };
20
+ export declare class LocalActionDispatcher {
21
+ #private;
22
+ constructor(options: LocalActionDispatcherOptions);
23
+ dispatch(request: LocalActionRequestPayload): Promise<LocalActionDispatchOutcome>;
24
+ duplicatePayloadMismatch(request: LocalActionRequestPayload): LocalActionDispatchOutcome;
25
+ markSessionActive(sessionId: string): void;
26
+ stopDevServersForSession(sessionId: string): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=dispatcher.d.ts.map