automata-cli 0.11.0-develop.462 → 0.11.0-develop.468

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 (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +102 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -133,6 +133,12 @@ One tick of the autonomous loop: answer the open issues whose newest message fro
133
133
 
134
134
  ---
135
135
 
136
+ ## `automata conductor`
137
+
138
+ One cron-friendly tick of the conductor role, run as an allowed (human) account with its own run lock beside `do-work`'s. Reference: [docs/conductor.md](docs/conductor.md).
139
+
140
+ ---
141
+
136
142
  ## Development
137
143
 
138
144
  ### Prerequisites
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  } from "./chunk-DTZB5FOY.js";
15
15
 
16
16
  // src/index.ts
17
- import { Command as Command7 } from "commander";
17
+ import { Command as Command8 } from "commander";
18
18
 
19
19
  // src/version.ts
20
20
  import { readFileSync } from "fs";
@@ -3581,6 +3581,19 @@ function identityProblemFor(login2, agentUser, allowedUsers) {
3581
3581
  }
3582
3582
  return `\`gh\` is authenticated as "${login2}" but agentUser is "${agentUser}". Comments posted under that identity are neither the agent's nor an authorized user's, so they are filtered out of the conversation: the answer boundary would never advance and the same message would start a run on every tick. Authenticate \`gh\` as the agent account (${agentUser}) in this environment, or correct \`agentUser\`.`;
3583
3583
  }
3584
+ function conductorIdentityProblemFor(login2, agentUser, allowedUsers) {
3585
+ if (login2 === null) {
3586
+ return "Could not determine which account `gh` is authenticated as, and the conductor must run as an account listed in allowedUsers. Run `gh auth login` as that account, or set `GH_TOKEN` in the scheduler's environment.";
3587
+ }
3588
+ const lower = login2.toLowerCase();
3589
+ if (lower === agentUser.toLowerCase()) {
3590
+ return `\`gh\` is authenticated as "${login2}", which is the agent (agentUser). The conductor instructs the agent and must run as a different account listed in allowedUsers.`;
3591
+ }
3592
+ if (!allowedUsers.some((user) => user.toLowerCase() === lower)) {
3593
+ return `\`gh\` is authenticated as "${login2}", which is not listed in allowedUsers. Authenticate \`gh\` as one of the allowed users, or add this account to allowedUsers.`;
3594
+ }
3595
+ return null;
3596
+ }
3584
3597
 
3585
3598
  // src/github/workDetection.ts
