superagent-claude 0.2.0 → 0.3.1
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 +1 -1
- package/lib/cli.js +1 -1
- package/lib/{main-DKxToYW7.js → main-CingWi3i.js} +160 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ It needs Node.js 22.19 or later. `npx superagent-claude start --config superagen
|
|
|
20
20
|
|
|
21
21
|
`auth.mode` picks which one (`api-key` or `subscription`); Bedrock and Vertex also work. See [docs/authentication.md](docs/authentication.md) for both modes and the subscription terms warning.
|
|
22
22
|
|
|
23
|
-
The config needs `repositories
|
|
23
|
+
The config needs `repositories` and `policy.allowed`; `auth.mode` defaults to `api-key` and needs its credential. See [docs/getting-started.md](docs/getting-started.md), which also covers building from source.
|
|
24
24
|
|
|
25
25
|
A container image is published as `ghcr.io/ok0/superagent-claude`. See [Docker](docs/deploy.md#docker) to run it.
|
|
26
26
|
|
package/lib/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
13
13
|
import { checkSync, lockSync } from "proper-lockfile";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
// package.json
|
|
16
|
-
var version = "0.
|
|
16
|
+
var version = "0.3.1";
|
|
17
17
|
// src/infra/env.ts
|
|
18
18
|
/**
|
|
19
19
|
* Environment for subprocesses: only the inherited whitelist (PATH, HOME, LANG) plus explicitly named variables.
|
|
@@ -1438,6 +1438,7 @@ var Assignments = class extends EnginePart {
|
|
|
1438
1438
|
if (report.state === "blocked" || report.state === "failed") ticket.undelivered = ticket.undelivered.filter((id) => !automatic(ticket.inputs.find((input) => input.id === id)));
|
|
1439
1439
|
this.repo.tickets.save(ticket);
|
|
1440
1440
|
if (report.limit && report.state === "blocked") this.replies.limitStopped(ticket, assignment.id, report.limit, pull);
|
|
1441
|
+
if (report.workspace && report.state === "blocked") this.replies.workspaceBlocked(ticket, assignment.id, summary);
|
|
1441
1442
|
const ready = !ticket.follows?.length ? "completed" : this.tickets.followsDone(ticket)?.outcome;
|
|
1442
1443
|
const outcome = report.state === "dropped" ? this.tickets.followsDone(ticket)?.outcome ?? "dropped" : report.state === "ready" ? ready : void 0;
|
|
1443
1444
|
if (outcome) this.tickets.end(ticketId, "executor", outcome, at);
|
|
@@ -2080,6 +2081,10 @@ function refusalText(reason) {
|
|
|
2080
2081
|
function limitText(limit, pull) {
|
|
2081
2082
|
return `I stopped before finishing: the ${limit} limit for this task was reached.` + (pull ? ` Work so far: ${pull}.` : "") + " Reply here to continue.";
|
|
2082
2083
|
}
|
|
2084
|
+
/** The note a person gets when a configured branch is missing, so the task could not start. */
|
|
2085
|
+
function workspaceBlockedText(reason) {
|
|
2086
|
+
return `I could not start this task. ${reason} Reply here once that is fixed to try again.`;
|
|
2087
|
+
}
|
|
2083
2088
|
/**
|
|
2084
2089
|
* Each message is queued under a deterministic id, so it is never sent twice. Replies to a person go where the person's
|
|
2085
2090
|
* message was written, and a result where the latest person's message to the ticket was written.
|
|
@@ -2118,6 +2123,24 @@ var Replies = class extends EnginePart {
|
|
|
2118
2123
|
ticketId: ticket.id
|
|
2119
2124
|
});
|
|
2120
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Queue the reason the task could not start where the latest person's message to the ticket was written, else where
|
|
2128
|
+
* it started; nothing when there is no conversation. Its id is `workspace-blocked:<assignmentId>:<triggerId>` for
|
|
2129
|
+
* the latest person's message, so a repeat or restart sends nothing more, and a person's reply that meets the same
|
|
2130
|
+
* block is told again.
|
|
2131
|
+
*/
|
|
2132
|
+
workspaceBlocked(ticket, assignmentId, reason) {
|
|
2133
|
+
const channel = this.replyChannel(ticket);
|
|
2134
|
+
if (!channel) return;
|
|
2135
|
+
const latest = ticket.inputs.findLast((input) => input.kind === "trigger" && input.human === true && !input.context)?.triggerId ?? (this.repo.triggers.get(ticket.origin.triggerId)?.human ? ticket.origin.triggerId : void 0);
|
|
2136
|
+
this.options.outbox.send({
|
|
2137
|
+
id: latest ? `workspace-blocked:${assignmentId}:${latest}` : `workspace-blocked:${assignmentId}`,
|
|
2138
|
+
channel,
|
|
2139
|
+
text: workspaceBlockedText(reason),
|
|
2140
|
+
kind: "reply",
|
|
2141
|
+
ticketId: ticket.id
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2121
2144
|
withdrawAttention(attentionId) {
|
|
2122
2145
|
this.options.outbox.withdraw(key("attention-reply", attentionId));
|
|
2123
2146
|
}
|
|
@@ -2866,6 +2889,12 @@ function settingsPolicy(settings, sources) {
|
|
|
2866
2889
|
}
|
|
2867
2890
|
const DEFAULT_STATE_DIR = "~/.superagent-claude";
|
|
2868
2891
|
const RepositorySchema = z.string().trim().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/, "must be owner/name");
|
|
2892
|
+
const BranchNameSchema = z.string().regex(/^(?![-/])(?!.*\.\.)(?!.*\/\/)(?!.*\/$)(?!.*\.lock$)[A-Za-z0-9._/-]{1,200}$/, "must be a branch name: letters, digits and . _ - /, not starting with - or /, without .. or //, not ending with / or .lock");
|
|
2893
|
+
const RepositoryEntrySchema = z.union([RepositorySchema, z.object({
|
|
2894
|
+
repository: RepositorySchema,
|
|
2895
|
+
branchFrom: BranchNameSchema.optional(),
|
|
2896
|
+
pullRequestTo: BranchNameSchema.optional()
|
|
2897
|
+
}).strict()]);
|
|
2869
2898
|
const AUTH_MODES = Object.keys(AUTH_ENV);
|
|
2870
2899
|
/** A refused start (exit code 2); `rule` names the violated startup rule. */
|
|
2871
2900
|
var StartupRefused = class extends Error {
|
|
@@ -2877,7 +2906,23 @@ var StartupRefused = class extends Error {
|
|
|
2877
2906
|
}
|
|
2878
2907
|
};
|
|
2879
2908
|
const ConfigSchema = z.object({
|
|
2880
|
-
repositories: z.array(
|
|
2909
|
+
repositories: z.array(RepositoryEntrySchema).min(1, "must name at least one repository").superRefine((items, ctx) => {
|
|
2910
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2911
|
+
const reported = /* @__PURE__ */ new Set();
|
|
2912
|
+
for (const item of items) {
|
|
2913
|
+
const name = typeof item === "string" ? item : item.repository;
|
|
2914
|
+
const key = name.toLowerCase();
|
|
2915
|
+
if (seen.has(key)) {
|
|
2916
|
+
if (!reported.has(key)) {
|
|
2917
|
+
reported.add(key);
|
|
2918
|
+
ctx.addIssue({
|
|
2919
|
+
code: "custom",
|
|
2920
|
+
message: `lists ${name} more than once`
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
} else seen.add(key);
|
|
2924
|
+
}
|
|
2925
|
+
}).transform((items) => items.map((item) => typeof item === "string" ? item : item.repository)),
|
|
2881
2926
|
policy: z.object({
|
|
2882
2927
|
allowed: z.array(LoginSchema).min(1, "must name at least one login"),
|
|
2883
2928
|
acceptMode: z.enum([
|
|
@@ -2932,6 +2977,24 @@ const ConfigSchema = z.object({
|
|
|
2932
2977
|
}),
|
|
2933
2978
|
stateDir: z.string().trim().min(1).default(DEFAULT_STATE_DIR)
|
|
2934
2979
|
}).strict();
|
|
2980
|
+
function repositoryBranches(entries) {
|
|
2981
|
+
const parsed = z.array(RepositoryEntrySchema).safeParse(entries);
|
|
2982
|
+
if (!parsed.success) return {};
|
|
2983
|
+
const branches = {};
|
|
2984
|
+
for (const entry of parsed.data) {
|
|
2985
|
+
if (typeof entry === "string") continue;
|
|
2986
|
+
const { repository, branchFrom, pullRequestTo } = entry;
|
|
2987
|
+
if (branchFrom === void 0 && pullRequestTo === void 0) continue;
|
|
2988
|
+
branches[repository.toLowerCase()] = {
|
|
2989
|
+
...branchFrom !== void 0 ? { branchFrom } : {},
|
|
2990
|
+
...pullRequestTo !== void 0 ? { pullRequestTo } : {}
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
return branches;
|
|
2994
|
+
}
|
|
2995
|
+
function branchesOf(config, repository) {
|
|
2996
|
+
return config.branches?.[repository.toLowerCase()] ?? {};
|
|
2997
|
+
}
|
|
2935
2998
|
var ConfigError = class extends StartupRefused {
|
|
2936
2999
|
constructor(message) {
|
|
2937
3000
|
super("config", message);
|
|
@@ -2960,9 +3023,11 @@ function loadConfig(path, env = process.env) {
|
|
|
2960
3023
|
const result = ConfigSchema.safeParse(raw);
|
|
2961
3024
|
if (!result.success) throw new ConfigError(`The config file ${path} is invalid:\n` + result.error.issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n"));
|
|
2962
3025
|
const data = result.data;
|
|
3026
|
+
const branches = repositoryBranches(raw.repositories);
|
|
2963
3027
|
return {
|
|
2964
3028
|
...data,
|
|
2965
|
-
stateDir: expandStateDir(data.stateDir, dirname(path), env)
|
|
3029
|
+
stateDir: expandStateDir(data.stateDir, dirname(path), env),
|
|
3030
|
+
...Object.keys(branches).length ? { branches } : {}
|
|
2966
3031
|
};
|
|
2967
3032
|
}
|
|
2968
3033
|
const byLogin = (a, b) => {
|
|
@@ -3001,18 +3066,8 @@ const SUBSCRIPTION_POLICY_WARNING = "auth.mode subscription runs the agent on a
|
|
|
3001
3066
|
const SHARED_USE_WARNING = "ToS risk: with auth.mode subscription, people other than the subscription owner (policy.allowed is not exactly [auth.owner]) can make the agent work on the owner's subscription; this shared use may violate the subscription terms.";
|
|
3002
3067
|
const SANDBOX_DOC = "docs/deploy.md#sandbox-in-containers";
|
|
3003
3068
|
const UNSANDBOXED_WARNING = `security.allowUnsandboxed is true and the runtime sandbox is unavailable here: sessions run without the sandbox, network.allow is not enforced and commands can read the agent's tokens from other processes; restrict egress outside the agent (see ${SANDBOX_DOC})`;
|
|
3004
|
-
const RULE_NUMBER = {
|
|
3005
|
-
credential: 1,
|
|
3006
|
-
"allowed-empty": 2,
|
|
3007
|
-
"repositories-empty": 3,
|
|
3008
|
-
"agent-allowed": 4,
|
|
3009
|
-
"subscription-shared": 6,
|
|
3010
|
-
unsandboxed: 7,
|
|
3011
|
-
"no-repository": 8
|
|
3012
|
-
};
|
|
3013
3069
|
function refused(rule, text) {
|
|
3014
|
-
|
|
3015
|
-
return new StartupRefused(rule, number ? `Startup refused (rule ${number}, ${rule}): ${text}` : `Startup refused (${rule}): ${text}`);
|
|
3070
|
+
return new StartupRefused(rule, `Startup refused (${rule}): ${text}`);
|
|
3016
3071
|
}
|
|
3017
3072
|
const present = (value) => typeof value === "string" && value.trim() !== "";
|
|
3018
3073
|
function resolveCredential(mode, env) {
|
|
@@ -3257,6 +3312,13 @@ var ReadLedger = class {
|
|
|
3257
3312
|
};
|
|
3258
3313
|
const DRAIN_MS = 2e3;
|
|
3259
3314
|
const workerKey = (ticketId) => `worker-${ticketId}`;
|
|
3315
|
+
/** A configured branch is missing on the remote; the message is the reason a person reads. */
|
|
3316
|
+
var WorkspaceBlocked = class extends Error {
|
|
3317
|
+
constructor(message) {
|
|
3318
|
+
super(message);
|
|
3319
|
+
this.name = "WorkspaceBlocked";
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3260
3322
|
/**
|
|
3261
3323
|
* Mode of a ticket Input (see docs/runtime-contract.md): a person's message steers; notes, reopen markers and
|
|
3262
3324
|
* a message no person wrote that waited on a ready ticket (`quiet`) are injected; everything else is a followup.
|
|
@@ -3542,6 +3604,14 @@ var WorkerExecutor = class {
|
|
|
3542
3604
|
try {
|
|
3543
3605
|
workspace = await this.deps.prepare(ticket);
|
|
3544
3606
|
} catch (e) {
|
|
3607
|
+
if (e instanceof WorkspaceBlocked) {
|
|
3608
|
+
this.report(ticketId, {
|
|
3609
|
+
state: "blocked",
|
|
3610
|
+
summary: e.message,
|
|
3611
|
+
workspace: true
|
|
3612
|
+
});
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3545
3615
|
this.report(ticketId, {
|
|
3546
3616
|
state: "failed",
|
|
3547
3617
|
summary: `The workspace could not be prepared: ${this.message(e)}`
|
|
@@ -6389,6 +6459,7 @@ function truncateOutput(text, maxLines, maxBytes) {
|
|
|
6389
6459
|
* Clone and fetch get the injected access env (authentication), and errors mask its secrets.
|
|
6390
6460
|
* Publishing pushes an agent branch without force, adding co-author trailers only to commits not yet pushed.
|
|
6391
6461
|
* Access errors carry the HTTP status when one is known. A transient push failure is retried up to 3 times (2 s, 5 s).
|
|
6462
|
+
* A worktree starts from the configured remote branch, the branch choice is fixed per ticket in a host file outside the worktree, and a missing configured branch stops prepare or push with `WorkspaceBlocked`.
|
|
6392
6463
|
*/
|
|
6393
6464
|
var RepositoryAccessError = class extends Error {
|
|
6394
6465
|
repository;
|
|
@@ -6491,7 +6562,30 @@ var Workspaces = class {
|
|
|
6491
6562
|
clonePath(repository) {
|
|
6492
6563
|
return join(this.stateDir, "repos", repository.replace("/", "__"));
|
|
6493
6564
|
}
|
|
6494
|
-
|
|
6565
|
+
branchesPath(ticketId) {
|
|
6566
|
+
return join(this.stateDir, "branches", ticketId + ".json");
|
|
6567
|
+
}
|
|
6568
|
+
fixedBranches(ticketId) {
|
|
6569
|
+
const path = this.branchesPath(ticketId);
|
|
6570
|
+
if (!existsSync(path)) return void 0;
|
|
6571
|
+
const record = JSON.parse(readFileSync(path, "utf8"));
|
|
6572
|
+
if (typeof record !== "object" || record === null || Array.isArray(record)) throw new Error(`The branch record of ticket ${ticketId} is unreadable`);
|
|
6573
|
+
const { from, to } = record;
|
|
6574
|
+
if (from !== void 0 && typeof from !== "string" || to !== void 0 && typeof to !== "string") throw new Error(`The branch record of ticket ${ticketId} is unreadable`);
|
|
6575
|
+
return {
|
|
6576
|
+
...from !== void 0 ? { from } : {},
|
|
6577
|
+
...to !== void 0 ? { to } : {}
|
|
6578
|
+
};
|
|
6579
|
+
}
|
|
6580
|
+
remoteHas(clone, name) {
|
|
6581
|
+
return this.git([
|
|
6582
|
+
"rev-parse",
|
|
6583
|
+
"--verify",
|
|
6584
|
+
"--quiet",
|
|
6585
|
+
"refs/remotes/origin/" + name
|
|
6586
|
+
], clone).then(() => true, () => false);
|
|
6587
|
+
}
|
|
6588
|
+
async prepare(ticket, name, branches = {}) {
|
|
6495
6589
|
if (!NAME.test(name) || name.includes("..") || name.endsWith(".lock")) throw new Error("Invalid worktree name: " + name);
|
|
6496
6590
|
const repository = this.repositoryOf(ticket);
|
|
6497
6591
|
if (!REPOSITORY.test(repository)) throw new RepositoryAccessError(repository, "not an owner/name repository");
|
|
@@ -6517,6 +6611,15 @@ var Workspaces = class {
|
|
|
6517
6611
|
const base = await this.clone(repository);
|
|
6518
6612
|
mkdirSync(dirname(cwd), { recursive: true });
|
|
6519
6613
|
await this.git(["worktree", "prune"], base);
|
|
6614
|
+
const stored = this.fixedBranches(ticket.id);
|
|
6615
|
+
const fixed = stored ?? {
|
|
6616
|
+
...branches.branchFrom ? { from: branches.branchFrom } : {},
|
|
6617
|
+
...branches.pullRequestTo ? { to: branches.pullRequestTo } : {}
|
|
6618
|
+
};
|
|
6619
|
+
const names = [...new Set([fixed.from, fixed.to].filter((name) => name !== void 0))];
|
|
6620
|
+
const missing = [];
|
|
6621
|
+
for (const name of names) if (!await this.remoteHas(base, name)) missing.push(name);
|
|
6622
|
+
if (missing.length) throw new WorkspaceBlocked(`${repository} has no branch ${missing.join(", ")}, so no workspace was made. A person must create the branch or change branchFrom or pullRequestTo for ${repository} in the config.`);
|
|
6520
6623
|
const exists = await this.git([
|
|
6521
6624
|
"rev-parse",
|
|
6522
6625
|
"--verify",
|
|
@@ -6535,8 +6638,16 @@ var Workspaces = class {
|
|
|
6535
6638
|
"-b",
|
|
6536
6639
|
branch,
|
|
6537
6640
|
cwd,
|
|
6538
|
-
"origin/HEAD"
|
|
6641
|
+
fixed.from ? "origin/" + fixed.from : "origin/HEAD"
|
|
6539
6642
|
], base);
|
|
6643
|
+
if (stored === void 0) {
|
|
6644
|
+
const path = this.branchesPath(ticket.id);
|
|
6645
|
+
mkdirSync(dirname(path), {
|
|
6646
|
+
recursive: true,
|
|
6647
|
+
mode: 448
|
|
6648
|
+
});
|
|
6649
|
+
writeFileSync(path, JSON.stringify(fixed) + "\n", { mode: 384 });
|
|
6650
|
+
}
|
|
6540
6651
|
return {
|
|
6541
6652
|
repository,
|
|
6542
6653
|
cwd,
|
|
@@ -6580,6 +6691,7 @@ var Workspaces = class {
|
|
|
6580
6691
|
return dir;
|
|
6581
6692
|
}
|
|
6582
6693
|
async remove(ticketId) {
|
|
6694
|
+
rmSync(this.branchesPath(ticketId), { force: true });
|
|
6583
6695
|
const path = this.worktreePath(ticketId);
|
|
6584
6696
|
if (!existsSync(path)) return;
|
|
6585
6697
|
const common = existsSync(join(path, ".git")) ? (await this.git([
|
|
@@ -6648,6 +6760,18 @@ var Workspaces = class {
|
|
|
6648
6760
|
"HEAD"
|
|
6649
6761
|
], cwd)).trim();
|
|
6650
6762
|
}
|
|
6763
|
+
async reviewBase(cwd, repo) {
|
|
6764
|
+
const configured = this.fixedBranches(basename(resolve(cwd)))?.from;
|
|
6765
|
+
if (configured === void 0) return "origin/HEAD";
|
|
6766
|
+
return await this.git([
|
|
6767
|
+
...repo,
|
|
6768
|
+
...GIT_READ_OPTIONS,
|
|
6769
|
+
"rev-parse",
|
|
6770
|
+
"--verify",
|
|
6771
|
+
"--quiet",
|
|
6772
|
+
"refs/remotes/origin/" + configured
|
|
6773
|
+
], cwd).then(() => true, () => false) ? "refs/remotes/origin/" + configured : "origin/HEAD";
|
|
6774
|
+
}
|
|
6651
6775
|
async diffStat(cwd, base) {
|
|
6652
6776
|
const repo = this.repository(cwd);
|
|
6653
6777
|
const known = base !== void 0 && SHA.test(base) && await this.git([
|
|
@@ -6661,7 +6785,7 @@ var Workspaces = class {
|
|
|
6661
6785
|
...repo,
|
|
6662
6786
|
...GIT_READ_OPTIONS,
|
|
6663
6787
|
"merge-base",
|
|
6664
|
-
|
|
6788
|
+
await this.reviewBase(cwd, repo),
|
|
6665
6789
|
"HEAD"
|
|
6666
6790
|
], cwd)).trim();
|
|
6667
6791
|
const out = await this.git([
|
|
@@ -6742,7 +6866,13 @@ var Workspaces = class {
|
|
|
6742
6866
|
const branch = (await this.git(["branch", "--show-current"], cwd)).trim();
|
|
6743
6867
|
if (!branch.startsWith("agent/")) throw new Error("The worktree is not on its agent branch");
|
|
6744
6868
|
const clone = await this.clone(repository);
|
|
6745
|
-
const
|
|
6869
|
+
const fixed = this.fixedBranches(ticket.id);
|
|
6870
|
+
const target = fixed?.to ?? fixed?.from;
|
|
6871
|
+
let base;
|
|
6872
|
+
if (target) {
|
|
6873
|
+
if (!await this.remoteHas(clone, target)) throw new WorkspaceBlocked(`${repository} has no branch ${target} to open the pull request against, so nothing was pushed and no pull request was opened. A person must create the branch or change branchFrom or pullRequestTo for ${repository} in the config.`);
|
|
6874
|
+
base = target;
|
|
6875
|
+
} else base = (await this.git([
|
|
6746
6876
|
"symbolic-ref",
|
|
6747
6877
|
"--short",
|
|
6748
6878
|
"refs/remotes/origin/HEAD"
|
|
@@ -9019,7 +9149,8 @@ function readTool(description, grammar, read) {
|
|
|
9019
9149
|
* Policy is checked before any git or GitHub call: live ticket, configured repository, a PR this ticket bound,
|
|
9020
9150
|
* and delivery.allowAutoMerge. Publish re-checks push permission first and returns `published: false`
|
|
9021
9151
|
* without any write when it is missing. Then the host review decision may stop it with `review_required`, again without any write.
|
|
9022
|
-
* Repository access errors file a failed report.
|
|
9152
|
+
* Repository access errors file a failed report. A configured target branch missing on the remote returns
|
|
9153
|
+
* `published: false` before any push.
|
|
9023
9154
|
*/
|
|
9024
9155
|
const REVIEW_REASON = "Run the superagent-reviewer subagent before publishing, give it the original request and the human constraints, apply only bug and missing-requirement findings, then call orchestrator_publish again";
|
|
9025
9156
|
/** The issue a Trigger is on: its meta names a repository and a number, and `isPr` is false. */
|
|
@@ -9081,7 +9212,7 @@ function deliveryTools(host, ticketId) {
|
|
|
9081
9212
|
return [
|
|
9082
9213
|
tool$1({
|
|
9083
9214
|
name: "orchestrator_publish",
|
|
9084
|
-
description: "Push this ticket's commits and open its pull request, or reuse the open one for the branch; never force-pushes. Binds the PR thread to this ticket. Returns published: false, pushing nothing, when the agent account has no write access to the repository. Returns published: false with review_required, pushing nothing, when the host requires a review first: run the superagent-reviewer subagent, then publish again.",
|
|
9215
|
+
description: "Push this ticket's commits and open its pull request, or reuse the open one for the branch; never force-pushes. Binds the PR thread to this ticket. Returns published: false, pushing nothing, when the agent account has no write access to the repository. Returns published: false with review_required, pushing nothing, when the host requires a review first: run the superagent-reviewer subagent, then publish again. Returns published: false, pushing nothing, when the configured target branch is missing on the remote.",
|
|
9085
9216
|
schema: z.object({
|
|
9086
9217
|
title: z.string().trim().min(1).max(256),
|
|
9087
9218
|
body: z.string().trim().min(1).max(6e4),
|
|
@@ -9122,6 +9253,12 @@ function deliveryTools(host, ticketId) {
|
|
|
9122
9253
|
try {
|
|
9123
9254
|
pushed = await deliveryHost().push(ticketId, { coAuthors: args.coAuthors });
|
|
9124
9255
|
} catch (error) {
|
|
9256
|
+
if (error instanceof WorkspaceBlocked) return {
|
|
9257
|
+
published: false,
|
|
9258
|
+
repository,
|
|
9259
|
+
reason: error.message,
|
|
9260
|
+
next: "Report blocked with the reason; a person must create the branch or fix the repository config"
|
|
9261
|
+
};
|
|
9125
9262
|
throw error instanceof RepositoryAccessError ? accessFailure(repository, error) : error;
|
|
9126
9263
|
}
|
|
9127
9264
|
let pr;
|
|
@@ -10736,7 +10873,7 @@ var Runtime = class {
|
|
|
10736
10873
|
});
|
|
10737
10874
|
};
|
|
10738
10875
|
const deps = {
|
|
10739
|
-
prepare: (ticket) => this.workspaces.prepare(ticket, worktreeName(ticket)),
|
|
10876
|
+
prepare: (ticket) => this.workspaces.prepare(ticket, worktreeName(ticket), branchesOf(this.current, repositoryOf(ticket))),
|
|
10740
10877
|
instructions: async (ticket) => this.prompts.instructions("worker", [{
|
|
10741
10878
|
source: "ticket-title",
|
|
10742
10879
|
text: ticket.title
|
|
@@ -11174,6 +11311,8 @@ var Runtime = class {
|
|
|
11174
11311
|
const effective = {
|
|
11175
11312
|
...this.current,
|
|
11176
11313
|
repositories: next.repositories,
|
|
11314
|
+
branches: next.branches,
|
|
11315
|
+
review: next.review,
|
|
11177
11316
|
policy: next.policy,
|
|
11178
11317
|
network: next.network,
|
|
11179
11318
|
retention: next.retention,
|