@companyhelm/runner 0.1.1 → 0.2.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/RUNTIME_IMAGE_VERSION +1 -1
- package/dist/commands/doctor.js +44 -0
- package/dist/commands/register-commands.js +2 -0
- package/dist/commands/root.js +172 -247
- package/dist/commands/runner/common.js +3 -1
- package/dist/commands/runner/start.js +3 -0
- package/dist/config.js +8 -2
- package/dist/preflight/check.js +2 -0
- package/dist/preflight/checks/linux/apparmor_restrict_unprivileged_userns_check.js +96 -0
- package/dist/preflight/entrypoints.js +53 -0
- package/dist/preflight/runner_preflight.js +56 -0
- package/dist/provisioning/host_provisioning/thread_metadata_store.js +249 -0
- package/dist/provisioning/host_provisioning/thread_metadata_types.js +2 -0
- package/dist/provisioning/host_provisioning/thread_workspace_provisioner.js +57 -0
- package/dist/provisioning/runtime_provisioning/script_renderer.js +120 -0
- package/dist/provisioning/runtime_provisioning/system_prompt.js +44 -0
- package/dist/provisioning/template_renderer.js +29 -0
- package/dist/service/companyhelm_api_client.js +0 -48
- package/dist/service/docker/app_server_container.js +16 -1
- package/dist/service/sdk/refresh_models.js +8 -0
- package/dist/service/thread_lifecycle.js +30 -41
- package/dist/service/thread_turn_state.js +1 -0
- package/dist/templates/provisioning/runtime_agent_metadata.sh.j2 +8 -0
- package/dist/templates/provisioning/runtime_bashrc.sh.j2 +7 -0
- package/dist/templates/provisioning/runtime_codex_config.sh.j2 +7 -0
- package/dist/templates/provisioning/runtime_git_config.sh.j2 +28 -0
- package/dist/templates/provisioning/runtime_identity.sh.j2 +65 -0
- package/dist/templates/provisioning/runtime_thread_git_skills_clone.sh.j2 +11 -0
- package/dist/templates/provisioning/runtime_thread_git_skills_link.sh.j2 +7 -0
- package/dist/templates/provisioning/runtime_tooling_validation.sh.j2 +32 -0
- package/dist/templates/system_prompts/common.md.j2 +37 -0
- package/dist/templates/system_prompts/dedicated_workspace.md.j2 +5 -0
- package/dist/templates/system_prompts/shared_workspace.md.j2 +6 -0
- package/dist/testing/vitest_reporter.js +23 -0
- package/dist/utils/daemon_startup_watchdog.js +27 -0
- package/package.json +2 -2
- package/dist/service/workspace_agents.js +0 -82
- package/dist/templates/runtime_agents.md.j2 +0 -50
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
## Shared Workspace
|
|
2
|
+
|
|
3
|
+
- This runtime is using a shared host workspace mounted directly at `/workspace`.
|
|
4
|
+
- Changes made in `/workspace` may be visible to multiple threads that use the same runner workspace path.
|
|
5
|
+
- Do not assume `/workspace` is isolated per thread.
|
|
6
|
+
- you need to use git worktrees to isolate your working directory from the shared workspace.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VitestReporterResolver = void 0;
|
|
4
|
+
class VitestReporterResolver {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.stdoutIsTTY = options.stdoutIsTTY;
|
|
7
|
+
this.env = options.env ?? process.env;
|
|
8
|
+
}
|
|
9
|
+
resolve() {
|
|
10
|
+
const configured = this.env.COMPANYHELM_VITEST_REPORTER?.trim();
|
|
11
|
+
if (configured && configured.length > 0) {
|
|
12
|
+
return configured
|
|
13
|
+
.split(",")
|
|
14
|
+
.map((value) => value.trim())
|
|
15
|
+
.filter((value) => value.length > 0);
|
|
16
|
+
}
|
|
17
|
+
if (this.stdoutIsTTY) {
|
|
18
|
+
return ["basic"];
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.VitestReporterResolver = VitestReporterResolver;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DaemonStartupWatchdog = void 0;
|
|
4
|
+
class DaemonStartupWatchdog {
|
|
5
|
+
constructor(timeoutMs, onTimeout) {
|
|
6
|
+
this.timeoutMs = timeoutMs;
|
|
7
|
+
this.onTimeout = onTimeout;
|
|
8
|
+
this.bump();
|
|
9
|
+
}
|
|
10
|
+
bump() {
|
|
11
|
+
if (this.timer) {
|
|
12
|
+
clearTimeout(this.timer);
|
|
13
|
+
}
|
|
14
|
+
this.timer = setTimeout(() => {
|
|
15
|
+
this.timer = undefined;
|
|
16
|
+
this.onTimeout();
|
|
17
|
+
}, this.timeoutMs);
|
|
18
|
+
}
|
|
19
|
+
finish() {
|
|
20
|
+
if (!this.timer) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
clearTimeout(this.timer);
|
|
24
|
+
this.timer = undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.DaemonStartupWatchdog = DaemonStartupWatchdog;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@companyhelm/runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Run the CompanyHelm runner in fully isolated Docker sandboxes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@bufbuild/protobuf": "^2.11.0",
|
|
34
34
|
"@clack/prompts": "^1.0.1",
|
|
35
|
-
"@companyhelm/protos": "^0.5.
|
|
35
|
+
"@companyhelm/protos": "^0.5.26",
|
|
36
36
|
"@grpc/grpc-js": "^1.14.3",
|
|
37
37
|
"@libsql/client": "^0.17.0",
|
|
38
38
|
"commander": "^14.0.0",
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renderRuntimeAgentsMd = renderRuntimeAgentsMd;
|
|
4
|
-
exports.ensureWorkspaceAgentsMd = ensureWorkspaceAgentsMd;
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
|
-
const RUNTIME_AGENTS_TEMPLATE_PATH = "templates/runtime_agents.md.j2";
|
|
8
|
-
const DEFAULT_HOME_DIRECTORY = "/home/agent";
|
|
9
|
-
function renderJinjaTemplate(template, context) {
|
|
10
|
-
return template.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_match, key) => {
|
|
11
|
-
const value = context[key];
|
|
12
|
-
if (value === undefined) {
|
|
13
|
-
throw new Error(`Missing template value for key '${key}'`);
|
|
14
|
-
}
|
|
15
|
-
return value;
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function resolveTemplatePath() {
|
|
19
|
-
const distRelativePath = (0, node_path_1.join)(__dirname, "..", RUNTIME_AGENTS_TEMPLATE_PATH);
|
|
20
|
-
if ((0, node_fs_1.existsSync)(distRelativePath)) {
|
|
21
|
-
return distRelativePath;
|
|
22
|
-
}
|
|
23
|
-
const sourceRelativePath = (0, node_path_1.join)(__dirname, "..", "..", "src", RUNTIME_AGENTS_TEMPLATE_PATH);
|
|
24
|
-
if ((0, node_fs_1.existsSync)(sourceRelativePath)) {
|
|
25
|
-
return sourceRelativePath;
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`Runtime AGENTS template was not found at ${distRelativePath} or ${sourceRelativePath}`);
|
|
28
|
-
}
|
|
29
|
-
function extractTopLevelSections(markdown) {
|
|
30
|
-
const sections = [];
|
|
31
|
-
const headingRegex = /^## .+$/gm;
|
|
32
|
-
const matches = [...markdown.matchAll(headingRegex)];
|
|
33
|
-
for (let index = 0; index < matches.length; index += 1) {
|
|
34
|
-
const match = matches[index];
|
|
35
|
-
const marker = match[0].trim();
|
|
36
|
-
const start = match.index ?? 0;
|
|
37
|
-
const end = index + 1 < matches.length ? (matches[index + 1].index ?? markdown.length) : markdown.length;
|
|
38
|
-
const content = markdown.slice(start, end).trimEnd();
|
|
39
|
-
sections.push({ marker, content });
|
|
40
|
-
}
|
|
41
|
-
return sections;
|
|
42
|
-
}
|
|
43
|
-
function renderRuntimeAgentsMd(homeDirectory = DEFAULT_HOME_DIRECTORY) {
|
|
44
|
-
const template = (0, node_fs_1.readFileSync)(resolveTemplatePath(), "utf8");
|
|
45
|
-
return renderJinjaTemplate(template, {
|
|
46
|
-
home_directory: homeDirectory,
|
|
47
|
-
}).trim() + "\n";
|
|
48
|
-
}
|
|
49
|
-
function ensureWorkspaceAgentsMd(workspaceDirectory, homeDirectory = DEFAULT_HOME_DIRECTORY) {
|
|
50
|
-
(0, node_fs_1.mkdirSync)(workspaceDirectory, { recursive: true });
|
|
51
|
-
const agentsPath = (0, node_path_1.join)(workspaceDirectory, "AGENTS.md");
|
|
52
|
-
let existing = "";
|
|
53
|
-
try {
|
|
54
|
-
existing = (0, node_fs_1.readFileSync)(agentsPath, "utf8");
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
existing = "";
|
|
58
|
-
}
|
|
59
|
-
let rendered = "";
|
|
60
|
-
try {
|
|
61
|
-
rendered = renderRuntimeAgentsMd(homeDirectory);
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const templateSections = extractTopLevelSections(rendered);
|
|
67
|
-
const pendingSections = templateSections
|
|
68
|
-
.filter((section) => !existing.includes(section.marker))
|
|
69
|
-
.map((section) => section.content);
|
|
70
|
-
if (pendingSections.length === 0) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const updated = existing.trim()
|
|
74
|
-
? `${existing.trimEnd()}\n\n${pendingSections.join("\n\n")}\n`
|
|
75
|
-
: rendered;
|
|
76
|
-
try {
|
|
77
|
-
(0, node_fs_1.writeFileSync)(agentsPath, updated, "utf8");
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// Best-effort workspace instruction file.
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Agent Instructions
|
|
2
|
-
|
|
3
|
-
## Workspace Structure
|
|
4
|
-
|
|
5
|
-
- You are running in a thread-specific container and workspace
|
|
6
|
-
- This workspace is not initialized as a Git repository by default. Repositories should live in a subdirectory of the workspace.
|
|
7
|
-
|
|
8
|
-
## Docker
|
|
9
|
-
|
|
10
|
-
Docker is available, the docker host runs in a separate container. The network is shared with container.
|
|
11
|
-
- only the `/workspace` is shared with the docker host
|
|
12
|
-
- `{{home_directory}}/.codex/auth.json` is also shared with the docker host
|
|
13
|
-
- Nested DinD is not supported in this environment because the outer runtime already uses rootless DinD.
|
|
14
|
-
- If you need to use Docker in Docker you can mount the docker socket in the container and use this environment DinD to run containers within containers.
|
|
15
|
-
|
|
16
|
-
## GitHub Installations
|
|
17
|
-
|
|
18
|
-
- Synced GitHub installation credentials are written to `/workspace/.companyhelm/installations.json`.
|
|
19
|
-
- Use `list-installations` to inspect installation IDs, repository scopes, tokens, and expiration timestamps.
|
|
20
|
-
- Use `gh-use-installation <installation-id>` to configure `gh` authentication for a specific installation.
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Inspect synced installation credentials
|
|
24
|
-
list-installations
|
|
25
|
-
|
|
26
|
-
# Configure gh to use installation 112331765
|
|
27
|
-
gh-use-installation 112331765
|
|
28
|
-
|
|
29
|
-
# Verify gh is authenticated for github.com
|
|
30
|
-
gh auth status --hostname github.com
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Available CLI Tools
|
|
34
|
-
|
|
35
|
-
- `list-installations`: list synced GitHub installations with repositories, access tokens, and expirations.
|
|
36
|
-
- `gh-use-installation <installation-id>`: configure `gh` authentication for a selected GitHub installation token.
|
|
37
|
-
- `aws`: AWS CLI is pre-installed and available in `PATH`.
|
|
38
|
-
- For scripted PR creation/updates, always use `gh pr create --body-file <path>` and `gh pr edit --body-file <path>` instead of inline `--body` to avoid shell interpolation of markdown backticks.
|
|
39
|
-
- DO NOT INSTALL PLAYWRIGHT IN THE RUNTIME IMAGE. Playwright CLI is already installed and available for browser automation tasks with Chromium pre-installed: `playwright open --browser=chromium ...`
|
|
40
|
-
|
|
41
|
-
## CompanyHelm Agent CLI
|
|
42
|
-
|
|
43
|
-
- `companyhelm-agent` is pre-installed in the runtime image.
|
|
44
|
-
- Thread bootstrap writes `{{home_directory}}/.config/companyhelm-agent-cli/config.json` with:
|
|
45
|
-
- `agent_api_url`: localhost targets are rewritten to `host.docker.internal` (for example `http://host.docker.internal:<port>`) for Docker-to-host access.
|
|
46
|
-
- `token`: sourced from the thread secret.
|
|
47
|
-
- Example commands:
|
|
48
|
-
- `companyhelm-agent task get --task-id <id>`
|
|
49
|
-
- `companyhelm-agent task dependencies --task-id <id>`
|
|
50
|
-
- `companyhelm-agent task update-status --task-id <id> --status <draft|pending|in_progress|completed>`
|