3586
3599
  var NO_ISSUE_MESSAGES = {
@@ -4137,12 +4150,15 @@ function readHeartbeat(token, cwd = process.cwd()) {
4137
4150
  var LOCK_DIR = ".automata";
4138
4151
  var LOCK_FILE = "automata.lock";
4139
4152
  var RUN_LOCK_RELATIVE_PATH = `${LOCK_DIR}/${LOCK_FILE}`;
4153
+ var CONDUCTOR_LOCK_FILE = "conductor.lock";
4154
+ var CONDUCTOR_LOCK_RELATIVE_PATH = `${LOCK_DIR}/${CONDUCTOR_LOCK_FILE}`;
4140
4155
  var AUTOMATA_OWN_PATHS = [
4141
4156
  RUN_LOCK_RELATIVE_PATH,
4157
+ CONDUCTOR_LOCK_RELATIVE_PATH,
4142
4158
  HEARTBEAT_RELATIVE_PATH
4143
4159
  ];
4144
- function lockPath() {
4145
- return join4(process.cwd(), LOCK_DIR, LOCK_FILE);
4160
+ function lockPath(lockFile = LOCK_FILE) {
4161
+ return join4(process.cwd(), LOCK_DIR, lockFile);
4146
4162
  }
4147
4163
  function processStartedAt(pid) {
4148
4164
  try {
@@ -4278,8 +4294,8 @@ function publishLock(path, command, token) {
4278
4294
  }
4279
4295
  }
4280
4296
  }
4281
- function acquireRunLock(command, staleMinutes) {
4282
- const path = lockPath();
4297
+ function acquireRunLock(command, staleMinutes, lockFile = LOCK_FILE) {
4298
+ const path = lockPath(lockFile);
4283
4299
  const token = randomUUID();
4284
4300
  mkdirSync(join4(process.cwd(), LOCK_DIR), { recursive: true });
4285
4301
  if (publishLock(path, command, token)) {
@@ -4299,6 +4315,9 @@ var UNKNOWN_OWNER = {
4299
4315
  command: "unknown",
4300
4316
  token: ""
4301
4317
  };
4318
+ function acquireConductorLock(staleMinutes) {
4319
+ return acquireRunLock("conductor", staleMinutes, CONDUCTOR_LOCK_FILE);
4320
+ }
4302
4321
  function claimStaleLock(path, token, expected) {
4303
4322
  const claimed = `${path}.stale.${token}`;
4304
4323
  try {
@@ -8063,8 +8082,84 @@ function toPlanJson(decision) {
8063
8082
  };
8064
8083
  }
8065
8084
 
8085
+ // src/commands/conductor.ts
8086
+ import { Command as Command7 } from "commander";
8087
+ function fail2(message) {
8088
+ process.stderr.write(`Error: ${message}
8089
+ `);
8090
+ return 1;
8091
+ }
8092
+ function runConductor() {
8093
+ let config;
8094
+ try {
8095
+ config = readConfig();
8096
+ } catch (err) {
8097
+ return fail2(err.message);
8098
+ }
8099
+ if (!isExplicitGitHub(config)) {
8100
+ return fail2(
8101
+ azdoUnsupportedMessage(
8102
+ "conductor",
8103
+ "It needs the GitHub APIs; set the remote with `automata config set type gh`."
8104
+ )
8105
+ );
8106
+ }
8107
+ const allowedUsers = (config.allowedUsers ?? []).filter((user) => user.trim().length > 0);
8108
+ if (allowedUsers.length === 0) {
8109
+ return fail2("No allowed users configured. Run `automata config set allowed-users <user1,user2>`.");
8110
+ }
8111
+ const agentUser = (config.agentUser ?? "").trim();
8112
+ if (agentUser.length === 0) {
8113
+ return fail2("No agent user configured. Run `automata config set agent-user <login>`.");
8114
+ }
8115
+ let login2;
8116
+ try {
8117
+ login2 = getAuthenticatedLogin();
8118
+ } catch (err) {
8119
+ return fail2(`\`gh\` could not be queried: ${err.message}`);
8120
+ }
8121
+ const problem = conductorIdentityProblemFor(login2, agentUser, allowedUsers);
8122
+ if (problem !== null) return fail2(problem);
8123
+ const rawStale = config.doWork?.lockStaleMinutes;
8124
+ if (rawStale !== void 0 && rawStale !== null) {
8125
+ if (typeof rawStale !== "number" || !Number.isSafeInteger(rawStale) || rawStale < 1) {
8126
+ return fail2(`doWork.lockStaleMinutes must be a positive integer, got ${JSON.stringify(rawStale)}.`);
8127
+ }
8128
+ }
8129
+ const staleMinutes = config.doWork?.lockStaleMinutes ?? DEFAULT_DO_WORK.lockStaleMinutes;
8130
+ const lock = acquireConductorLock(staleMinutes);
8131
+ if (!lock.ok) {
8132
+ const held = lock.heldBy;
8133
+ process.stdout.write(
8134
+ `Another conductor is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}). Doing nothing.
8135
+ `
8136
+ );
8137
+ if (lock.suspect) {
8138
+ process.stderr.write(
8139
+ `Warning: that lock has been held longer than ${String(staleMinutes)} minutes. If no tick is really running, its process id was probably reused; remove ${CONDUCTOR_LOCK_RELATIVE_PATH} once you have confirmed that.
8140
+ `
8141
+ );
8142
+ return 2;
8143
+ }
8144
+ return 0;
8145
+ }
8146
+ try {
8147
+ process.stdout.write(`Conductor: running as ${login2 ?? ""}. Nothing to do yet.
8148
+ `);
8149
+ return 0;
8150
+ } finally {
8151
+ lock.handle.release();
8152
+ }
8153
+ }
8154
+ var conductorCommand = new Command7("conductor").description(
8155
+ "Run one conductor tick as an allowed (human) account: verify the identity, take the conductor's own run lock, then act"
8156
+ ).action(() => {
8157
+ const exitCode = runConductor();
8158
+ if (exitCode !== 0) process.exit(exitCode);
8159
+ });
8160
+
8066
8161
  // src/index.ts
8067
- var program = new Command7();
8162
+ var program = new Command8();
8068
8163
  program.name("automata").description("Automata CLI tool").version(version, "-v, --version");
8069
8164
  program.addCommand(configCommand);
8070
8165
  program.addCommand(gitCommand2);
@@ -8072,6 +8167,7 @@ program.addCommand(implementNextCommand);
8072
8167
  program.addCommand(executeCommand);
8073
8168
  program.addCommand(executePromptCommand);
8074
8169
  program.addCommand(doWorkCommand);
8170
+ program.addCommand(conductorCommand);
8075
8171
  program.showHelpAfterError();
8076
8172
  program.parse();
8077
8173
  if (process.argv.length <= 2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.11.0-develop.462",
3
+ "version": "0.11.0-develop.468",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {