@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,110 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { open, readFile, realpath, rename, rm, stat } from "node:fs/promises";
3
+ import { isAbsolute, relative, sep } from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { z } from "zod";
6
+ import { RunnerConfigSchema } from "../config/index.js";
7
+ export class WorkspaceManager {
8
+ config;
9
+ configPath;
10
+ sessions;
11
+ constructor(config, configPath, sessions) {
12
+ this.config = config;
13
+ this.configPath = configPath;
14
+ this.sessions = sessions;
15
+ }
16
+ snapshot() {
17
+ return {
18
+ revision: this.config.workspace_revision ?? createHash("sha256").update(JSON.stringify(this.config.workspaces)).digest("hex"),
19
+ allowed_roots: this.configPath ? this.config.workspace_management?.allowed_roots ?? [] : [],
20
+ };
21
+ }
22
+ async execute(requestId, request) {
23
+ let outcome;
24
+ try {
25
+ if (request.operation.kind === "list") {
26
+ if (Date.parse(request.expires_at) <= Date.now())
27
+ throw new Error("Request expired. Refresh workspaces.");
28
+ }
29
+ else {
30
+ await this.sessions.updateWorkspaceConfiguration(() => this.change(request));
31
+ }
32
+ outcome = { kind: "success" };
33
+ }
34
+ catch (error) {
35
+ outcome = { kind: "error", message: error instanceof Error ? error.message.slice(0, 1000) : "Workspace update failed." };
36
+ }
37
+ return { request_id: requestId, outcome, management: this.snapshot(), workspaces: this.config.workspaces.map(workspace => ({ ...workspace })) };
38
+ }
39
+ async change(request) {
40
+ if (Date.parse(request.expires_at) <= Date.now())
41
+ throw new Error("Request expired. Refresh workspaces.");
42
+ const configPath = this.configPath;
43
+ const roots = this.snapshot().allowed_roots;
44
+ if (!configPath || roots.length === 0)
45
+ throw new Error("Workspace management is disabled. Configure workspace_management.allowed_roots locally, then restart the runner.");
46
+ if (request.expected_revision !== this.snapshot().revision)
47
+ throw new Error("Workspaces changed. Refresh and try again.");
48
+ const operation = request.operation;
49
+ let workspaces = this.config.workspaces.map(workspace => ({ ...workspace }));
50
+ if (operation.kind === "add") {
51
+ if (!isAbsolute(operation.path))
52
+ throw new Error("Use an absolute folder path on this machine.");
53
+ const path = await realpath(operation.path);
54
+ if (!(await stat(path)).isDirectory())
55
+ throw new Error("The workspace must be an existing folder.");
56
+ const canonicalRoots = await Promise.all(roots.map(root => realpath(root)));
57
+ if (!canonicalRoots.some(root => contained(root, path)))
58
+ throw new Error("The folder is outside this machine’s allowed roots.");
59
+ const existingPaths = await Promise.all(workspaces.map(async (workspace) => {
60
+ try {
61
+ return await realpath(workspace.path);
62
+ }
63
+ catch (error) {
64
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
65
+ return workspace.path;
66
+ throw error;
67
+ }
68
+ }));
69
+ if (existingPaths.includes(path))
70
+ throw new Error("This folder is already registered.");
71
+ workspaces.push({ id: randomUUID(), path, display_name: operation.display_name });
72
+ }
73
+ else if (operation.kind === "rename" || operation.kind === "remove") {
74
+ const workspace = workspaces.find(entry => entry.id === operation.id);
75
+ if (!workspace)
76
+ throw new Error("Workspace no longer exists. Refresh the list.");
77
+ if (operation.kind === "rename")
78
+ workspace.display_name = operation.display_name;
79
+ else
80
+ workspaces = workspaces.filter(entry => entry.id !== operation.id);
81
+ }
82
+ const raw = z.record(z.string(), z.unknown()).parse(JSON.parse(await readFile(configPath, "utf8")));
83
+ if (!isDeepStrictEqual(RunnerConfigSchema.parse(raw), this.config))
84
+ throw new Error("Runner configuration changed on disk. Restart the runner before managing workspaces.");
85
+ const revision = randomUUID();
86
+ RunnerConfigSchema.parse({ ...raw, workspaces, workspace_revision: revision });
87
+ const temporaryPath = `${configPath}.${randomUUID()}.tmp`;
88
+ try {
89
+ const file = await open(temporaryPath, "wx", 0o600);
90
+ try {
91
+ await file.writeFile(`${JSON.stringify({ ...raw, workspaces, workspace_revision: revision }, null, 2)}\n`);
92
+ await file.sync();
93
+ }
94
+ finally {
95
+ await file.close();
96
+ }
97
+ await rename(temporaryPath, configPath);
98
+ this.config.workspaces = workspaces;
99
+ this.config.workspace_revision = revision;
100
+ }
101
+ finally {
102
+ await rm(temporaryPath, { force: true });
103
+ }
104
+ }
105
+ }
106
+ function contained(root, path) {
107
+ const child = relative(root, path);
108
+ return child === "" || (!isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`));
109
+ }
110
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@harness-control/runner",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0",
5
+ "type": "module",
6
+ "bin": {
7
+ "hcp-runner": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./connection": {
17
+ "types": "./dist/connection/index.d.ts",
18
+ "import": "./dist/connection/index.js"
19
+ },
20
+ "./config": {
21
+ "types": "./dist/config/index.d.ts",
22
+ "import": "./dist/config/index.js"
23
+ },
24
+ "./harnesses": {
25
+ "types": "./dist/harnesses/index.d.ts",
26
+ "import": "./dist/harnesses/index.js"
27
+ },
28
+ "./mcp": {
29
+ "types": "./dist/mcp/index.d.ts",
30
+ "import": "./dist/mcp/index.js"
31
+ },
32
+ "./state": {
33
+ "types": "./dist/state/index.d.ts",
34
+ "import": "./dist/state/index.js"
35
+ },
36
+ "./pairing": {
37
+ "types": "./dist/pairing/index.d.ts",
38
+ "import": "./dist/pairing/index.js"
39
+ }
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -b",
43
+ "check": "tsc -b --pretty false",
44
+ "test": "node --import tsx --test --test-concurrency=1 src/**/*.test.ts"
45
+ },
46
+ "dependencies": {
47
+ "@anthropic-ai/claude-agent-sdk": "0.3.267",
48
+ "@harness-control/protocol": "0.1.0",
49
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
+ "ws": "^8.21.0",
51
+ "zod": "^4.4.3"
52
+ },
53
+ "devDependencies": {
54
+ "@types/ws": "^8.18.1"
55
+ },
56
+ "description": "Local coding-agent runner for Harness Control Protocol",
57
+ "engines": {
58
+ "node": ">=22"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/qazisaad/harness-control-protocol.git",
66
+ "directory": "packages/hcp-runner"
67
+ },
68
+ "files": [
69
+ "dist",
70
+ "!dist/**/*.test.*",
71
+ "!dist/**/test/**",
72
+ "!dist/**/*.map",
73
+ "README.md",
74
+ "LICENSE"
75
+ ]
76
+ }