@companyhelm/runner 0.1.1 → 0.1.3

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 (37) hide show
  1. package/RUNTIME_IMAGE_VERSION +1 -1
  2. package/dist/commands/doctor.js +44 -0
  3. package/dist/commands/register-commands.js +2 -0
  4. package/dist/commands/root.js +132 -40
  5. package/dist/commands/runner/common.js +3 -1
  6. package/dist/commands/runner/start.js +3 -0
  7. package/dist/config.js +8 -2
  8. package/dist/preflight/check.js +2 -0
  9. package/dist/preflight/checks/linux/apparmor_restrict_unprivileged_userns_check.js +96 -0
  10. package/dist/preflight/entrypoints.js +53 -0
  11. package/dist/preflight/runner_preflight.js +56 -0
  12. package/dist/provisioning/host_provisioning/thread_metadata_store.js +249 -0
  13. package/dist/provisioning/host_provisioning/thread_metadata_types.js +2 -0
  14. package/dist/provisioning/host_provisioning/thread_workspace_provisioner.js +57 -0
  15. package/dist/provisioning/runtime_provisioning/script_renderer.js +130 -0
  16. package/dist/provisioning/runtime_provisioning/system_prompt.js +43 -0
  17. package/dist/provisioning/template_renderer.js +29 -0
  18. package/dist/service/docker/app_server_container.js +16 -1
  19. package/dist/service/sdk/refresh_models.js +8 -0
  20. package/dist/service/thread_lifecycle.js +46 -24
  21. package/dist/service/thread_turn_state.js +1 -0
  22. package/dist/templates/provisioning/runtime_agent_cli_config.sh.j2 +8 -0
  23. package/dist/templates/provisioning/runtime_agent_metadata.sh.j2 +8 -0
  24. package/dist/templates/provisioning/runtime_bashrc.sh.j2 +7 -0
  25. package/dist/templates/provisioning/runtime_codex_config.sh.j2 +7 -0
  26. package/dist/templates/provisioning/runtime_git_config.sh.j2 +28 -0
  27. package/dist/templates/provisioning/runtime_identity.sh.j2 +65 -0
  28. package/dist/templates/provisioning/runtime_thread_git_skills_clone.sh.j2 +11 -0
  29. package/dist/templates/provisioning/runtime_thread_git_skills_link.sh.j2 +7 -0
  30. package/dist/templates/provisioning/runtime_tooling_validation.sh.j2 +32 -0
  31. package/dist/templates/system_prompts/common.md.j2 +46 -0
  32. package/dist/templates/system_prompts/dedicated_workspace.md.j2 +5 -0
  33. package/dist/templates/system_prompts/shared_workspace.md.j2 +5 -0
  34. package/dist/utils/daemon_startup_watchdog.js +27 -0
  35. package/package.json +1 -1
  36. package/dist/service/workspace_agents.js +0 -82
  37. package/dist/templates/runtime_agents.md.j2 +0 -50
@@ -15,6 +15,7 @@ const dockerode_1 = __importDefault(require("dockerode"));
15
15
  const node_child_process_1 = require("node:child_process");
16
16
  const node_fs_1 = require("node:fs");
17
17
  const node_path_1 = require("node:path");
18
+ const script_renderer_js_1 = require("../provisioning/runtime_provisioning/script_renderer.js");
18
19
  const path_js_1 = require("../utils/path.js");
19
20
  const runtime_bashrc_js_1 = require("./runtime_bashrc.js");
20
21
  const runtime_shell_js_1 = require("./runtime_shell.js");
