automata-cli 0.11.0-develop.457 → 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.
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
@@ -10,7 +10,7 @@ import {
10
10
  readConfig,
11
11
  readRawConfig,
12
12
  writeConfig
13
- } from "./chunk-OUJQLQP3.js";
13
+ } from "./chunk-DTZB5FOY.js";
14
14
 
15
15
  // src/config/ConfigWizard.tsx
16
16
  import { useState } from "react";
@@ -16,7 +16,7 @@ var DEFAULT_DO_WORK = {
16
16
  dumpOnBlock: true,
17
17
  postRunLog: true
18
18
  };
19
- var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete.\n\nEither way, always post a reply on the issue before you finish \u2014 including when you implemented and opened a pull request. Silence is indistinguishable from a crash, and the run will be reported as having produced no answer.";
19
+ var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file in the repository, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete.\n\nEither way, always post a reply on the issue before you finish \u2014 including when you implemented and opened a pull request. Silence is indistinguishable from a crash, and the run will be reported as having produced no answer.\n\nPost the reply yourself with the GitHub CLI: write it to a temporary file outside the repository (the only file you may create in this case) and run `gh issue comment <issue-number> --body-file <file>`, or `gh pr comment <pr-number> --body-file <file>` when you are replying on a pull request. `do-work` does not publish your output, so printing the answer to stdout does not count as a posted reply.";
20
20
  var DEFAULT_DO_WORK_PR_WORK_PROMPT = "You are the agent named in the context below, working on the pull request for a GitHub issue together with the people allowed to instruct you. Work on the branch named below, which is already checked out and up to date.\n\nAddress every message marked NEW and every unresolved review thread listed. Follow the project's existing conventions, run the tests and the linter, then commit and push to that branch. Do not merge the pull request and do not push to the base branch.\n\nOnce a review thread is fixed and pushed, resolve it (GraphQL `resolveReviewThread`) after replying; leave open any thread you did not fix. Reply on the pull request with a short summary of what you changed, or reply in the review thread when your answer belongs to a specific comment. Always post a reply \u2014 silence looks like a crash.";
21
21
  var DEFAULT_DO_WORK_PR_ORPHAN_PROMPT = "You are the agent named in the context below, working on a pull request that is not linked to any issue \u2014 a dependency bump, or another change opened without one \u2014 together with the people allowed to instruct you. Work on the branch named below, which is already checked out and up to date.\n\nAddress every message marked NEW and every unresolved review thread listed. Typical work here is finding out why the checks are failing, bringing the branch up to date with the base branch, and fixing whatever the change needs in order to be mergeable. Follow the project's existing conventions, run the tests and the linter, then commit and push to that branch.\n\nDo not merge the pull request, do not close it, and do not push to the base branch. If you conclude that it should be merged or closed, say so in your reply and leave the decision to the humans.\n\nOnce a review thread is fixed and pushed, resolve it (GraphQL `resolveReviewThread`) after replying; leave open any thread you did not fix. Reply on the pull request with a short summary of what you did and what you recommend, or reply in the review thread when your answer belongs to a specific comment. Always post a reply \u2014 silence looks like a crash.";
22
22
  var CONFIG_DIR = ".automata";
package/dist/index.js CHANGED
@@ -11,10 +11,10 @@ import {
11
11
  readConfig,
12
12
  readRawConfig,
13
13
  writeConfig
14
- } from "./chunk-OUJQLQP3.js";
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";
@@ -321,7 +321,7 @@ var configCommand = new Command("config").description("Configure automata settin
321
321
  const [{ render }, React, { ConfigWizard }] = await Promise.all([
322
322
  import("ink"),
323
323
  import("react"),
324
- import("./ConfigWizard-QZ2CJJS3.js")
324
+ import("./ConfigWizard-KI43QOGG.js")
325
325
  ]);
326
326
  const { waitUntilExit } = render(React.createElement(ConfigWizard));
327
327
  await waitUntilExit();
@@ -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.457",
3
+ "version": "0.11.0-develop.468",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {