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.
- package/README.md +6 -0
- package/dist/index.js +102 -6
- 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
|
|
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,
|
|
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
|
|
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) {
|