@@ -193,12 +194,6 @@ function buildRuntimeToolingValidationScript(user) {
193
194
  return [
194
195
  bootstrap,
195
196
  "",
196
- 'if ! command -v companyhelm-agent >/dev/null 2>&1; then',
197
- ' echo "companyhelm-agent CLI is not available after sourcing nvm." >&2',
198
- ' echo "Fix: install @companyhelm/agent-cli in the runtime image." >&2',
199
- " exit 1",
200
- "fi",
201
- "",
202
197
  'if ! command -v aws >/dev/null 2>&1; then',
203
198
  ' echo "aws CLI is not available in runtime PATH." >&2',
204
199
  ' echo "Fix: install awscli in the runtime image." >&2',
@@ -442,6 +437,7 @@ class ThreadContainerService {
442
437
  constructor(docker, runCommand = node_child_process_1.spawnSync) {
443
438
  this.docker = docker ?? new dockerode_1.default();
444
439
  this.runCommand = runCommand;
440
+ this.scriptRenderer = new script_renderer_js_1.RuntimeProvisioningScriptRenderer();
445
441
  }
446
442
  runDockerExecScript(args, contextMessage) {
447
443
  const result = this.runCommand("docker", args, {
@@ -616,46 +612,72 @@ class ThreadContainerService {
616
612
  }
617
613
  }
618
614
  async ensureRuntimeContainerIdentity(name, user) {
619
- const script = buildRuntimeIdentityProvisionScript(user);
615
+ const script = this.scriptRenderer.renderIdentityScript(user);
620
616
  this.runDockerExecScript(["exec", "-u", "0", name, "bash", "-lc", script], `Failed to provision runtime user '${user.agentUser}' in container '${name}'`);
621
617
  }
622
618
  async ensureRuntimeContainerTooling(name, user) {
623
- const script = buildRuntimeToolingValidationScript(user);
624
- this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to validate runtime tooling (nvm/codex/companyhelm-agent/aws/playwright) in container '${name}'`);
619
+ const script = this.scriptRenderer.renderToolingValidationScript(user);
620
+ this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to validate runtime tooling (nvm/codex/aws/playwright) in container '${name}'`);
625
621
  }
626
622
  async ensureRuntimeContainerBashrc(name, user) {
627
- const script = buildRuntimeBashrcProvisionScript(user);
623
+ const script = this.scriptRenderer.renderBashrcScript(user);
628
624
  this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to provision runtime .bashrc in container '${name}'`);
629
625
  }
630
626
  async ensureRuntimeContainerCodexConfig(name, user, configToml) {
631
- const script = [
632
- "set -euo pipefail",
633
- `AGENT_HOME=${shellQuote(user.agentHomeDirectory)}`,
634
- `CONFIG_CONTENT=${shellQuote(configToml)}`,
635
- "",
636
- 'install -d -m 0755 "$AGENT_HOME/.codex"',
637
- 'printf \'%s\' "$CONFIG_CONTENT" > "$AGENT_HOME/.codex/config.toml"',
638
- 'chmod 0644 "$AGENT_HOME/.codex/config.toml"',
639
- ].join("\n");
627
+ const script = this.scriptRenderer.renderCodexConfigScript(user, configToml);
640
628
  this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to write runtime Codex config.toml in container '${name}'`);
641
629
  }
642
630
  async ensureRuntimeContainerAgentCliConfig(name, user, config) {
643
- const script = buildRuntimeAgentCliConfigScript(user, config);
644
- this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to write runtime companyhelm-agent CLI config in container '${name}'`);
631
+ const script = this.scriptRenderer.renderAgentCliConfigScript(user, config);
632
+ this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to write runtime agent config in container '${name}'`);
645
633
  }
646
634
  async ensureRuntimeContainerGitConfig(name, user, gitUserName, gitUserEmail) {
647
- const script = buildRuntimeGitConfigScript(gitUserName, gitUserEmail);
635
+ const script = this.scriptRenderer.renderGitConfigScript(gitUserName, gitUserEmail);
648
636
  this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to configure git author defaults in runtime container '${name}'`);
649
637
  }
650
638
  async ensureRuntimeContainerThreadGitSkills(name, user, options) {
651
639
  if (options.packages.length === 0) {
652
640
  return;
653
641
  }
654
- const cloneScript = buildRuntimeThreadGitSkillsCloneScript(options);
642
+ const cloneScript = this.scriptRenderer.renderThreadGitSkillsCloneScript(options);
655
643
  this.runDockerExecScript(["exec", "-u", "0", name, "bash", "-lc", cloneScript], `Failed to provision thread git skills in runtime container '${name}'`);
656
- const linkScript = buildRuntimeThreadGitSkillsLinkScript(user, options);
644
+ const linkScript = this.scriptRenderer.renderThreadGitSkillsLinkScript(user, options);
657
645
  this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", linkScript], `Failed to provision thread git skills in runtime container '${name}'`);
658
646
  }
647
+ async ensureRuntimeContainerAgentMetadataFiles(name, user, files, contextMessage) {
648
+ if (files.length === 0) {
649
+ return;
650
+ }
651
+ const script = this.scriptRenderer.renderAgentMetadataScript(user, files);
652
+ this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], contextMessage);
653
+ }
654
+ async ensureRuntimeContainerGithubInstallations(name, user, payload) {
655
+ await this.ensureRuntimeContainerAgentMetadataFiles(name, user, [
656
+ {
657
+ filename: "installations.json",
658
+ content: `${JSON.stringify(payload, null, 2)}\n`,
659
+ },
660
+ ], `Failed to write runtime GitHub installations metadata in container '${name}'`);
661
+ }
662
+ async ensureRuntimeContainerThreadMetadata(name, user, payload) {
663
+ const files = [
664
+ {
665
+ filename: "thread-mcp.json",
666
+ content: `${JSON.stringify({ servers: payload.mcpServers }, null, 2)}\n`,
667
+ },
668
+ {
669
+ filename: "thread-git-skills.json",
670
+ content: `${JSON.stringify({ packages: payload.gitSkillPackages }, null, 2)}\n`,
671
+ },
672
+ ];
673
+ if (payload.threadAgentCliConfig) {
674
+ files.push({
675
+ filename: "thread-agent-cli.json",
676
+ content: `${JSON.stringify(payload.threadAgentCliConfig, null, 2)}\n`,
677
+ });
678
+ }
679
+ await this.ensureRuntimeContainerAgentMetadataFiles(name, user, files, `Failed to write runtime thread metadata in container '${name}'`);
680
+ }
659
681
  async stopContainer(name) {
660
682
  try {
661
683
  await this.docker.getContainer(name).stop({ t: 10 });
@@ -12,6 +12,7 @@ async function loadThreadMessageExecutionState(stateDbPath, threadId) {
12
12
  .select({
13
13
  id: schema_js_1.threads.id,
14
14
  workspace: schema_js_1.threads.workspace,
15
+ cliSecret: schema_js_1.threads.cliSecret,
15
16
  sdkThreadId: schema_js_1.threads.sdkThreadId,
16
17
  model: schema_js_1.threads.model,
17
18
  reasoningLevel: schema_js_1.threads.reasoningLevel,
@@ -0,0 +1,8 @@
1
+ set -euo pipefail
2
+ CONFIG_DIR={{config_dir}}
3
+ CONFIG_PATH={{config_path}}
4
+ CONFIG_CONTENT={{config_content}}
5
+
6
+ install -d -m 0755 "$CONFIG_DIR"
7
+ printf '%s' "$CONFIG_CONTENT" > "$CONFIG_PATH"
8
+ chmod 0600 "$CONFIG_PATH"
@@ -0,0 +1,8 @@
1
+ set -euo pipefail
2
+ AGENT_HOME={{agent_home}}
3
+ METADATA_ROOT={{metadata_root}}
4
+
5
+ install -d -m 0755 "$AGENT_HOME/.companyhelm"
6
+ install -d -m 0755 "$METADATA_ROOT"
7
+
8
+ {{file_blocks}}
@@ -0,0 +1,7 @@
1
+ set -euo pipefail
2
+ AGENT_HOME={{agent_home}}
3
+ BASHRC_CONTENT={{bashrc_content}}
4
+
5
+ install -d -m 0755 "$AGENT_HOME"
6
+ printf '%s' "$BASHRC_CONTENT" > "$AGENT_HOME/.bashrc"
7
+ chmod 0644 "$AGENT_HOME/.bashrc"
@@ -0,0 +1,7 @@
1
+ set -euo pipefail
2
+ AGENT_HOME={{agent_home}}
3
+ CONFIG_CONTENT={{config_content}}
4
+
5
+ install -d -m 0755 "$AGENT_HOME/.codex"
6
+ printf '%s' "$CONFIG_CONTENT" > "$AGENT_HOME/.codex/config.toml"
7
+ chmod 0644 "$AGENT_HOME/.codex/config.toml"
@@ -0,0 +1,28 @@
1
+ set -euo pipefail
2
+ DEFAULT_GIT_USER_NAME={{default_git_user_name}}
3
+ DEFAULT_GIT_USER_EMAIL={{default_git_user_email}}
4
+
5
+ if ! command -v git >/dev/null 2>&1; then
6
+ exit 0
7
+ fi
8
+
9
+ if ! git config --global --get user.name >/dev/null 2>&1; then
10
+ git config --global user.name "$DEFAULT_GIT_USER_NAME"
11
+ fi
12
+ if ! git config --global --get user.email >/dev/null 2>&1; then
13
+ git config --global user.email "$DEFAULT_GIT_USER_EMAIL"
14
+ fi
15
+
16
+ if [ ! -d /workspace ]; then
17
+ exit 0
18
+ fi
19
+
20
+ while IFS= read -r -d '' git_dir; do
21
+ repo_root="$(dirname "$git_dir")"
22
+ if ! git -C "$repo_root" config --local --get user.name >/dev/null 2>&1; then
23
+ git -C "$repo_root" config --local user.name "$DEFAULT_GIT_USER_NAME"
24
+ fi
25
+ if ! git -C "$repo_root" config --local --get user.email >/dev/null 2>&1; then
26
+ git -C "$repo_root" config --local user.email "$DEFAULT_GIT_USER_EMAIL"
27
+ fi
28
+ done < <(find /workspace -type d -name .git -print0 2>/dev/null || true)
@@ -0,0 +1,65 @@
1
+ set -euo pipefail
2
+ AGENT_USER={{agent_user}}
3
+ AGENT_HOME={{agent_home}}
4
+ AGENT_UID={{agent_uid}}
5
+ AGENT_GID={{agent_gid}}
6
+
7
+ AGENT_GROUP="$AGENT_USER"
8
+ EXISTING_GID_GROUP="$(getent group "$AGENT_GID" | cut -d: -f1 || true)"
9
+ if [ -n "$EXISTING_GID_GROUP" ] && [ "$EXISTING_GID_GROUP" != "$AGENT_USER" ] && ! getent group "$AGENT_USER" >/dev/null 2>&1; then
10
+ groupmod -n "$AGENT_USER" "$EXISTING_GID_GROUP"
11
+ fi
12
+ if getent group "$AGENT_USER" >/dev/null 2>&1; then
13
+ if [ "$(getent group "$AGENT_USER" | cut -d: -f3)" != "$AGENT_GID" ]; then
14
+ groupmod -g "$AGENT_GID" "$AGENT_USER"
15
+ fi
16
+ else
17
+ groupadd -g "$AGENT_GID" "$AGENT_USER"
18
+ fi
19
+
20
+ EXISTING_UID_USER="$(getent passwd "$AGENT_UID" | cut -d: -f1 || true)"
21
+ if [ -n "$EXISTING_UID_USER" ] && [ "$EXISTING_UID_USER" != "$AGENT_USER" ] && ! id -u "$AGENT_USER" >/dev/null 2>&1; then
22
+ usermod -l "$AGENT_USER" -d "$AGENT_HOME" -g "$AGENT_GROUP" -s /bin/bash "$EXISTING_UID_USER"
23
+ elif id -u "$AGENT_USER" >/dev/null 2>&1; then
24
+ usermod -u "$AGENT_UID" -g "$AGENT_GROUP" -d "$AGENT_HOME" -s /bin/bash "$AGENT_USER"
25
+ else
26
+ useradd -m -d "$AGENT_HOME" -u "$AGENT_UID" -g "$AGENT_GROUP" -s /bin/bash "$AGENT_USER"
27
+ fi
28
+
29
+ install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME"
30
+
31
+ SUDOERS_FILE="/etc/sudoers.d/90-companyhelm-agent"
32
+ if command -v sudo >/dev/null 2>&1; then
33
+ install -d -m 0750 /etc/sudoers.d
34
+ printf '%s\n' "$AGENT_USER ALL=(ALL) NOPASSWD:ALL" > "$SUDOERS_FILE"
35
+ chmod 0440 "$SUDOERS_FILE"
36
+ if command -v visudo >/dev/null 2>&1; then
37
+ visudo -cf "$SUDOERS_FILE" >/dev/null
38
+ fi
39
+ fi
40
+
41
+ install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME/.codex"
42
+ install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME/.cache"
43
+ if [ -e "$AGENT_HOME/.codex/auth.json" ]; then
44
+ chown "$AGENT_UID:$AGENT_GID" "$AGENT_HOME/.codex/auth.json" || true
45
+ fi
46
+
47
+ PLAYWRIGHT_SHARED_CACHE="/ms-playwright"
48
+ PLAYWRIGHT_DEFAULT_CACHE="$AGENT_HOME/.cache/ms-playwright"
49
+ install -d -m 0755 "$PLAYWRIGHT_SHARED_CACHE"
50
+ if [ "$(stat -c '%u:%g' "$PLAYWRIGHT_SHARED_CACHE" 2>/dev/null || true)" != "$AGENT_UID:$AGENT_GID" ]; then
51
+ chown -R "$AGENT_UID:$AGENT_GID" "$PLAYWRIGHT_SHARED_CACHE" || true
52
+ fi
53
+ if [ -L "$PLAYWRIGHT_DEFAULT_CACHE" ]; then
54
+ if [ "$(readlink "$PLAYWRIGHT_DEFAULT_CACHE" 2>/dev/null || true)" != "$PLAYWRIGHT_SHARED_CACHE" ]; then
55
+ rm -f "$PLAYWRIGHT_DEFAULT_CACHE"
56
+ ln -s "$PLAYWRIGHT_SHARED_CACHE" "$PLAYWRIGHT_DEFAULT_CACHE"
57
+ fi
58
+ elif [ ! -e "$PLAYWRIGHT_DEFAULT_CACHE" ]; then
59
+ ln -s "$PLAYWRIGHT_SHARED_CACHE" "$PLAYWRIGHT_DEFAULT_CACHE"
60
+ else
61
+ chown -R "$AGENT_UID:$AGENT_GID" "$PLAYWRIGHT_DEFAULT_CACHE" || true
62
+ fi
63
+ install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME/.companyhelm"
64
+ install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME/.companyhelm/agent"
65
+ chown -R "$AGENT_UID:$AGENT_GID" "$AGENT_HOME" || true
@@ -0,0 +1,11 @@
1
+ set -euo pipefail
2
+ SKILLS_ROOT={{skills_root}}
3
+
4
+ install -d -m 0755 "$SKILLS_ROOT"
5
+
6
+ if ! command -v git >/dev/null 2>&1; then
7
+ echo "git is not available in the runtime container." >&2
8
+ exit 1
9
+ fi
10
+
11
+ {{package_blocks}}
@@ -0,0 +1,7 @@
1
+ set -euo pipefail
2
+ SKILLS_ROOT={{skills_root}}
3
+ CODEX_SKILLS_ROOT={{codex_skills_root}}
4
+
5
+ install -d -m 0755 "$CODEX_SKILLS_ROOT"
6
+
7
+ {{skill_blocks}}
@@ -0,0 +1,32 @@
1
+ {{bootstrap}}
2
+
3
+ if ! command -v aws >/dev/null 2>&1; then
4
+ echo "aws CLI is not available in runtime PATH." >&2
5
+ echo "Fix: install awscli in the runtime image." >&2
6
+ exit 1
7
+ fi
8
+
9
+ if ! command -v playwright >/dev/null 2>&1; then
10
+ echo "playwright CLI is not available after sourcing nvm." >&2
11
+ echo "Fix: install playwright in the runtime image (for example: npm install --global playwright)." >&2
12
+ exit 1
13
+ fi
14
+
15
+ PLAYWRIGHT_CACHE_DIR="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
16
+ if [ ! -d "$PLAYWRIGHT_CACHE_DIR" ]; then
17
+ echo "Playwright browser cache directory is missing: $PLAYWRIGHT_CACHE_DIR" >&2
18
+ echo "Fix: set PLAYWRIGHT_BROWSERS_PATH and run npx playwright install chromium during runtime image build." >&2
19
+ exit 1
20
+ fi
21
+
22
+ CHROMIUM_BIN="$(find "$PLAYWRIGHT_CACHE_DIR" -type f -path "*/chrome-linux/chrome" -print -quit 2>/dev/null || true)"
23
+ if [ -z "$CHROMIUM_BIN" ]; then
24
+ echo "Playwright chromium binary is missing under $PLAYWRIGHT_CACHE_DIR." >&2
25
+ echo "Fix: run npx playwright install chromium while building the runtime image." >&2
26
+ exit 1
27
+ fi
28
+
29
+ if [ ! -x "$CHROMIUM_BIN" ]; then
30
+ echo "Playwright chromium binary exists but is not executable: $CHROMIUM_BIN" >&2
31
+ exit 1
32
+ fi
@@ -0,0 +1,46 @@
1
+ # Agent Instructions
2
+
3
+ ## Workspace Structure
4
+
5
+ - You are running in a thread-specific container and workspace.
6
+ - This workspace may be shared or dedicated depending on runner startup configuration.
7
+ - Repositories should live in subdirectories of `/workspace`.
8
+
9
+ ## Docker
10
+
11
+ Docker is available, the docker host runs in a separate container. The network is shared with the container.
12
+ - Only `/workspace` is shared with the docker host.
13
+ - `{{home_directory}}/.codex/auth.json` is also shared with the docker host.
14
+ - Nested DinD is not supported in this environment because the outer runtime already uses rootless DinD.
15
+ - If you need Docker in Docker, mount the configured host runtime instead of trying nested DinD.
16
+
17
+ ## GitHub Installations
18
+
19
+ - Synced GitHub installation credentials are stored at `{{home_directory}}/.companyhelm/agent/installations.json`.
20
+ - Use `list-installations` to inspect installation IDs, repository scopes, tokens, and expiration timestamps.
21
+ - Use `gh-use-installation <installation-id>` to configure `gh` authentication for a specific installation.
22
+
23
+ ```bash
24
+ list-installations
25
+ gh-use-installation {installation_id}
26
+ gh auth status --hostname github.com
27
+ ```
28
+
29
+ ## Available CLI Tools
30
+
31
+ - `list-installations`: list synced GitHub installations with repositories, access tokens, and expirations.
32
+ - `gh-use-installation <installation-id>`: configure `gh` authentication for a selected GitHub installation token.
33
+ - `aws`: AWS CLI is pre-installed and available in `PATH`.
34
+ - For scripted PR creation and updates, use `gh pr create --body-file <path>` and `gh pr edit --body-file <path>`.
35
+ - Playwright CLI is already installed and available with Chromium pre-installed.
36
+
37
+ ## Agent API
38
+
39
+ - Use the CompanyHelm agent REST API directly with `Authorization: Bearer {{agent_token}}`.
40
+ - Agent API base URL: `{{agent_api_url}}`
41
+ - The bearer token above is the existing thread secret.
42
+
43
+ ```bash
44
+ curl -H "Authorization: Bearer {{agent_token}}" \
45
+ {{agent_api_url}}/
46
+ ```
@@ -0,0 +1,5 @@
1
+ ## Dedicated Workspace
2
+
3
+ - This runtime is using a dedicated per-thread host workspace mounted at `/workspace`.
4
+ - Files in `/workspace` are isolated to this thread workspace lifecycle.
5
+ - The runner may remove the host workspace when the thread is deleted.
@@ -0,0 +1,5 @@
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.
@@ -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.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Run the CompanyHelm runner in fully isolated Docker sandboxes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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>`