@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,316 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CompanyhelmApiClient = exports.CompanyhelmCommandChannel = void 0;
|
|
37
|
-
exports.parseCompanyhelmApiUrl = parseCompanyhelmApiUrl;
|
|
38
|
-
exports.createAgentRunnerControlServiceDefinition = createAgentRunnerControlServiceDefinition;
|
|
39
|
-
const protobuf_1 = require("@bufbuild/protobuf");
|
|
40
|
-
const protos_1 = require("@companyhelm/protos");
|
|
41
|
-
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
42
|
-
const async_queue_js_1 = require("../utils/async_queue.js");
|
|
43
|
-
function normalizePathPrefix(value) {
|
|
44
|
-
if (!value || value === "/") {
|
|
45
|
-
return "";
|
|
46
|
-
}
|
|
47
|
-
const withLeadingSlash = value.startsWith("/") ? value : `/${value}`;
|
|
48
|
-
const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/g, "");
|
|
49
|
-
return withoutTrailingSlash === "/" ? "" : withoutTrailingSlash;
|
|
50
|
-
}
|
|
51
|
-
function buildRpcPath(methodName, pathPrefix) {
|
|
52
|
-
return `${normalizePathPrefix(pathPrefix)}/${protos_1.AgentRunnerControlService.typeName}/${methodName}`.replace(/\/{2,}/g, "/");
|
|
53
|
-
}
|
|
54
|
-
function extractTargetHost(target) {
|
|
55
|
-
const trimmed = target.trim().toLowerCase();
|
|
56
|
-
if (trimmed.startsWith("[")) {
|
|
57
|
-
const closingBracketIndex = trimmed.indexOf("]");
|
|
58
|
-
if (closingBracketIndex > 0) {
|
|
59
|
-
return trimmed.slice(1, closingBracketIndex);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const colonIndex = trimmed.indexOf(":");
|
|
63
|
-
return colonIndex >= 0 ? trimmed.slice(0, colonIndex) : trimmed;
|
|
64
|
-
}
|
|
65
|
-
function isLikelyLocalTarget(target) {
|
|
66
|
-
const host = extractTargetHost(target);
|
|
67
|
-
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1";
|
|
68
|
-
}
|
|
69
|
-
function parseCompanyhelmApiUrl(value) {
|
|
70
|
-
const trimmed = value.trim();
|
|
71
|
-
if (!trimmed) {
|
|
72
|
-
throw new Error("companyhelm_api_url cannot be empty.");
|
|
73
|
-
}
|
|
74
|
-
if (trimmed.includes("://")) {
|
|
75
|
-
const parsed = new URL(trimmed);
|
|
76
|
-
const host = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
|
|
77
|
-
const target = parsed.port ? `${host}:${parsed.port}` : host;
|
|
78
|
-
if (!target) {
|
|
79
|
-
throw new Error(`Invalid companyhelm_api_url '${value}'.`);
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
target,
|
|
83
|
-
pathPrefix: normalizePathPrefix(parsed.pathname),
|
|
84
|
-
useTls: parsed.protocol !== "http:",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const firstSlash = trimmed.indexOf("/");
|
|
88
|
-
const target = firstSlash >= 0 ? trimmed.slice(0, firstSlash) : trimmed;
|
|
89
|
-
const pathPrefix = firstSlash >= 0 ? trimmed.slice(firstSlash) : "";
|
|
90
|
-
if (!target) {
|
|
91
|
-
throw new Error(`Invalid companyhelm_api_url '${value}'.`);
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
target,
|
|
95
|
-
pathPrefix: normalizePathPrefix(pathPrefix),
|
|
96
|
-
useTls: !isLikelyLocalTarget(target),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function defaultCredentials(endpoint) {
|
|
100
|
-
return endpoint.useTls ? grpc.credentials.createSsl() : grpc.credentials.createInsecure();
|
|
101
|
-
}
|
|
102
|
-
function createAgentRunnerControlServiceDefinition(pathPrefix = "") {
|
|
103
|
-
const methods = protos_1.AgentRunnerControlService.method;
|
|
104
|
-
return {
|
|
105
|
-
registerRunner: {
|
|
106
|
-
path: buildRpcPath(methods.registerRunner.name, pathPrefix),
|
|
107
|
-
requestStream: false,
|
|
108
|
-
responseStream: false,
|
|
109
|
-
requestSerialize: (request) => Buffer.from((0, protobuf_1.toBinary)(protos_1.RegisterRunnerRequestSchema, request)),
|
|
110
|
-
requestDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.RegisterRunnerRequestSchema, bytes),
|
|
111
|
-
responseSerialize: (response) => Buffer.from((0, protobuf_1.toBinary)(protos_1.RegisterRunnerResponseSchema, response)),
|
|
112
|
-
responseDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.RegisterRunnerResponseSchema, bytes),
|
|
113
|
-
},
|
|
114
|
-
controlChannel: {
|
|
115
|
-
path: buildRpcPath(methods.controlChannel.name, pathPrefix),
|
|
116
|
-
requestStream: true,
|
|
117
|
-
responseStream: true,
|
|
118
|
-
requestSerialize: (request) => Buffer.from((0, protobuf_1.toBinary)(protos_1.ClientMessageSchema, request)),
|
|
119
|
-
requestDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.ClientMessageSchema, bytes),
|
|
120
|
-
responseSerialize: (response) => Buffer.from((0, protobuf_1.toBinary)(protos_1.ServerMessageSchema, response)),
|
|
121
|
-
responseDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.ServerMessageSchema, bytes),
|
|
122
|
-
},
|
|
123
|
-
listGithubInstallationsForRunner: {
|
|
124
|
-
path: buildRpcPath(methods.listGithubInstallationsForRunner.name, pathPrefix),
|
|
125
|
-
requestStream: false,
|
|
126
|
-
responseStream: false,
|
|
127
|
-
requestSerialize: (request) => Buffer.from((0, protobuf_1.toBinary)(protos_1.ListGithubInstallationsForRunnerRequestSchema, request)),
|
|
128
|
-
requestDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.ListGithubInstallationsForRunnerRequestSchema, bytes),
|
|
129
|
-
responseSerialize: (response) => Buffer.from((0, protobuf_1.toBinary)(protos_1.ListGithubInstallationsForRunnerResponseSchema, response)),
|
|
130
|
-
responseDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.ListGithubInstallationsForRunnerResponseSchema, bytes),
|
|
131
|
-
},
|
|
132
|
-
getGithubInstallationAccessTokenForRunner: {
|
|
133
|
-
path: buildRpcPath(methods.getGithubInstallationAccessTokenForRunner.name, pathPrefix),
|
|
134
|
-
requestStream: false,
|
|
135
|
-
responseStream: false,
|
|
136
|
-
requestSerialize: (request) => Buffer.from((0, protobuf_1.toBinary)(protos_1.GetGithubInstallationAccessTokenForRunnerRequestSchema, request)),
|
|
137
|
-
requestDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.GetGithubInstallationAccessTokenForRunnerRequestSchema, bytes),
|
|
138
|
-
responseSerialize: (response) => Buffer.from((0, protobuf_1.toBinary)(protos_1.GetGithubInstallationAccessTokenForRunnerResponseSchema, response)),
|
|
139
|
-
responseDeserialize: (bytes) => (0, protobuf_1.fromBinary)(protos_1.GetGithubInstallationAccessTokenForRunnerResponseSchema, bytes),
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function createAgentRunnerControlClient(endpoint, credentials, channelOptions) {
|
|
144
|
-
const ClientCtor = grpc.makeGenericClientConstructor(createAgentRunnerControlServiceDefinition(endpoint.pathPrefix), "AgentRunnerControlService");
|
|
145
|
-
return new ClientCtor(endpoint.target, credentials, channelOptions);
|
|
146
|
-
}
|
|
147
|
-
function toError(error) {
|
|
148
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
149
|
-
}
|
|
150
|
-
class CompanyhelmCommandChannel {
|
|
151
|
-
constructor(call) {
|
|
152
|
-
this.messages = new async_queue_js_1.AsyncQueue();
|
|
153
|
-
this.resolveOpened = null;
|
|
154
|
-
this.rejectOpened = null;
|
|
155
|
-
this.openState = "pending";
|
|
156
|
-
this.call = call;
|
|
157
|
-
this.opened = new Promise((resolve, reject) => {
|
|
158
|
-
this.resolveOpened = resolve;
|
|
159
|
-
this.rejectOpened = reject;
|
|
160
|
-
});
|
|
161
|
-
call.on("data", (message) => {
|
|
162
|
-
this.setOpened();
|
|
163
|
-
this.messages.push(message);
|
|
164
|
-
});
|
|
165
|
-
call.on("metadata", () => {
|
|
166
|
-
this.setOpened();
|
|
167
|
-
});
|
|
168
|
-
call.on("end", () => {
|
|
169
|
-
this.failOpen(new Error("command channel closed before becoming usable"));
|
|
170
|
-
this.messages.close();
|
|
171
|
-
});
|
|
172
|
-
call.on("error", (error) => {
|
|
173
|
-
const serviceError = error;
|
|
174
|
-
if (serviceError.code === grpc.status.CANCELLED) {
|
|
175
|
-
this.failOpen(new Error("command channel cancelled before becoming usable"));
|
|
176
|
-
this.messages.close();
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const channelError = toError(error);
|
|
180
|
-
this.failOpen(channelError);
|
|
181
|
-
this.messages.fail(channelError);
|
|
182
|
-
});
|
|
183
|
-
call.on("status", (status) => {
|
|
184
|
-
if (status.code === grpc.status.OK || status.code === grpc.status.CANCELLED) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const statusError = new Error(`command channel closed with status ${status.code}: ${status.details}`);
|
|
188
|
-
this.failOpen(statusError);
|
|
189
|
-
this.messages.fail(statusError);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
setOpened() {
|
|
193
|
-
if (this.openState !== "pending") {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
this.openState = "opened";
|
|
197
|
-
this.resolveOpened?.();
|
|
198
|
-
this.resolveOpened = null;
|
|
199
|
-
this.rejectOpened = null;
|
|
200
|
-
}
|
|
201
|
-
failOpen(error) {
|
|
202
|
-
if (this.openState !== "pending") {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
this.openState = "failed";
|
|
206
|
-
this.rejectOpened?.(error);
|
|
207
|
-
this.resolveOpened = null;
|
|
208
|
-
this.rejectOpened = null;
|
|
209
|
-
}
|
|
210
|
-
send(message) {
|
|
211
|
-
return new Promise((resolve, reject) => {
|
|
212
|
-
this.call.write(message, (error) => {
|
|
213
|
-
if (error) {
|
|
214
|
-
reject(error);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
resolve();
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
closeWrite() {
|
|
222
|
-
this.call.end();
|
|
223
|
-
}
|
|
224
|
-
cancel() {
|
|
225
|
-
this.call.cancel();
|
|
226
|
-
}
|
|
227
|
-
async waitForOpen(timeoutMs = 5000) {
|
|
228
|
-
let timeoutHandle;
|
|
229
|
-
try {
|
|
230
|
-
await Promise.race([
|
|
231
|
-
this.opened,
|
|
232
|
-
new Promise((_, reject) => {
|
|
233
|
-
timeoutHandle = setTimeout(() => {
|
|
234
|
-
reject(new Error(`command channel did not open within ${timeoutMs}ms`));
|
|
235
|
-
}, timeoutMs);
|
|
236
|
-
}),
|
|
237
|
-
]);
|
|
238
|
-
}
|
|
239
|
-
finally {
|
|
240
|
-
if (timeoutHandle) {
|
|
241
|
-
clearTimeout(timeoutHandle);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
nextMessage() {
|
|
246
|
-
return this.messages.pop();
|
|
247
|
-
}
|
|
248
|
-
async *[Symbol.asyncIterator]() {
|
|
249
|
-
while (true) {
|
|
250
|
-
const message = await this.nextMessage();
|
|
251
|
-
if (message === null) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
yield message;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
exports.CompanyhelmCommandChannel = CompanyhelmCommandChannel;
|
|
259
|
-
class CompanyhelmApiClient {
|
|
260
|
-
constructor(options) {
|
|
261
|
-
this.endpoint = parseCompanyhelmApiUrl(options.apiUrl);
|
|
262
|
-
this.client = createAgentRunnerControlClient(this.endpoint, options.credentials ?? defaultCredentials(this.endpoint), options.channelOptions);
|
|
263
|
-
}
|
|
264
|
-
registerRunner(request, options) {
|
|
265
|
-
const metadata = options?.metadata ?? new grpc.Metadata();
|
|
266
|
-
const callOptions = options?.callOptions ?? {};
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
this.client.registerRunner(request, metadata, callOptions, (error, response) => {
|
|
269
|
-
if (error) {
|
|
270
|
-
reject(error);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
resolve(response ?? (0, protobuf_1.create)(protos_1.RegisterRunnerResponseSchema));
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
async connect(registerRequest, options) {
|
|
278
|
-
await this.registerRunner(registerRequest, options);
|
|
279
|
-
const stream = this.client.controlChannel(options?.metadata, options?.callOptions);
|
|
280
|
-
return new CompanyhelmCommandChannel(stream);
|
|
281
|
-
}
|
|
282
|
-
listGithubInstallationsForRunner(options) {
|
|
283
|
-
const metadata = options?.metadata ?? new grpc.Metadata();
|
|
284
|
-
const callOptions = options?.callOptions ?? {};
|
|
285
|
-
const request = (0, protobuf_1.create)(protos_1.ListGithubInstallationsForRunnerRequestSchema, {});
|
|
286
|
-
return new Promise((resolve, reject) => {
|
|
287
|
-
this.client.listGithubInstallationsForRunner(request, metadata, callOptions, (error, response) => {
|
|
288
|
-
if (error) {
|
|
289
|
-
reject(error);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
resolve(response ?? (0, protobuf_1.create)(protos_1.ListGithubInstallationsForRunnerResponseSchema, {}));
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
getGithubInstallationAccessTokenForRunner(installationId, options) {
|
|
297
|
-
const metadata = options?.metadata ?? new grpc.Metadata();
|
|
298
|
-
const callOptions = options?.callOptions ?? {};
|
|
299
|
-
const request = (0, protobuf_1.create)(protos_1.GetGithubInstallationAccessTokenForRunnerRequestSchema, {
|
|
300
|
-
installationId,
|
|
301
|
-
});
|
|
302
|
-
return new Promise((resolve, reject) => {
|
|
303
|
-
this.client.getGithubInstallationAccessTokenForRunner(request, metadata, callOptions, (error, response) => {
|
|
304
|
-
if (error) {
|
|
305
|
-
reject(error);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
resolve(response ?? (0, protobuf_1.create)(protos_1.GetGithubInstallationAccessTokenForRunnerResponseSchema, {}));
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
close() {
|
|
313
|
-
this.client.close();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
exports.CompanyhelmApiClient = CompanyhelmApiClient;
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AppServerContainerService = void 0;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const node_child_process_1 = require("node:child_process");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
|
-
const drizzle_orm_1 = require("drizzle-orm");
|
|
8
|
-
const config_js_1 = require("../../config.js");
|
|
9
|
-
const db_js_1 = require("../../state/db.js");
|
|
10
|
-
const schema_js_1 = require("../../state/schema.js");
|
|
11
|
-
const async_queue_js_1 = require("../../utils/async_queue.js");
|
|
12
|
-
const path_js_1 = require("../../utils/path.js");
|
|
13
|
-
const runtime_shell_js_1 = require("../runtime_shell.js");
|
|
14
|
-
const host_js_1 = require("../host.js");
|
|
15
|
-
const DEFAULT_APP_SERVER_COMMAND = (0, runtime_shell_js_1.buildCodexAppServerCommand)();
|
|
16
|
-
const BOOTSTRAP_TEMPLATE_PATH = "templates/app_server_bootstrap.sh.j2";
|
|
17
|
-
function resolveContainerPath(path, containerHome) {
|
|
18
|
-
if (path === "~") {
|
|
19
|
-
return containerHome;
|
|
20
|
-
}
|
|
21
|
-
if (path.startsWith("~/")) {
|
|
22
|
-
return `${containerHome}${path.slice(1)}`;
|
|
23
|
-
}
|
|
24
|
-
return path;
|
|
25
|
-
}
|
|
26
|
-
function resolveTemplatePath() {
|
|
27
|
-
const distRelativePath = (0, node_path_1.join)(__dirname, "..", "..", BOOTSTRAP_TEMPLATE_PATH);
|
|
28
|
-
if ((0, node_fs_1.existsSync)(distRelativePath)) {
|
|
29
|
-
return distRelativePath;
|
|
30
|
-
}
|
|
31
|
-
const sourceRelativePath = (0, node_path_1.join)(__dirname, "..", "..", "..", "src", BOOTSTRAP_TEMPLATE_PATH);
|
|
32
|
-
if ((0, node_fs_1.existsSync)(sourceRelativePath)) {
|
|
33
|
-
return sourceRelativePath;
|
|
34
|
-
}
|
|
35
|
-
throw new Error(`Bootstrap template was not found at ${distRelativePath} or ${sourceRelativePath}`);
|
|
36
|
-
}
|
|
37
|
-
function renderJinjaTemplate(template, context) {
|
|
38
|
-
return template.replace(/{{\s*([a-zA-Z0-9_]+)\s*}}/g, (_match, key) => {
|
|
39
|
-
const value = context[key];
|
|
40
|
-
if (value === undefined) {
|
|
41
|
-
throw new Error(`Missing template value for key '${key}'`);
|
|
42
|
-
}
|
|
43
|
-
return value;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function shellQuote(value) {
|
|
47
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
48
|
-
}
|
|
49
|
-
class AppServerContainerService {
|
|
50
|
-
constructor() {
|
|
51
|
-
this.messageQueue = new async_queue_js_1.AsyncQueue();
|
|
52
|
-
this.child = null;
|
|
53
|
-
this.containerName = null;
|
|
54
|
-
this.running = false;
|
|
55
|
-
}
|
|
56
|
-
async start() {
|
|
57
|
-
if (this.running) {
|
|
58
|
-
throw new Error("App server container is already running");
|
|
59
|
-
}
|
|
60
|
-
const cfg = config_js_1.config.parse({});
|
|
61
|
-
const { db, client } = await (0, db_js_1.initDb)(cfg.state_db_path);
|
|
62
|
-
let codexAuthMode;
|
|
63
|
-
try {
|
|
64
|
-
const sdk = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get();
|
|
65
|
-
if (!sdk) {
|
|
66
|
-
throw new Error("Codex SDK is not configured.");
|
|
67
|
-
}
|
|
68
|
-
if (!sdk.authentication || sdk.authentication === "unauthenticated") {
|
|
69
|
-
throw new Error("Codex SDK authentication is not configured.");
|
|
70
|
-
}
|
|
71
|
-
codexAuthMode = sdk.authentication;
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
client.close();
|
|
75
|
-
}
|
|
76
|
-
const hostInfo = (0, host_js_1.getHostInfo)(cfg.codex.codex_auth_path);
|
|
77
|
-
const containerHome = cfg.agent_home_directory;
|
|
78
|
-
const containerAuthPath = resolveContainerPath(cfg.codex.codex_auth_path, containerHome);
|
|
79
|
-
const hostDedicatedAuthPath = `${(0, path_js_1.expandHome)(cfg.config_directory)}/${cfg.codex.codex_auth_file_path}`;
|
|
80
|
-
const mountArgs = [];
|
|
81
|
-
if (codexAuthMode === "dedicated") {
|
|
82
|
-
if (!(0, host_js_1.getHostInfo)(hostDedicatedAuthPath).codexAuthExists) {
|
|
83
|
-
throw new Error(`Dedicated Codex auth file was not found at ${hostDedicatedAuthPath}`);
|
|
84
|
-
}
|
|
85
|
-
mountArgs.push("--mount", `type=bind,src=${hostDedicatedAuthPath},dst=${containerAuthPath}`);
|
|
86
|
-
}
|
|
87
|
-
this.containerName = `companyhelm-codex-app-server-${Date.now()}`;
|
|
88
|
-
const bootstrapTemplate = (0, node_fs_1.readFileSync)(resolveTemplatePath(), "utf8");
|
|
89
|
-
const bootstrapScript = renderJinjaTemplate(bootstrapTemplate, {
|
|
90
|
-
agent_user: shellQuote(cfg.agent_user),
|
|
91
|
-
agent_home: shellQuote(containerHome),
|
|
92
|
-
agent_uid: shellQuote(String(hostInfo.uid)),
|
|
93
|
-
agent_gid: shellQuote(String(hostInfo.gid)),
|
|
94
|
-
codex_auth_path: shellQuote(containerAuthPath),
|
|
95
|
-
app_server_command: shellQuote(DEFAULT_APP_SERVER_COMMAND),
|
|
96
|
-
});
|
|
97
|
-
const args = [
|
|
98
|
-
"run",
|
|
99
|
-
"--rm",
|
|
100
|
-
"-i",
|
|
101
|
-
"--name",
|
|
102
|
-
this.containerName,
|
|
103
|
-
"--entrypoint",
|
|
104
|
-
"bash",
|
|
105
|
-
...mountArgs,
|
|
106
|
-
cfg.runtime_image,
|
|
107
|
-
"-lc",
|
|
108
|
-
bootstrapScript,
|
|
109
|
-
];
|
|
110
|
-
const child = (0, node_child_process_1.spawn)("docker", args, {
|
|
111
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
112
|
-
});
|
|
113
|
-
child.stdout.on("data", (chunk) => {
|
|
114
|
-
this.messageQueue.push({ type: "stdout", payload: chunk });
|
|
115
|
-
});
|
|
116
|
-
child.stderr.on("data", (chunk) => {
|
|
117
|
-
this.messageQueue.push({ type: "stderr", payload: chunk.toString("utf8") });
|
|
118
|
-
});
|
|
119
|
-
child.on("error", (err) => {
|
|
120
|
-
this.messageQueue.push({ type: "error", reason: `docker process error: ${err.message}` });
|
|
121
|
-
this.running = false;
|
|
122
|
-
this.messageQueue.close();
|
|
123
|
-
});
|
|
124
|
-
child.on("exit", () => {
|
|
125
|
-
this.running = false;
|
|
126
|
-
this.messageQueue.close();
|
|
127
|
-
});
|
|
128
|
-
this.child = child;
|
|
129
|
-
this.running = true;
|
|
130
|
-
}
|
|
131
|
-
async stop() {
|
|
132
|
-
this.running = false;
|
|
133
|
-
this.messageQueue.close();
|
|
134
|
-
const child = this.child;
|
|
135
|
-
this.child = null;
|
|
136
|
-
if (child) {
|
|
137
|
-
if (!child.killed) {
|
|
138
|
-
child.kill("SIGTERM");
|
|
139
|
-
}
|
|
140
|
-
await new Promise((resolve) => {
|
|
141
|
-
child.once("exit", () => resolve());
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
if (this.containerName) {
|
|
145
|
-
(0, node_child_process_1.spawnSync)("docker", ["rm", "-f", this.containerName], { stdio: "ignore" });
|
|
146
|
-
this.containerName = null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
async sendRaw(payload) {
|
|
150
|
-
if (!this.running || !this.child || !this.child.stdin) {
|
|
151
|
-
throw new Error("App server container is not running");
|
|
152
|
-
}
|
|
153
|
-
this.child.stdin.write(payload);
|
|
154
|
-
}
|
|
155
|
-
async *receiveOutput() {
|
|
156
|
-
while (true) {
|
|
157
|
-
const item = await this.messageQueue.pop();
|
|
158
|
-
if (!item) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
yield item;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
exports.AppServerContainerService = AppServerContainerService;
|
|
@@ -1,114 +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.DinDService = void 0;
|
|
7
|
-
const dockerode_1 = __importDefault(require("dockerode"));
|
|
8
|
-
const DIND_IMAGE = "docker:dind-rootless";
|
|
9
|
-
const DIND_PORT = 2375;
|
|
10
|
-
const READY_TIMEOUT_MS = 30000;
|
|
11
|
-
const READY_POLL_MS = 500;
|
|
12
|
-
class DinDService {
|
|
13
|
-
constructor(docker) {
|
|
14
|
-
this.container = null;
|
|
15
|
-
this._endpoint = null;
|
|
16
|
-
this.docker = docker ?? new dockerode_1.default();
|
|
17
|
-
}
|
|
18
|
-
async start() {
|
|
19
|
-
if (this.container) {
|
|
20
|
-
throw new Error("DinD service is already running");
|
|
21
|
-
}
|
|
22
|
-
const uid = process.getuid?.();
|
|
23
|
-
if (uid === undefined) {
|
|
24
|
-
throw new Error("Cannot determine current UID (not supported on this platform)");
|
|
25
|
-
}
|
|
26
|
-
await this.pullImage();
|
|
27
|
-
const container = await this.docker.createContainer({
|
|
28
|
-
Image: DIND_IMAGE,
|
|
29
|
-
User: String(uid),
|
|
30
|
-
Env: [
|
|
31
|
-
// Disable TLS so the inner daemon listens on plain 2375.
|
|
32
|
-
"DOCKER_TLS_CERTDIR=",
|
|
33
|
-
],
|
|
34
|
-
ExposedPorts: { [`${DIND_PORT}/tcp`]: {} },
|
|
35
|
-
HostConfig: {
|
|
36
|
-
Privileged: true,
|
|
37
|
-
PublishAllPorts: true,
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
await container.start();
|
|
41
|
-
this.container = container;
|
|
42
|
-
const info = await container.inspect();
|
|
43
|
-
const bindings = info.NetworkSettings.Ports[`${DIND_PORT}/tcp`];
|
|
44
|
-
if (!bindings?.[0]) {
|
|
45
|
-
await this.stop();
|
|
46
|
-
throw new Error("DinD container started but port was not bound");
|
|
47
|
-
}
|
|
48
|
-
this._endpoint = {
|
|
49
|
-
host: "127.0.0.1",
|
|
50
|
-
port: parseInt(bindings[0].HostPort, 10),
|
|
51
|
-
};
|
|
52
|
-
await this.waitForReady();
|
|
53
|
-
}
|
|
54
|
-
async stop() {
|
|
55
|
-
if (!this.container)
|
|
56
|
-
return;
|
|
57
|
-
const c = this.container;
|
|
58
|
-
this.container = null;
|
|
59
|
-
this._endpoint = null;
|
|
60
|
-
try {
|
|
61
|
-
await c.stop();
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
// already stopped
|
|
65
|
-
}
|
|
66
|
-
try {
|
|
67
|
-
await c.remove({ force: true });
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
// already removed
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
getEndpoint() {
|
|
74
|
-
if (!this._endpoint) {
|
|
75
|
-
throw new Error("DinD service is not running");
|
|
76
|
-
}
|
|
77
|
-
return this._endpoint;
|
|
78
|
-
}
|
|
79
|
-
getContainer() {
|
|
80
|
-
if (!this.container) {
|
|
81
|
-
throw new Error("DinD service is not running");
|
|
82
|
-
}
|
|
83
|
-
return this.container;
|
|
84
|
-
}
|
|
85
|
-
async pullImage() {
|
|
86
|
-
try {
|
|
87
|
-
await this.docker.getImage(DIND_IMAGE).inspect();
|
|
88
|
-
return; // already present
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// not found – pull it
|
|
92
|
-
}
|
|
93
|
-
const stream = await this.docker.pull(DIND_IMAGE);
|
|
94
|
-
await new Promise((resolve, reject) => {
|
|
95
|
-
this.docker.modem.followProgress(stream, (err) => (err ? reject(err) : resolve()));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
async waitForReady() {
|
|
99
|
-
const { host, port } = this.getEndpoint();
|
|
100
|
-
const inner = new dockerode_1.default({ host, port });
|
|
101
|
-
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
102
|
-
while (Date.now() < deadline) {
|
|
103
|
-
try {
|
|
104
|
-
await inner.ping();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
await new Promise((r) => setTimeout(r, READY_POLL_MS));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
throw new Error(`DinD daemon did not become ready within ${READY_TIMEOUT_MS}ms`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
exports.DinDService = DinDService;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RuntimeContainerAppServerTransport = void 0;
|
|
4
|
-
const node_child_process_1 = require("node:child_process");
|
|
5
|
-
const async_queue_js_1 = require("../../utils/async_queue.js");
|
|
6
|
-
const runtime_shell_js_1 = require("../runtime_shell.js");
|
|
7
|
-
const DEFAULT_APP_SERVER_COMMAND = (0, runtime_shell_js_1.buildCodexAppServerCommand)();
|
|
8
|
-
const PROCESS_EXIT_TIMEOUT_MS = 5000;
|
|
9
|
-
class RuntimeContainerAppServerTransport {
|
|
10
|
-
constructor(runtimeContainerName, appServerCommand = DEFAULT_APP_SERVER_COMMAND) {
|
|
11
|
-
this.runtimeContainerName = runtimeContainerName;
|
|
12
|
-
this.appServerCommand = appServerCommand;
|
|
13
|
-
this.messageQueue = new async_queue_js_1.AsyncQueue();
|
|
14
|
-
this.child = null;
|
|
15
|
-
this.running = false;
|
|
16
|
-
}
|
|
17
|
-
async start() {
|
|
18
|
-
if (this.running) {
|
|
19
|
-
throw new Error("Runtime app-server transport is already running.");
|
|
20
|
-
}
|
|
21
|
-
const child = (0, node_child_process_1.spawn)("docker", ["exec", "-i", this.runtimeContainerName, "bash", "-lc", this.appServerCommand], { stdio: ["pipe", "pipe", "pipe"] });
|
|
22
|
-
child.stdout.on("data", (chunk) => {
|
|
23
|
-
this.messageQueue.push({ type: "stdout", payload: chunk });
|
|
24
|
-
});
|
|
25
|
-
child.stderr.on("data", (chunk) => {
|
|
26
|
-
this.messageQueue.push({ type: "stderr", payload: chunk.toString("utf8") });
|
|
27
|
-
});
|
|
28
|
-
child.on("error", (error) => {
|
|
29
|
-
this.messageQueue.push({ type: "error", reason: `docker exec error: ${error.message}` });
|
|
30
|
-
this.running = false;
|
|
31
|
-
this.messageQueue.close();
|
|
32
|
-
});
|
|
33
|
-
child.on("exit", (code, signal) => {
|
|
34
|
-
this.running = false;
|
|
35
|
-
this.messageQueue.push({
|
|
36
|
-
type: "error",
|
|
37
|
-
reason: `docker exec exited (${code !== null ? `code ${code}` : `signal ${signal ?? "unknown"}`})`,
|
|
38
|
-
});
|
|
39
|
-
this.messageQueue.close();
|
|
40
|
-
});
|
|
41
|
-
this.child = child;
|
|
42
|
-
this.running = true;
|
|
43
|
-
}
|
|
44
|
-
async stop() {
|
|
45
|
-
this.running = false;
|
|
46
|
-
this.messageQueue.close();
|
|
47
|
-
const child = this.child;
|
|
48
|
-
this.child = null;
|
|
49
|
-
if (!child) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const waitForExit = (timeoutMs) => Promise.race([
|
|
53
|
-
new Promise((resolveExit) => {
|
|
54
|
-
child.once("exit", () => resolveExit(true));
|
|
55
|
-
}),
|
|
56
|
-
new Promise((resolveExit) => {
|
|
57
|
-
setTimeout(() => resolveExit(false), timeoutMs);
|
|
58
|
-
}),
|
|
59
|
-
]);
|
|
60
|
-
// Close stdin first so app-server can flush and exit cleanly.
|
|
61
|
-
if (child.stdin && !child.stdin.destroyed && child.stdin.writable) {
|
|
62
|
-
child.stdin.end();
|
|
63
|
-
}
|
|
64
|
-
let exited = await waitForExit(PROCESS_EXIT_TIMEOUT_MS);
|
|
65
|
-
if (exited) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (!child.killed) {
|
|
69
|
-
child.kill("SIGTERM");
|
|
70
|
-
}
|
|
71
|
-
exited = await waitForExit(PROCESS_EXIT_TIMEOUT_MS);
|
|
72
|
-
if (exited) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (!exited && !child.killed) {
|
|
76
|
-
child.kill("SIGKILL");
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async sendRaw(payload) {
|
|
80
|
-
if (!this.running || !this.child || !this.child.stdin) {
|
|
81
|
-
throw new Error("Runtime app-server transport is not running.");
|
|
82
|
-
}
|
|
83
|
-
this.child.stdin.write(payload);
|
|
84
|
-
}
|
|
85
|
-
async *receiveOutput() {
|
|
86
|
-
while (true) {
|
|
87
|
-
const event = await this.messageQueue.pop();
|
|
88
|
-
if (!event) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
yield event;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
exports.RuntimeContainerAppServerTransport = RuntimeContainerAppServerTransport;
|