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
|
|
@@ -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-
|
|
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";
|
|
@@ -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-
|
|
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,
|
|
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) {
|