@companyhelm/runner 0.0.19 → 0.0.22

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.
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRunnerLogsCommand = runRunnerLogsCommand;
4
+ exports.registerLogsCommand = registerLogsCommand;
5
+ const node_fs_1 = require("node:fs");
6
+ const promises_1 = require("node:fs/promises");
7
+ const promises_2 = require("node:timers/promises");
8
+ const config_js_1 = require("../config.js");
9
+ const daemon_state_js_1 = require("../state/daemon_state.js");
10
+ const daemon_js_1 = require("../utils/daemon.js");
11
+ class RunnerLogsCommand {
12
+ constructor(dependencies = {}) {
13
+ this.fileExistsFn = dependencies.fileExistsFn ?? defaultFileExists;
14
+ this.openFileFn = dependencies.openFileFn ?? defaultOpenFile;
15
+ this.pollIntervalMs = dependencies.pollIntervalMs ?? 100;
16
+ this.readCurrentDaemonStateFn = dependencies.readCurrentDaemonStateFn ?? daemon_state_js_1.readCurrentDaemonState;
17
+ this.readFileFn = dependencies.readFileFn ?? defaultReadFile;
18
+ this.signal = dependencies.signal;
19
+ this.stdout = dependencies.stdout ?? process.stdout;
20
+ }
21
+ async run(options) {
22
+ const cfg = config_js_1.config.parse({
23
+ state_db_path: options.stateDbPath,
24
+ });
25
+ const logPath = await this.resolveLogPath(cfg.state_db_path);
26
+ if (!(await this.fileExistsFn(logPath))) {
27
+ this.stdout.write(`CompanyHelm runner log file not found at ${logPath}.\n`);
28
+ return;
29
+ }
30
+ if (!options.live) {
31
+ this.stdout.write(await this.readFileFn(logPath));
32
+ return;
33
+ }
34
+ await this.followLogFile(logPath);
35
+ }
36
+ async resolveLogPath(stateDbPath) {
37
+ const state = await this.readCurrentDaemonStateFn(stateDbPath);
38
+ return state?.logPath ?? (0, daemon_js_1.resolveDaemonLogPath)(stateDbPath);
39
+ }
40
+ async followLogFile(logPath) {
41
+ const fileHandle = await this.openFileFn(logPath);
42
+ try {
43
+ let offset = await this.writeAvailableContents(fileHandle, 0);
44
+ while (!this.signal?.aborted) {
45
+ try {
46
+ await (0, promises_2.setTimeout)(this.pollIntervalMs, undefined, this.signal ? { signal: this.signal } : undefined);
47
+ }
48
+ catch (error) {
49
+ if (isAbortError(error)) {
50
+ return;
51
+ }
52
+ throw error;
53
+ }
54
+ offset = await this.writeAvailableContents(fileHandle, offset);
55
+ }
56
+ }
57
+ finally {
58
+ await fileHandle.close();
59
+ }
60
+ }
61
+ async writeAvailableContents(fileHandle, offset) {
62
+ const stats = await fileHandle.stat();
63
+ let nextOffset = offset;
64
+ if (stats.size < nextOffset) {
65
+ nextOffset = 0;
66
+ }
67
+ const unreadBytes = stats.size - nextOffset;
68
+ if (unreadBytes <= 0) {
69
+ return nextOffset;
70
+ }
71
+ const buffer = Buffer.alloc(unreadBytes);
72
+ const { bytesRead } = await fileHandle.read(buffer, 0, unreadBytes, nextOffset);
73
+ if (bytesRead > 0) {
74
+ this.stdout.write(buffer.subarray(0, bytesRead));
75
+ }
76
+ return nextOffset + bytesRead;
77
+ }
78
+ }
79
+ function isAbortError(error) {
80
+ return error instanceof Error && error.name === "AbortError";
81
+ }
82
+ async function defaultFileExists(filePath) {
83
+ try {
84
+ await (0, promises_1.access)(filePath, node_fs_1.constants.F_OK);
85
+ return true;
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ async function defaultOpenFile(filePath) {
92
+ return await (0, promises_1.open)(filePath, "r");
93
+ }
94
+ async function defaultReadFile(filePath) {
95
+ return await (0, promises_1.readFile)(filePath, "utf8");
96
+ }
97
+ async function runRunnerLogsCommand(options, dependencies) {
98
+ await new RunnerLogsCommand(dependencies).run(options);
99
+ }
100
+ function registerLogsCommand(program) {
101
+ program
102
+ .command("logs")
103
+ .description("Print the local CompanyHelm daemon log output.")
104
+ .option("--live", "Keep streaming appended daemon log output.")
105
+ .option("--state-db-path <path>", "State database path override (defaults to state.db under the active config directory).")
106
+ .action(async (options) => {
107
+ await runRunnerLogsCommand(options);
108
+ });
109
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerCommands = registerCommands;
4
4
  const common_js_1 = require("./runner/common.js");
5
+ const logs_js_1 = require("./logs.js");
5
6
  const register_runner_commands_js_1 = require("./runner/register-runner-commands.js");
6
7
  const start_js_1 = require("./runner/start.js");
7
8
  const shell_js_1 = require("./shell.js");
@@ -14,6 +15,7 @@ function registerCommands(program) {
14
15
  .description("Alias for starting the local CompanyHelm runner.")).action(start_js_1.runRunnerStartCommand);
15
16
  (0, register_runner_commands_js_1.registerRunnerCommands)(program);
16
17
  (0, status_js_1.registerStatusCommand)(program);
18
+ (0, logs_js_1.registerLogsCommand)(program);
17
19
  (0, register_thread_commands_js_1.registerThreadCommands)(program);
18
20
  (0, shell_js_1.registerShellCommand)(program);
19
21
  (0, register_sdk_commands_js_1.registerSdkCommands)(program);
@@ -340,7 +340,10 @@ function isNoActiveTurnSteerError(error) {
340
340
  return /no active turn to steer/i.test(toErrorMessage(error));
341
341
  }
342
342
  function isNoRunningTurnInterruptError(error) {
343
- return /no running turn to interrupt/i.test(toErrorMessage(error));
343
+ const message = toErrorMessage(error);
344
+ return /no running turn to interrupt/i.test(message)
345
+ || /thread .* is not running/i.test(message)
346
+ || /thread .* already stopped/i.test(message);
344
347
  }
345
348
  function isTurnCompletionTimeoutError(error) {
346
349
  return /timed out waiting for completion of turn/i.test(toErrorMessage(error));
@@ -1649,6 +1652,9 @@ async function resolveThreadAuthMode(cfg) {
1649
1652
  if (!codexSdk) {
1650
1653
  throw new Error("Codex SDK is not configured.");
1651
1654
  }
1655
+ if (codexSdk.authentication === "api-key") {
1656
+ return "dedicated";
1657
+ }
1652
1658
  if (codexSdk.authentication !== "host" && codexSdk.authentication !== "dedicated") {
1653
1659
  throw new Error(`Unsupported Codex authentication mode '${codexSdk.authentication}' for thread creation.`);
1654
1660
  }
@@ -1957,7 +1963,7 @@ async function reportNoRunningInterruptAsReady(cfg, commandChannel, request, thr
1957
1963
  await sendTurnExecutionUpdate(commandChannel, request.threadId, threadState.currentSdkTurnId, protos_1.TurnStatus.COMPLETED);
1958
1964
  }
1959
1965
  await sendThreadUpdate(commandChannel, request.threadId, protos_1.ThreadStatus.READY);
1960
- logger.info(logMessage);
1966
+ logger.warn(logMessage);
1961
1967
  }
1962
1968
  async function handleInterruptTurnRequest(cfg, commandChannel, request, logger) {
1963
1969
  let threadState;
@@ -40,6 +40,9 @@ exports.defaultUseDedicatedCodexAuthDependencies = {
40
40
  spawnCommand: node_child_process_1.spawn,
41
41
  spawnSyncCommand: node_child_process_1.spawnSync,
42
42
  };
43
+ function isDedicatedCodexAuthentication(authentication) {
44
+ return authentication === "dedicated" || authentication === "api-key";
45
+ }
43
46
  function resolveContainerPath(pathValue, containerHome) {
44
47
  if (pathValue === "~") {
45
48
  return containerHome;
@@ -279,14 +282,26 @@ async function ensureCodexRunnerStartState(cfg, overrides = {}) {
279
282
  const { db, client } = await deps.initDbFn(cfg.state_db_path);
280
283
  try {
281
284
  const existingSdk = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get();
285
+ const hostInfo = deps.getHostInfoFn(cfg.codex.codex_auth_path);
286
+ const dedicatedAuthPath = (0, node_path_1.join)((0, path_js_1.expandHome)(cfg.config_directory), cfg.codex.codex_auth_file_path);
287
+ const dedicatedAuthInfo = deps.getHostInfoFn(dedicatedAuthPath);
282
288
  if (deps.useDedicatedAuth) {
283
- if (existingSdk?.authentication === "dedicated" && existingSdk.status === "configured") {
289
+ if (existingSdk?.status === "configured" &&
290
+ isDedicatedCodexAuthentication(existingSdk.authentication) &&
291
+ dedicatedAuthInfo.codexAuthExists) {
284
292
  return;
285
293
  }
286
294
  await setCodexUnconfiguredInDb(db);
287
295
  return;
288
296
  }
289
- const hostInfo = deps.getHostInfoFn(cfg.codex.codex_auth_path);
297
+ if (existingSdk?.status === "configured") {
298
+ if (existingSdk.authentication === "host" && hostInfo.codexAuthExists) {
299
+ return;
300
+ }
301
+ if (isDedicatedCodexAuthentication(existingSdk.authentication) && dedicatedAuthInfo.codexAuthExists) {
302
+ return;
303
+ }
304
+ }
290
305
  if (hostInfo.codexAuthExists) {
291
306
  deps.logInfo(`Detected Codex host auth at ${(0, path_js_1.expandHome)(cfg.codex.codex_auth_path)}; using host auth automatically.`);
292
307
  await setCodexHostAuthInDb(db);
@@ -51,16 +51,6 @@ const terminal_js_1 = require("../utils/terminal.js");
51
51
  const path_js_1 = require("../utils/path.js");
52
52
  const NON_OVERRIDABLE_DAEMON_OPTION_NAMES = new Set(["daemon", "serverUrl", "secret", "help"]);
53
53
  const SHELL_PROMPT = "companyhelm db> ";
54
- const SHELL_HELP_TEXT = [
55
- "Available commands:",
56
- " help Show this help.",
57
- " list threads List full thread rows from the state DB.",
58
- " thread status <id> Show the full thread row for one thread.",
59
- " list containers List thread container fields from the state DB.",
60
- " thread docker <id> Docker exec bash into the selected thread runtime container.",
61
- " show daemon Show the daemon_state row from the state DB.",
62
- " exit Exit the shell.",
63
- ].join("\n");
64
54
  function resolveDaemonOptionValue(option, values) {
65
55
  const explicit = values[option.name];
66
56
  if (explicit !== undefined) {
@@ -115,6 +105,64 @@ function buildShellDaemonOverrideArgs(options, values) {
115
105
  }
116
106
  return args;
117
107
  }
108
+ const SHELL_TABLES = [
109
+ {
110
+ key: "threads",
111
+ label: "Threads",
112
+ menuLabel: "List threads",
113
+ menuHint: "Full rows from the threads table",
114
+ commandAliases: ["threads", "thread"],
115
+ loadRows: async (db) => (await db.select().from(schema_js_1.threads).orderBy(schema_js_1.threads.id).all()),
116
+ },
117
+ {
118
+ key: "agent-sdks",
119
+ label: "Agent SDKs",
120
+ menuLabel: "List SDKs",
121
+ menuHint: "Full rows from the agent_sdks table",
122
+ commandAliases: ["sdks", "sdk", "agent-sdks"],
123
+ loadRows: async (db) => (await db.select().from(schema_js_1.agentSdks).orderBy(schema_js_1.agentSdks.name).all()),
124
+ },
125
+ {
126
+ key: "llm-models",
127
+ label: "LLM models",
128
+ menuLabel: "List models",
129
+ menuHint: "Full rows from the llm_models table",
130
+ commandAliases: ["models", "model", "llm-models"],
131
+ loadRows: async (db) => (await db.select().from(schema_js_1.llmModels).orderBy(schema_js_1.llmModels.sdkName, schema_js_1.llmModels.name).all()),
132
+ },
133
+ {
134
+ key: "thread-user-message-request-store",
135
+ label: "Thread user message request store",
136
+ menuLabel: "List requests",
137
+ menuHint: "Full rows from the thread_user_message_request_store table",
138
+ commandAliases: ["requests", "request", "message-requests", "user-message-requests"],
139
+ loadRows: async (db) => (await db.select().from(schema_js_1.threadUserMessageRequestStore).orderBy(schema_js_1.threadUserMessageRequestStore.id).all()),
140
+ },
141
+ {
142
+ key: "daemon-state",
143
+ label: "Daemon state table",
144
+ menuLabel: "List daemon rows",
145
+ menuHint: "Full rows from the daemon_state table",
146
+ commandAliases: ["daemon", "daemons", "daemon-state"],
147
+ loadRows: async (db) => (await db.select().from(schema_js_1.daemonState).orderBy(schema_js_1.daemonState.id).all()),
148
+ },
149
+ ];
150
+ const SHELL_TABLE_BY_KEY = new Map(SHELL_TABLES.map((table) => [table.key, table]));
151
+ const SHELL_TABLE_BY_ALIAS = new Map(SHELL_TABLES.flatMap((table) => table.commandAliases.map((alias) => [alias, table])));
152
+ const SHELL_HELP_TEXT = [
153
+ "Available commands:",
154
+ " help Show this help.",
155
+ " list threads List full thread rows from the threads table.",
156
+ " list sdks List full rows from the agent_sdks table.",
157
+ " list models List full rows from the llm_models table.",
158
+ " list requests List full rows from the thread_user_message_request_store table.",
159
+ " list daemon List full rows from the daemon_state table.",
160
+ " thread status <id> Show the full thread row for one thread.",
161
+ " list containers List thread container fields from the state DB.",
162
+ " thread docker <id> Docker exec bash into the selected thread runtime container.",
163
+ " show daemon Show the daemon_state row from the state DB.",
164
+ " exit Exit the shell.",
165
+ ].join("\n");
118
166
  function jsonReplacer(_key, value) {
119
167
  return typeof value === "bigint" ? value.toString() : value;
120
168
  }
@@ -137,6 +185,9 @@ function printRow(label, row) {
137
185
  console.log(JSON.stringify(row, jsonReplacer, 2));
138
186
  console.log();
139
187
  }
188
+ function resolveShellTableByAlias(alias) {
189
+ return SHELL_TABLE_BY_ALIAS.get(alias.toLowerCase());
190
+ }
140
191
  function parseShellCommand(input) {
141
192
  const trimmed = input.trim();
142
193
  if (!trimmed) {
@@ -150,7 +201,16 @@ function parseShellCommand(input) {
150
201
  case "?":
151
202
  return { type: "help" };
152
203
  case "threads":
153
- return { type: "list-threads" };
204
+ return { type: "list-table", tableKey: "threads" };
205
+ case "sdks":
206
+ case "sdk":
207
+ return { type: "list-table", tableKey: "agent-sdks" };
208
+ case "models":
209
+ case "model":
210
+ return { type: "list-table", tableKey: "llm-models" };
211
+ case "requests":
212
+ case "request":
213
+ return { type: "list-table", tableKey: "thread-user-message-request-store" };
154
214
  case "containers":
155
215
  return { type: "list-containers" };
156
216
  case "daemon":
@@ -163,12 +223,15 @@ function parseShellCommand(input) {
163
223
  }
164
224
  }
165
225
  if (normalized.length === 2) {
166
- if (normalized[0] === "list" && normalized[1] === "threads") {
167
- return { type: "list-threads" };
168
- }
169
226
  if (normalized[0] === "list" && normalized[1] === "containers") {
170
227
  return { type: "list-containers" };
171
228
  }
229
+ if (normalized[0] === "list") {
230
+ const table = resolveShellTableByAlias(normalized[1]);
231
+ if (table) {
232
+ return { type: "list-table", tableKey: table.key };
233
+ }
234
+ }
172
235
  if (normalized[0] === "show" && normalized[1] === "daemon") {
173
236
  return { type: "show-daemon" };
174
237
  }
@@ -215,7 +278,7 @@ async function promptShellAction() {
215
278
  const action = await p.select({
216
279
  message: "Choose shell action",
217
280
  options: [
218
- { value: "list-threads", label: "List threads" },
281
+ { value: "list-table", label: "Inspect runner tables", hint: "Threads, SDKs, models, requests, daemon state" },
219
282
  { value: "thread-status", label: "Thread status" },
220
283
  { value: "list-containers", label: "List containers" },
221
284
  { value: "thread-docker-shell", label: "Thread docker shell (bash)" },
@@ -226,6 +289,17 @@ async function promptShellAction() {
226
289
  });
227
290
  return p.isCancel(action) ? null : action;
228
291
  }
292
+ async function promptShellTable() {
293
+ const selection = await p.select({
294
+ message: "Choose table to list",
295
+ options: SHELL_TABLES.map((table) => ({
296
+ value: table.key,
297
+ label: table.menuLabel,
298
+ hint: table.menuHint,
299
+ })),
300
+ });
301
+ return p.isCancel(selection) ? null : selection;
302
+ }
229
303
  async function runClackShell(db, stateDbPath) {
230
304
  p.intro(`CompanyHelm DB shell\n${(0, path_js_1.expandHome)(stateDbPath)}`);
231
305
  try {
@@ -240,9 +314,14 @@ async function runClackShell(db, stateDbPath) {
240
314
  case "help":
241
315
  console.log(SHELL_HELP_TEXT);
242
316
  break;
243
- case "list-threads":
244
- await runParsedShellCommand(db, { type: "list-threads" });
317
+ case "list-table": {
318
+ const tableKey = await promptShellTable();
319
+ if (!tableKey) {
320
+ break;
321
+ }
322
+ await runParsedShellCommand(db, { type: "list-table", tableKey });
245
323
  break;
324
+ }
246
325
  case "thread-status": {
247
326
  const threadId = await selectThreadId(db, "Select thread");
248
327
  if (!threadId) {
@@ -284,9 +363,14 @@ async function runParsedShellCommand(db, command) {
284
363
  case "help":
285
364
  console.log(SHELL_HELP_TEXT);
286
365
  return false;
287
- case "list-threads": {
288
- const rows = await db.select().from(schema_js_1.threads).orderBy(schema_js_1.threads.id).all();
289
- printRows("Threads", rows);
366
+ case "list-table": {
367
+ const table = SHELL_TABLE_BY_KEY.get(command.tableKey);
368
+ if (!table) {
369
+ console.log(`Unknown table key: ${command.tableKey}`);
370
+ return false;
371
+ }
372
+ const rows = await table.loadRows(db);
373
+ printRows(table.label, rows);
290
374
  return false;
291
375
  }
292
376
  case "thread-status": {
@@ -219,7 +219,7 @@ class AppServerContainerService {
219
219
  const hostAuthPath = (0, path_js_1.expandHome)(cfg.codex.codex_auth_path);
220
220
  const hostDedicatedAuthPath = `${(0, path_js_1.expandHome)(cfg.config_directory)}/${cfg.codex.codex_auth_file_path}`;
221
221
  const mountArgs = [];
222
- if (codexAuthMode === "dedicated") {
222
+ if (codexAuthMode === "dedicated" || codexAuthMode === "api-key") {
223
223
  if (!(0, host_js_1.getHostInfo)(hostDedicatedAuthPath).codexAuthExists) {
224
224
  throw new Error(`Dedicated Codex auth file was not found at ${hostDedicatedAuthPath}`);
225
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companyhelm/runner",
3
- "version": "0.0.19",
3
+ "version": "0.0.22",
4
4
  "description": "Run the CompanyHelm runner in fully isolated Docker sandboxes.",
5
5
  "license": "MIT",
6
6
  "repository": {