@companyhelm/cli 0.0.2 → 0.0.6
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/README.md +24 -62
- package/RUNTIME_IMAGE_VERSION +1 -1
- package/dist/cli.js +29 -1
- package/dist/commands/register-commands.js +2 -0
- package/dist/commands/root.js +341 -20
- package/dist/commands/startup.js +138 -55
- package/dist/commands/status.js +32 -0
- package/dist/service/app_server.js +23 -9
- package/dist/service/docker/app_server_container.js +3 -1
- package/dist/service/thread_lifecycle.js +4 -1
- package/dist/state/daemon_state.js +83 -0
- package/dist/state/schema.js +9 -1
- package/dist/templates/app_server_bootstrap.sh.j2 +46 -0
- package/dist/templates/runtime_agents.md.j2 +50 -0
- package/dist/templates/runtime_bashrc.j2 +19 -0
- package/dist/utils/daemon.js +15 -0
- package/dist/utils/process.js +22 -0
- package/drizzle/0011_actual_lucky.sql +7 -0
- package/drizzle/meta/_journal.json +8 -1
- package/package.json +7 -3
- package/dist/commands/agent/index.js +0 -10
- package/dist/commands/agent/list.js +0 -31
- package/dist/commands/agent/register-agent-commands.js +0 -10
- package/dist/commands/index.js +0 -15
- package/dist/commands/sdk/index.js +0 -12
- package/dist/commands/thread/index.js +0 -12
- package/dist/config/local.js +0 -1
- package/dist/config/schema.js +0 -7
- package/dist/model.js +0 -22
- package/dist/schema.js +0 -47
- package/dist/service/docker/docker_provider.js +0 -1
- package/dist/service/docker/runtime_container.js +0 -1
- package/dist/service/docker/runtime_image.js +0 -40
- package/dist/startup.js +0 -166
- package/dist/state/service/app_server.js +0 -392
- package/dist/state/service/buffered_client_message_sender.js +0 -73
- package/dist/state/service/companyhelm_api_client.js +0 -316
- package/dist/state/service/docker/app_server_container.js +0 -165
- package/dist/state/service/docker/dind.js +0 -114
- package/dist/state/service/docker/runtime_app_server_exec.js +0 -95
- package/dist/state/service/host.js +0 -15
- package/dist/state/service/runtime_shell.js +0 -23
- package/dist/state/service/sdk/refresh_models.js +0 -83
- package/dist/state/service/thread_lifecycle.js +0 -327
- package/dist/state/service/thread_runtime.js +0 -11
- package/dist/state/service/thread_turn_state.js +0 -45
- package/dist/state/service/workspace_agents.js +0 -115
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getHostInfo = getHostInfo;
|
|
4
|
-
const node_os_1 = require("node:os");
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
|
-
const path_js_1 = require("../utils/path.js");
|
|
7
|
-
function getHostInfo(codexAuthPath) {
|
|
8
|
-
const info = (0, node_os_1.userInfo)();
|
|
9
|
-
return {
|
|
10
|
-
uid: info.uid,
|
|
11
|
-
gid: info.gid,
|
|
12
|
-
home: (0, node_os_1.homedir)(),
|
|
13
|
-
codexAuthExists: (0, node_fs_1.existsSync)((0, path_js_1.expandHome)(codexAuthPath)),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildNvmCodexBootstrapScript = buildNvmCodexBootstrapScript;
|
|
4
|
-
exports.buildCodexAppServerCommand = buildCodexAppServerCommand;
|
|
5
|
-
function shellQuote(value) {
|
|
6
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
7
|
-
}
|
|
8
|
-
const CODEX_APP_SERVER_STDIN_LISTEN_FLAG = "--listen stdio://";
|
|
9
|
-
const CODEX_YOLO_FLAG = "--dangerously-bypass-approvals-and-sandbox";
|
|
10
|
-
function buildNvmCodexBootstrapScript(homeDirectory) {
|
|
11
|
-
const lines = [
|
|
12
|
-
"set -euo pipefail",
|
|
13
|
-
];
|
|
14
|
-
if (homeDirectory) {
|
|
15
|
-
lines.push(`export HOME=${shellQuote(homeDirectory)}`);
|
|
16
|
-
}
|
|
17
|
-
lines.push('if [ -z "${NVM_DIR:-}" ] || [ ! -s "$NVM_DIR/nvm.sh" ]; then', ' for candidate in "/usr/local/nvm" "$HOME/.nvm" "/opt/nvm" "/root/.nvm"; do', ' if [ -s "$candidate/nvm.sh" ]; then', ' export NVM_DIR="$candidate"', " break", " fi", " done", "fi", 'if [ -z "${NVM_DIR:-}" ] || [ ! -s "$NVM_DIR/nvm.sh" ]; then', ' echo "nvm is not configured: unable to locate nvm.sh." >&2', " exit 1", "fi", '. "$NVM_DIR/nvm.sh"', 'nvm use --silent default >/dev/null 2>&1 || true', 'if ! command -v codex >/dev/null 2>&1; then', ' echo "codex command is not available after sourcing nvm." >&2', " exit 1", "fi");
|
|
18
|
-
return lines.join("\n");
|
|
19
|
-
}
|
|
20
|
-
function buildCodexAppServerCommand(homeDirectory) {
|
|
21
|
-
const bootstrap = buildNvmCodexBootstrapScript(homeDirectory);
|
|
22
|
-
return `${bootstrap}\ncodex ${CODEX_YOLO_FLAG} app-server ${CODEX_APP_SERVER_STDIN_LISTEN_FLAG}`;
|
|
23
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.refreshSdkModels = refreshSdkModels;
|
|
4
|
-
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
-
const config_js_1 = require("../../config.js");
|
|
6
|
-
const db_js_1 = require("../../state/db.js");
|
|
7
|
-
const schema_js_1 = require("../../state/schema.js");
|
|
8
|
-
const app_server_js_1 = require("../app_server.js");
|
|
9
|
-
const app_server_container_js_1 = require("../docker/app_server_container.js");
|
|
10
|
-
async function fetchCodexModelsFromAppServer(clientName, logger) {
|
|
11
|
-
const transport = new app_server_container_js_1.AppServerContainerService();
|
|
12
|
-
const appServer = new app_server_js_1.AppServerService(transport, clientName, logger);
|
|
13
|
-
await appServer.start();
|
|
14
|
-
const models = [];
|
|
15
|
-
let nextCursor = null;
|
|
16
|
-
try {
|
|
17
|
-
while (true) {
|
|
18
|
-
const result = await appServer.listModels(nextCursor, 100);
|
|
19
|
-
models.push(...result.data);
|
|
20
|
-
if (!result.nextCursor) {
|
|
21
|
-
break;
|
|
22
|
-
}
|
|
23
|
-
nextCursor = result.nextCursor;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
finally {
|
|
27
|
-
await appServer.stop();
|
|
28
|
-
}
|
|
29
|
-
return models;
|
|
30
|
-
}
|
|
31
|
-
async function refreshCodexModels(cfg, logger) {
|
|
32
|
-
const models = await fetchCodexModelsFromAppServer(cfg.codex.app_server_client_name, logger);
|
|
33
|
-
const { db, client } = await (0, db_js_1.initDb)(cfg.state_db_path);
|
|
34
|
-
try {
|
|
35
|
-
await db.delete(schema_js_1.llmModels).where((0, drizzle_orm_1.eq)(schema_js_1.llmModels.sdkName, "codex"));
|
|
36
|
-
if (models.length > 0) {
|
|
37
|
-
await db.insert(schema_js_1.llmModels).values(models.map((model) => ({
|
|
38
|
-
name: model.model,
|
|
39
|
-
sdkName: "codex",
|
|
40
|
-
reasoningLevels: model.supportedReasoningEfforts.map((effort) => effort.reasoningEffort),
|
|
41
|
-
})));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
client.close();
|
|
46
|
-
}
|
|
47
|
-
return models.length;
|
|
48
|
-
}
|
|
49
|
-
async function refreshSdkModels(options) {
|
|
50
|
-
const cfg = config_js_1.config.parse({});
|
|
51
|
-
const { db, client } = await (0, db_js_1.initDb)(cfg.state_db_path);
|
|
52
|
-
let selectedSdks = [];
|
|
53
|
-
try {
|
|
54
|
-
if (options?.sdk) {
|
|
55
|
-
const configured = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, options.sdk)).get();
|
|
56
|
-
if (!configured) {
|
|
57
|
-
throw new Error(`SDK '${options.sdk}' is not configured.`);
|
|
58
|
-
}
|
|
59
|
-
selectedSdks = [configured];
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
selectedSdks = await db.select().from(schema_js_1.agentSdks).all();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
finally {
|
|
66
|
-
client.close();
|
|
67
|
-
}
|
|
68
|
-
if (selectedSdks.length === 0) {
|
|
69
|
-
throw new Error("No SDKs are configured.");
|
|
70
|
-
}
|
|
71
|
-
const results = [];
|
|
72
|
-
for (const sdk of selectedSdks) {
|
|
73
|
-
if (!sdk.authentication || sdk.authentication === "unauthenticated") {
|
|
74
|
-
throw new Error(`SDK '${sdk.name}' is missing authentication.`);
|
|
75
|
-
}
|
|
76
|
-
if (sdk.name !== "codex") {
|
|
77
|
-
throw new Error(`SDK '${sdk.name}' is not supported by model refresh yet.`);
|
|
78
|
-
}
|
|
79
|
-
const modelCount = await refreshCodexModels(cfg, options?.logger);
|
|
80
|
-
results.push({ sdk: sdk.name, modelCount });
|
|
81
|
-
}
|
|
82
|
-
return results;
|
|
83
|
-
}
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ThreadContainerService = void 0;
|
|
7
|
-
exports.buildThreadContainerNames = buildThreadContainerNames;
|
|
8
|
-
exports.resolveThreadsRootDirectory = resolveThreadsRootDirectory;
|
|
9
|
-
exports.resolveThreadDirectory = resolveThreadDirectory;
|
|
10
|
-
exports.buildSharedThreadMounts = buildSharedThreadMounts;
|
|
11
|
-
exports.buildDindContainerOptions = buildDindContainerOptions;
|
|
12
|
-
exports.buildRuntimeContainerOptions = buildRuntimeContainerOptions;
|
|
13
|
-
const dockerode_1 = __importDefault(require("dockerode"));
|
|
14
|
-
const node_child_process_1 = require("node:child_process");
|
|
15
|
-
const node_path_1 = require("node:path");
|
|
16
|
-
const path_js_1 = require("../utils/path.js");
|
|
17
|
-
const runtime_shell_js_1 = require("./runtime_shell.js");
|
|
18
|
-
const CONTAINER_START_TIMEOUT_MS = 30000;
|
|
19
|
-
const CONTAINER_START_POLL_MS = 500;
|
|
20
|
-
function resolveContainerPath(path, containerHome) {
|
|
21
|
-
if (path === "~") {
|
|
22
|
-
return containerHome;
|
|
23
|
-
}
|
|
24
|
-
if (path.startsWith("~/")) {
|
|
25
|
-
return `${containerHome}${path.slice(1)}`;
|
|
26
|
-
}
|
|
27
|
-
return path;
|
|
28
|
-
}
|
|
29
|
-
function buildThreadContainerNames(threadId) {
|
|
30
|
-
return {
|
|
31
|
-
dind: `companyhelm-dind-thread-${threadId}`,
|
|
32
|
-
runtime: `companyhelm-runtime-thread-${threadId}`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function resolveThreadsRootDirectory(configDirectory, threadsDirectory) {
|
|
36
|
-
const expandedThreadsDirectory = (0, path_js_1.expandHome)(threadsDirectory);
|
|
37
|
-
if ((0, node_path_1.isAbsolute)(expandedThreadsDirectory)) {
|
|
38
|
-
return expandedThreadsDirectory;
|
|
39
|
-
}
|
|
40
|
-
return (0, node_path_1.join)((0, path_js_1.expandHome)(configDirectory), expandedThreadsDirectory);
|
|
41
|
-
}
|
|
42
|
-
function resolveThreadDirectory(configDirectory, threadsDirectory, agentId, threadId) {
|
|
43
|
-
return (0, node_path_1.join)(resolveThreadsRootDirectory(configDirectory, threadsDirectory), `agent-${agentId}`, `thread-${threadId}`);
|
|
44
|
-
}
|
|
45
|
-
function buildSharedThreadMounts(options) {
|
|
46
|
-
const mounts = [
|
|
47
|
-
{
|
|
48
|
-
Type: "bind",
|
|
49
|
-
Source: options.threadDirectory,
|
|
50
|
-
Target: "/workspace",
|
|
51
|
-
},
|
|
52
|
-
];
|
|
53
|
-
if (options.codexAuthMode === "dedicated") {
|
|
54
|
-
mounts.push({
|
|
55
|
-
Type: "bind",
|
|
56
|
-
Source: (0, node_path_1.join)((0, path_js_1.expandHome)(options.configDirectory), options.codexAuthFilePath),
|
|
57
|
-
Target: resolveContainerPath(options.codexAuthPath, options.containerHomeDirectory),
|
|
58
|
-
});
|
|
59
|
-
return mounts;
|
|
60
|
-
}
|
|
61
|
-
const hostAuthPath = (0, path_js_1.expandHome)(options.codexAuthPath);
|
|
62
|
-
mounts.push({
|
|
63
|
-
Type: "bind",
|
|
64
|
-
Source: hostAuthPath,
|
|
65
|
-
Target: hostAuthPath,
|
|
66
|
-
});
|
|
67
|
-
return mounts;
|
|
68
|
-
}
|
|
69
|
-
function buildCommonContainerEnv(user) {
|
|
70
|
-
return [
|
|
71
|
-
`HOME=${user.agentHomeDirectory}`,
|
|
72
|
-
`USER=${user.agentUser}`,
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
function shellQuote(value) {
|
|
76
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
77
|
-
}
|
|
78
|
-
function buildRuntimeIdentityProvisionScript(user) {
|
|
79
|
-
return [
|
|
80
|
-
"set -euo pipefail",
|
|
81
|
-
`AGENT_USER=${shellQuote(user.agentUser)}`,
|
|
82
|
-
`AGENT_HOME=${shellQuote(user.agentHomeDirectory)}`,
|
|
83
|
-
`AGENT_UID=${shellQuote(String(user.uid))}`,
|
|
84
|
-
`AGENT_GID=${shellQuote(String(user.gid))}`,
|
|
85
|
-
"",
|
|
86
|
-
'install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME"',
|
|
87
|
-
"",
|
|
88
|
-
'AGENT_GROUP="$AGENT_USER"',
|
|
89
|
-
'if getent group "$AGENT_GID" >/dev/null 2>&1; then',
|
|
90
|
-
' AGENT_GROUP="$(getent group "$AGENT_GID" | cut -d: -f1)"',
|
|
91
|
-
'elif getent group "$AGENT_USER" >/dev/null 2>&1; then',
|
|
92
|
-
' groupmod -g "$AGENT_GID" "$AGENT_USER"',
|
|
93
|
-
' AGENT_GROUP="$AGENT_USER"',
|
|
94
|
-
"else",
|
|
95
|
-
' groupadd -g "$AGENT_GID" "$AGENT_USER"',
|
|
96
|
-
' AGENT_GROUP="$AGENT_USER"',
|
|
97
|
-
"fi",
|
|
98
|
-
"",
|
|
99
|
-
'if id -u "$AGENT_USER" >/dev/null 2>&1; then',
|
|
100
|
-
' usermod -u "$AGENT_UID" -g "$AGENT_GROUP" -d "$AGENT_HOME" "$AGENT_USER" || true',
|
|
101
|
-
"else",
|
|
102
|
-
' useradd -m -d "$AGENT_HOME" -u "$AGENT_UID" -g "$AGENT_GROUP" -s /bin/bash "$AGENT_USER"',
|
|
103
|
-
"fi",
|
|
104
|
-
"",
|
|
105
|
-
'install -d -m 0755 -o "$AGENT_UID" -g "$AGENT_GID" "$AGENT_HOME/.codex"',
|
|
106
|
-
'if [ -e "$AGENT_HOME/.codex/auth.json" ]; then',
|
|
107
|
-
' chown "$AGENT_UID:$AGENT_GID" "$AGENT_HOME/.codex/auth.json" || true',
|
|
108
|
-
"fi",
|
|
109
|
-
'chown -R "$AGENT_UID:$AGENT_GID" "$AGENT_HOME" || true',
|
|
110
|
-
].join("\n");
|
|
111
|
-
}
|
|
112
|
-
function buildRuntimeToolingValidationScript(user) {
|
|
113
|
-
return (0, runtime_shell_js_1.buildNvmCodexBootstrapScript)(user.agentHomeDirectory);
|
|
114
|
-
}
|
|
115
|
-
function buildDindContainerOptions(options) {
|
|
116
|
-
return {
|
|
117
|
-
name: options.names.dind,
|
|
118
|
-
Image: options.dindImage,
|
|
119
|
-
WorkingDir: "/workspace",
|
|
120
|
-
Env: [
|
|
121
|
-
"DOCKER_TLS_CERTDIR=",
|
|
122
|
-
],
|
|
123
|
-
HostConfig: {
|
|
124
|
-
Privileged: true,
|
|
125
|
-
Mounts: options.mounts,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function buildRuntimeContainerOptions(options) {
|
|
130
|
-
return {
|
|
131
|
-
name: options.names.runtime,
|
|
132
|
-
Image: options.runtimeImage,
|
|
133
|
-
User: `${options.user.uid}:${options.user.gid}`,
|
|
134
|
-
WorkingDir: "/workspace",
|
|
135
|
-
Env: [
|
|
136
|
-
...buildCommonContainerEnv(options.user),
|
|
137
|
-
"DOCKER_HOST=tcp://localhost:2375",
|
|
138
|
-
],
|
|
139
|
-
Cmd: ["sleep", "infinity"],
|
|
140
|
-
HostConfig: {
|
|
141
|
-
NetworkMode: `container:${options.names.dind}`,
|
|
142
|
-
Mounts: options.mounts,
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function isContainerNotFound(error) {
|
|
147
|
-
if (typeof error !== "object" || error === null) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
const statusCode = "statusCode" in error ? error.statusCode : undefined;
|
|
151
|
-
if (statusCode === 404) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
-
return /No such container/i.test(message);
|
|
156
|
-
}
|
|
157
|
-
function isContainerAlreadyStarted(error) {
|
|
158
|
-
if (typeof error !== "object" || error === null) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
const statusCode = "statusCode" in error ? error.statusCode : undefined;
|
|
162
|
-
if (statusCode === 304) {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
-
return /already started/i.test(message);
|
|
167
|
-
}
|
|
168
|
-
function isContainerNotRunning(error) {
|
|
169
|
-
if (typeof error !== "object" || error === null) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
const statusCode = "statusCode" in error ? error.statusCode : undefined;
|
|
173
|
-
if (statusCode === 304) {
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
-
return /is not running/i.test(message);
|
|
178
|
-
}
|
|
179
|
-
function isImageNotFound(error) {
|
|
180
|
-
if (typeof error !== "object" || error === null) {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
const statusCode = "statusCode" in error ? error.statusCode : undefined;
|
|
184
|
-
if (statusCode === 404) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
-
return /No such image/i.test(message);
|
|
189
|
-
}
|
|
190
|
-
function toErrorMessage(error) {
|
|
191
|
-
return error instanceof Error ? error.message : String(error);
|
|
192
|
-
}
|
|
193
|
-
class ThreadContainerService {
|
|
194
|
-
constructor(docker, runCommand = node_child_process_1.spawnSync) {
|
|
195
|
-
this.docker = docker ?? new dockerode_1.default();
|
|
196
|
-
this.runCommand = runCommand;
|
|
197
|
-
}
|
|
198
|
-
runDockerExecScript(args, contextMessage) {
|
|
199
|
-
const result = this.runCommand("docker", args, {
|
|
200
|
-
encoding: "utf8",
|
|
201
|
-
});
|
|
202
|
-
if (result.status === 0 && !result.error) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const detail = [result.error?.message, result.stderr, result.stdout]
|
|
206
|
-
.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
207
|
-
.map((value) => value.trim())
|
|
208
|
-
.join(" | ");
|
|
209
|
-
const status = result.status === null ? "unknown" : String(result.status);
|
|
210
|
-
throw new Error(`${contextMessage} (exit ${status})${detail ? `: ${detail}` : "."}`);
|
|
211
|
-
}
|
|
212
|
-
async pullImage(image) {
|
|
213
|
-
await new Promise((resolve, reject) => {
|
|
214
|
-
this.docker.pull(image, (error, stream) => {
|
|
215
|
-
if (error) {
|
|
216
|
-
reject(error);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (!stream) {
|
|
220
|
-
reject(new Error(`Docker returned an empty stream while pulling image '${image}'.`));
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const modem = this.docker.modem;
|
|
224
|
-
if (!modem?.followProgress) {
|
|
225
|
-
resolve();
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
modem.followProgress(stream, (pullError) => {
|
|
229
|
-
if (pullError) {
|
|
230
|
-
reject(pullError);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
resolve();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
async ensureImageAvailable(image) {
|
|
239
|
-
try {
|
|
240
|
-
await this.docker.getImage(image).inspect();
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
if (!isImageNotFound(error)) {
|
|
245
|
-
throw error;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
await this.pullImage(image);
|
|
249
|
-
}
|
|
250
|
-
async createThreadContainers(options) {
|
|
251
|
-
await this.ensureImageAvailable(options.dindImage);
|
|
252
|
-
if (options.runtimeImage !== options.dindImage) {
|
|
253
|
-
await this.ensureImageAvailable(options.runtimeImage);
|
|
254
|
-
}
|
|
255
|
-
await this.docker.createContainer(buildDindContainerOptions(options));
|
|
256
|
-
try {
|
|
257
|
-
await this.docker.createContainer(buildRuntimeContainerOptions(options));
|
|
258
|
-
}
|
|
259
|
-
catch (error) {
|
|
260
|
-
await this.forceRemoveContainer(options.names.dind);
|
|
261
|
-
throw new Error(toErrorMessage(error));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async startContainer(name) {
|
|
265
|
-
const container = this.docker.getContainer(name);
|
|
266
|
-
try {
|
|
267
|
-
await container.start();
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
if (isContainerAlreadyStarted(error)) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
throw error;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
async waitForContainerRunning(name, timeoutMs = CONTAINER_START_TIMEOUT_MS) {
|
|
277
|
-
const container = this.docker.getContainer(name);
|
|
278
|
-
const deadline = Date.now() + timeoutMs;
|
|
279
|
-
while (Date.now() < deadline) {
|
|
280
|
-
const details = await container.inspect();
|
|
281
|
-
const state = details.State;
|
|
282
|
-
if (state?.Running) {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (state?.Status === "exited" || state?.Status === "dead") {
|
|
286
|
-
throw new Error(`Container '${name}' is not running (status=${state.Status}, exitCode=${state.ExitCode ?? "unknown"}).`);
|
|
287
|
-
}
|
|
288
|
-
await new Promise((resolve) => setTimeout(resolve, CONTAINER_START_POLL_MS));
|
|
289
|
-
}
|
|
290
|
-
throw new Error(`Container '${name}' did not reach running state within ${timeoutMs}ms.`);
|
|
291
|
-
}
|
|
292
|
-
async ensureContainerRunning(name, timeoutMs = CONTAINER_START_TIMEOUT_MS) {
|
|
293
|
-
await this.startContainer(name);
|
|
294
|
-
await this.waitForContainerRunning(name, timeoutMs);
|
|
295
|
-
}
|
|
296
|
-
async ensureRuntimeContainerIdentity(name, user) {
|
|
297
|
-
const script = buildRuntimeIdentityProvisionScript(user);
|
|
298
|
-
this.runDockerExecScript(["exec", "-u", "0", name, "bash", "-lc", script], `Failed to provision runtime user '${user.agentUser}' in container '${name}'`);
|
|
299
|
-
}
|
|
300
|
-
async ensureRuntimeContainerTooling(name, user) {
|
|
301
|
-
const script = buildRuntimeToolingValidationScript(user);
|
|
302
|
-
this.runDockerExecScript(["exec", "-u", user.agentUser, name, "bash", "-lc", script], `Failed to validate nvm/codex in runtime container '${name}'`);
|
|
303
|
-
}
|
|
304
|
-
async stopContainer(name) {
|
|
305
|
-
try {
|
|
306
|
-
await this.docker.getContainer(name).stop({ t: 10 });
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
if (isContainerNotFound(error) || isContainerNotRunning(error)) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
throw error;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
async forceRemoveContainer(name) {
|
|
316
|
-
try {
|
|
317
|
-
await this.docker.getContainer(name).remove({ force: true });
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
if (isContainerNotFound(error)) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
exports.ThreadContainerService = ThreadContainerService;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ensureThreadRuntimeReady = ensureThreadRuntimeReady;
|
|
4
|
-
const thread_lifecycle_js_1 = require("./thread_lifecycle.js");
|
|
5
|
-
async function ensureThreadRuntimeReady(options) {
|
|
6
|
-
const containerService = options.containerService ?? new thread_lifecycle_js_1.ThreadContainerService();
|
|
7
|
-
await containerService.ensureContainerRunning(options.dindContainer);
|
|
8
|
-
await containerService.ensureContainerRunning(options.runtimeContainer);
|
|
9
|
-
await containerService.ensureRuntimeContainerIdentity(options.runtimeContainer, options.user);
|
|
10
|
-
await containerService.ensureRuntimeContainerTooling(options.runtimeContainer, options.user);
|
|
11
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadThreadMessageExecutionState = loadThreadMessageExecutionState;
|
|
4
|
-
exports.updateThreadTurnState = updateThreadTurnState;
|
|
5
|
-
const drizzle_orm_1 = require("drizzle-orm");
|
|
6
|
-
const db_js_1 = require("../state/db.js");
|
|
7
|
-
const schema_js_1 = require("../state/schema.js");
|
|
8
|
-
async function loadThreadMessageExecutionState(stateDbPath, agentId, threadId) {
|
|
9
|
-
const { db, client } = await (0, db_js_1.initDb)(stateDbPath);
|
|
10
|
-
try {
|
|
11
|
-
return await db
|
|
12
|
-
.select({
|
|
13
|
-
id: schema_js_1.threads.id,
|
|
14
|
-
agentId: schema_js_1.threads.agentId,
|
|
15
|
-
sdkThreadId: schema_js_1.threads.sdkThreadId,
|
|
16
|
-
model: schema_js_1.threads.model,
|
|
17
|
-
reasoningLevel: schema_js_1.threads.reasoningLevel,
|
|
18
|
-
currentSdkTurnId: schema_js_1.threads.currentSdkTurnId,
|
|
19
|
-
isCurrentTurnRunning: schema_js_1.threads.isCurrentTurnRunning,
|
|
20
|
-
runtimeContainer: schema_js_1.threads.runtimeContainer,
|
|
21
|
-
dindContainer: schema_js_1.threads.dindContainer,
|
|
22
|
-
homeDirectory: schema_js_1.threads.homeDirectory,
|
|
23
|
-
uid: schema_js_1.threads.uid,
|
|
24
|
-
gid: schema_js_1.threads.gid,
|
|
25
|
-
})
|
|
26
|
-
.from(schema_js_1.threads)
|
|
27
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.threads.id, threadId), (0, drizzle_orm_1.eq)(schema_js_1.threads.agentId, agentId)))
|
|
28
|
-
.get();
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
client.close();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async function updateThreadTurnState(stateDbPath, agentId, threadId, update) {
|
|
35
|
-
const { db, client } = await (0, db_js_1.initDb)(stateDbPath);
|
|
36
|
-
try {
|
|
37
|
-
await db
|
|
38
|
-
.update(schema_js_1.threads)
|
|
39
|
-
.set(update)
|
|
40
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_js_1.threads.id, threadId), (0, drizzle_orm_1.eq)(schema_js_1.threads.agentId, agentId)));
|
|
41
|
-
}
|
|
42
|
-
finally {
|
|
43
|
-
client.close();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AGENTS_MD_CLI_TOOLS_SECTION = exports.AGENTS_MD_WORKSPACE_SECTION = void 0;
|
|
4
|
-
exports.renderRuntimeAgentsMd = renderRuntimeAgentsMd;
|
|
5
|
-
exports.ensureWorkspaceAgentsMd = ensureWorkspaceAgentsMd;
|
|
6
|
-
const node_fs_1 = require("node:fs");
|
|
7
|
-
const node_path_1 = require("node:path");
|
|
8
|
-
exports.AGENTS_MD_WORKSPACE_SECTION = `## Workspace Structure
|
|
9
|
-
|
|
10
|
-
- \`/workspace\` is a thread-specific bind mount from the host.
|
|
11
|
-
- Host path layout: \`<config_directory>/<workspaces_directory>/agent-<agent-id>/thread-<thread-id>\`.
|
|
12
|
-
- This workspace is not initialized as a Git repository by default.
|
|
13
|
-
- Clone a repository into \`/workspace\` or run \`git init\` manually when version history is required.
|
|
14
|
-
`;
|
|
15
|
-
exports.AGENTS_MD_CLI_TOOLS_SECTION = `## Available CLI Tools
|
|
16
|
-
|
|
17
|
-
- There are currently no additional CompanyHelm helper CLI tools installed in this runtime.
|
|
18
|
-
`;
|
|
19
|
-
const AGENTS_MD_GITHUB_SECTION_MARKER = "## GitHub Installations";
|
|
20
|
-
const RUNTIME_AGENTS_TEMPLATE_PATH = "templates/runtime_agents.md.j2";
|
|
21
|
-
const DEFAULT_HOME_DIRECTORY = "/home/agent";
|
|
22
|
-
function buildGithubInstallationsSection(installations) {
|
|
23
|
-
const lines = [AGENTS_MD_GITHUB_SECTION_MARKER, ""];
|
|
24
|
-
if (installations.length === 0) {
|
|
25
|
-
lines.push("- No linked GitHub installations were provided for this thread.");
|
|
26
|
-
return lines.join("\n");
|
|
27
|
-
}
|
|
28
|
-
for (const installation of installations) {
|
|
29
|
-
lines.push(`### Installation ${installation.installationId}`);
|
|
30
|
-
lines.push(`- Access token: \`${installation.accessToken}\``);
|
|
31
|
-
lines.push(`- Access token expires (unix ms): \`${installation.accessTokenExpiresUnixTimeMs}\``);
|
|
32
|
-
if (installation.repositories.length === 0) {
|
|
33
|
-
lines.push("- Repositories: none reported.");
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
lines.push("- Repositories:");
|
|
37
|
-
for (const repository of installation.repositories) {
|
|
38
|
-
lines.push(` - \`${repository}\``);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
lines.push("");
|
|
42
|
-
}
|
|
43
|
-
return lines.join("\n").trimEnd();
|
|
44
|
-
}
|
|
45
|
-
function renderJinjaTemplate(template, context) {
|
|
46
|
-
return template.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_match, key) => {
|
|
47
|
-
const value = context[key];
|
|
48
|
-
if (value === undefined) {
|
|
49
|
-
throw new Error(`Missing template value for key '${key}'`);
|
|
50
|
-
}
|
|
51
|
-
return value;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
function resolveTemplatePath() {
|
|
55
|
-
const distRelativePath = (0, node_path_1.join)(__dirname, "..", RUNTIME_AGENTS_TEMPLATE_PATH);
|
|
56
|
-
if ((0, node_fs_1.existsSync)(distRelativePath)) {
|
|
57
|
-
return distRelativePath;
|
|
58
|
-
}
|
|
59
|
-
const sourceRelativePath = (0, node_path_1.join)(__dirname, "..", "..", "src", RUNTIME_AGENTS_TEMPLATE_PATH);
|
|
60
|
-
if ((0, node_fs_1.existsSync)(sourceRelativePath)) {
|
|
61
|
-
return sourceRelativePath;
|
|
62
|
-
}
|
|
63
|
-
throw new Error(`Runtime AGENTS template was not found at ${distRelativePath} or ${sourceRelativePath}`);
|
|
64
|
-
}
|
|
65
|
-
function renderRuntimeAgentsMd(homeDirectory = DEFAULT_HOME_DIRECTORY, githubInstallations = []) {
|
|
66
|
-
const githubInstallationsSection = buildGithubInstallationsSection(githubInstallations);
|
|
67
|
-
const defaultTemplate = `# Agent Instructions
|
|
68
|
-
|
|
69
|
-
${exports.AGENTS_MD_WORKSPACE_SECTION}
|
|
70
|
-
${githubInstallationsSection}
|
|
71
|
-
|
|
72
|
-
${exports.AGENTS_MD_CLI_TOOLS_SECTION}`;
|
|
73
|
-
try {
|
|
74
|
-
const template = (0, node_fs_1.readFileSync)(resolveTemplatePath(), "utf8");
|
|
75
|
-
return renderJinjaTemplate(template, {
|
|
76
|
-
home_directory: homeDirectory,
|
|
77
|
-
github_installations_section: githubInstallationsSection,
|
|
78
|
-
}).trim() + "\n";
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return defaultTemplate.trim() + "\n";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function ensureWorkspaceAgentsMd(workspaceDirectory, homeDirectory = DEFAULT_HOME_DIRECTORY, githubInstallations = []) {
|
|
85
|
-
(0, node_fs_1.mkdirSync)(workspaceDirectory, { recursive: true });
|
|
86
|
-
const agentsPath = (0, node_path_1.join)(workspaceDirectory, "AGENTS.md");
|
|
87
|
-
const githubInstallationsSection = buildGithubInstallationsSection(githubInstallations);
|
|
88
|
-
const sections = [
|
|
89
|
-
{ marker: "## Workspace Structure", content: exports.AGENTS_MD_WORKSPACE_SECTION },
|
|
90
|
-
{ marker: AGENTS_MD_GITHUB_SECTION_MARKER, content: githubInstallationsSection },
|
|
91
|
-
{ marker: "## Available CLI Tools", content: exports.AGENTS_MD_CLI_TOOLS_SECTION },
|
|
92
|
-
];
|
|
93
|
-
let existing = "";
|
|
94
|
-
try {
|
|
95
|
-
existing = (0, node_fs_1.readFileSync)(agentsPath, "utf8");
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
existing = "";
|
|
99
|
-
}
|
|
100
|
-
const pendingSections = sections
|
|
101
|
-
.filter((section) => !existing.includes(section.marker))
|
|
102
|
-
.map((section) => section.content);
|
|
103
|
-
if (pendingSections.length === 0) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const updated = existing.trim()
|
|
107
|
-
? `${existing.trimEnd()}\n\n${pendingSections.join("\n\n")}`
|
|
108
|
-
: renderRuntimeAgentsMd(homeDirectory, githubInstallations);
|
|
109
|
-
try {
|
|
110
|
-
(0, node_fs_1.writeFileSync)(agentsPath, updated, "utf8");
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Best-effort workspace instruction file.
|
|
114
|
-
}
|
|
115
|
-
}
|