@coreplane/switchboard 1.264.0 → 1.265.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/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/publicationPush.ts +173 -0
- package/dist/assets/src/core/runEvents.ts +11 -0
- package/dist/assets/src/core/ship/coordinator.ts +3 -1
- package/dist/cli.js +320 -63
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.265.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.265.1",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -21060,7 +21060,7 @@
|
|
|
21060
21060
|
},
|
|
21061
21061
|
"packages/switchboard": {
|
|
21062
21062
|
"name": "@coreplane/switchboard",
|
|
21063
|
-
"version": "1.
|
|
21063
|
+
"version": "1.265.1",
|
|
21064
21064
|
"license": "Apache-2.0",
|
|
21065
21065
|
"dependencies": {
|
|
21066
21066
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.265.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ExistingPrPublicationBinding } from "./coordinator/contract.js";
|
|
2
|
+
import { COMMAND_CAP, TOOL_OUTPUT_CAP, type RunEvent } from "./runEvents.js";
|
|
3
|
+
|
|
4
|
+
export interface PublicationPushReceipt {
|
|
5
|
+
callId: string;
|
|
6
|
+
previousHeadSha: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
pr: number;
|
|
9
|
+
owner: ExistingPrPublicationBinding["owner"];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type PushEvent = Extract<RunEvent, { type: "pushed_head" }>;
|
|
13
|
+
const fullSha = (s: string): boolean => /^[a-f0-9]{40}$/.test(s);
|
|
14
|
+
const refName = (s: string): boolean =>
|
|
15
|
+
/^[a-zA-Z0-9_/-]+(?:\.[a-zA-Z0-9_/-]+)*$/.test(s) && !s.includes("..") && !s.startsWith("-");
|
|
16
|
+
const shortRef = (s: string): string => s.replace(/^refs\/heads\//, "");
|
|
17
|
+
|
|
18
|
+
/** Deliberately not a shell interpreter. Only a standalone literal push can
|
|
19
|
+
* attest its result: pipelines, scripts, substitutions and compound commands
|
|
20
|
+
* cannot prove which operation produced stdout or the successful exit. */
|
|
21
|
+
export function leasedPushCommand(command: string): { ref: string; expectedHeadSha: string } | undefined {
|
|
22
|
+
if (command.length >= COMMAND_CAP) return;
|
|
23
|
+
const literal = command.replace(/[ \t]+2>&1[ \t]*$/, "");
|
|
24
|
+
if (!/^git push\s/.test(literal) || /[\n\r'"`$;|<>\\&]/.test(literal)) return;
|
|
25
|
+
const words = literal.trim().split(/\s+/).slice(2);
|
|
26
|
+
const leases = words.filter((w) => w.startsWith("--force-with-lease="));
|
|
27
|
+
if (leases.length !== 1) return;
|
|
28
|
+
const lease = /^--force-with-lease=refs\/heads\/([^:]+):([a-f0-9]{40})$/.exec(leases[0]!);
|
|
29
|
+
if (!lease || !refName(lease[1]!)) return;
|
|
30
|
+
const args = words.filter((w) => w !== leases[0] && w !== "-u" && w !== "--set-upstream");
|
|
31
|
+
if (args.length !== 2 || args[0] !== "origin") return;
|
|
32
|
+
const refs = args[1]!.split(":");
|
|
33
|
+
if (refs.length > 2 || shortRef(refs[0]!) !== lease[1] || shortRef(refs[1] ?? refs[0]!) !== lease[1]) return;
|
|
34
|
+
return { ref: lease[1]!, expectedHeadSha: lease[2]! };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A legacy record needs the complete pair, never its display summary, an
|
|
38
|
+
* assistant's prose or the remote head alone. More than one attempted push is
|
|
39
|
+
* intentionally ambiguous; historical reconciliation does not choose one. */
|
|
40
|
+
export function pairedPublicationPush(
|
|
41
|
+
events: readonly RunEvent[],
|
|
42
|
+
binding: ExistingPrPublicationBinding,
|
|
43
|
+
head: string,
|
|
44
|
+
callId?: string,
|
|
45
|
+
): PushEvent | undefined {
|
|
46
|
+
if (!fullSha(head) || !fullSha(binding.expectedHeadSha) || head === binding.expectedHeadSha) return;
|
|
47
|
+
const calls = events.filter(
|
|
48
|
+
(e): e is Extract<RunEvent, { type: "tool_call" }> =>
|
|
49
|
+
e.type === "tool_call" &&
|
|
50
|
+
e.tool === "bash" &&
|
|
51
|
+
(callId === undefined ? /\bgit\s+push\b/.test(e.command ?? e.summary) : e.callId === callId),
|
|
52
|
+
);
|
|
53
|
+
if (calls.length !== 1) return;
|
|
54
|
+
const call = calls[0]!;
|
|
55
|
+
if (
|
|
56
|
+
!call.callId ||
|
|
57
|
+
!call.command ||
|
|
58
|
+
events.filter((e) => e.type === "tool_call" && e.callId === call.callId).length !== 1
|
|
59
|
+
)
|
|
60
|
+
return;
|
|
61
|
+
const command = leasedPushCommand(call.command);
|
|
62
|
+
if (command?.ref !== binding.publicationRef || command.expectedHeadSha !== binding.expectedHeadSha) return;
|
|
63
|
+
const results = events.filter(
|
|
64
|
+
(e): e is Extract<RunEvent, { type: "tool_result" }> => e.type === "tool_result" && e.callId === call.callId,
|
|
65
|
+
);
|
|
66
|
+
if (results.length !== 1) return;
|
|
67
|
+
const result = results[0]!;
|
|
68
|
+
if (
|
|
69
|
+
events.indexOf(result) <= events.indexOf(call) ||
|
|
70
|
+
result.tool !== "bash" ||
|
|
71
|
+
!result.ok ||
|
|
72
|
+
result.exitCode !== 0 ||
|
|
73
|
+
result.cut ||
|
|
74
|
+
!result.output ||
|
|
75
|
+
result.output.length >= TOOL_OUTPUT_CAP
|
|
76
|
+
)
|
|
77
|
+
return;
|
|
78
|
+
let lines = result.output.trim().split(/\r?\n/);
|
|
79
|
+
const tracking = `branch '${binding.publicationRef}' set up to track 'origin/${binding.publicationRef}'.`;
|
|
80
|
+
if (
|
|
81
|
+
/(?:^|\s)(?:-u|--set-upstream)(?:\s|$)/.test(call.command) &&
|
|
82
|
+
lines.filter((line) => line === tracking).length === 1
|
|
83
|
+
)
|
|
84
|
+
lines = lines.filter((line) => line !== tracking);
|
|
85
|
+
if (lines.length !== 2) return;
|
|
86
|
+
const remote = /^To (?:https:\/\/github\.com\/|git@github\.com:|github\.com:)([^\s]+?)(?:\.git)?$/.exec(lines[0]!);
|
|
87
|
+
const update = /^\s*\+?\s*([a-f0-9]{7,40})(\.{2,3})([a-f0-9]{7,40})\s+(\S+) -> (\S+)( \(forced update\))?$/.exec(
|
|
88
|
+
lines[1]!,
|
|
89
|
+
);
|
|
90
|
+
if (
|
|
91
|
+
!remote ||
|
|
92
|
+
remote[1]!.toLowerCase() !== binding.repo.toLowerCase() ||
|
|
93
|
+
!update ||
|
|
94
|
+
!binding.expectedHeadSha.startsWith(update[1]!) ||
|
|
95
|
+
!head.startsWith(update[3]!) ||
|
|
96
|
+
update[1] === update[3] ||
|
|
97
|
+
shortRef(update[4]!) !== binding.publicationRef ||
|
|
98
|
+
shortRef(update[5]!) !== binding.publicationRef
|
|
99
|
+
)
|
|
100
|
+
return;
|
|
101
|
+
return {
|
|
102
|
+
type: "pushed_head",
|
|
103
|
+
ref: binding.publicationRef,
|
|
104
|
+
sha: head,
|
|
105
|
+
by: "push",
|
|
106
|
+
receipt: {
|
|
107
|
+
callId: call.callId,
|
|
108
|
+
previousHeadSha: binding.expectedHeadSha,
|
|
109
|
+
repo: binding.repo,
|
|
110
|
+
pr: binding.pr,
|
|
111
|
+
owner: { ...binding.owner },
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** State is stored JSON, not an unchecked cast back into publication authority. */
|
|
117
|
+
export function publicationReceiptsFromState(value: unknown): PushEvent[] {
|
|
118
|
+
if (!Array.isArray(value)) return [];
|
|
119
|
+
const valid = value.every((v) => {
|
|
120
|
+
if (typeof v !== "object" || v === null) return false;
|
|
121
|
+
const r = v.receipt;
|
|
122
|
+
return (
|
|
123
|
+
v.type === "pushed_head" &&
|
|
124
|
+
v.by === "push" &&
|
|
125
|
+
typeof v.ref === "string" &&
|
|
126
|
+
refName(v.ref) &&
|
|
127
|
+
typeof v.sha === "string" &&
|
|
128
|
+
fullSha(v.sha) &&
|
|
129
|
+
typeof r === "object" &&
|
|
130
|
+
r !== null &&
|
|
131
|
+
typeof r.callId === "string" &&
|
|
132
|
+
r.callId.length > 0 &&
|
|
133
|
+
typeof r.previousHeadSha === "string" &&
|
|
134
|
+
fullSha(r.previousHeadSha) &&
|
|
135
|
+
typeof r.repo === "string" &&
|
|
136
|
+
Number.isSafeInteger(r.pr) &&
|
|
137
|
+
r.pr > 0 &&
|
|
138
|
+
typeof r.owner === "object" &&
|
|
139
|
+
r.owner !== null &&
|
|
140
|
+
typeof r.owner.instanceId === "string" &&
|
|
141
|
+
typeof r.owner.unit === "string"
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
return valid ? ([...value] as PushEvent[]) : [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Only the run's typed, contiguous leased transitions can move its restart
|
|
148
|
+
* fence. The caller still verifies the fresh PR and attached workspace. */
|
|
149
|
+
export function restoredPublicationHead(
|
|
150
|
+
events: readonly RunEvent[],
|
|
151
|
+
binding: ExistingPrPublicationBinding,
|
|
152
|
+
): string | undefined {
|
|
153
|
+
let head = binding.expectedHeadSha;
|
|
154
|
+
const seen = new Set<string>();
|
|
155
|
+
for (const e of events) {
|
|
156
|
+
if (e.type !== "pushed_head" || e.receipt === undefined) continue;
|
|
157
|
+
const r = e.receipt;
|
|
158
|
+
if (
|
|
159
|
+
e.ref !== binding.publicationRef ||
|
|
160
|
+
r.repo !== binding.repo ||
|
|
161
|
+
r.pr !== binding.pr ||
|
|
162
|
+
r.owner.instanceId !== binding.owner.instanceId ||
|
|
163
|
+
r.owner.unit !== binding.owner.unit ||
|
|
164
|
+
r.previousHeadSha !== head ||
|
|
165
|
+
!fullSha(e.sha) ||
|
|
166
|
+
seen.has(r.callId)
|
|
167
|
+
)
|
|
168
|
+
return;
|
|
169
|
+
seen.add(r.callId);
|
|
170
|
+
head = e.sha;
|
|
171
|
+
}
|
|
172
|
+
return head === binding.expectedHeadSha ? undefined : head;
|
|
173
|
+
}
|
|
@@ -944,12 +944,23 @@ export type RunEvent =
|
|
|
944
944
|
ref: string;
|
|
945
945
|
sha: string;
|
|
946
946
|
by: "push" | "salvage";
|
|
947
|
+
/** Exact leased tool result, retained before any post-step can refuse. */
|
|
948
|
+
receipt?: import("./publicationPush.js").PublicationPushReceipt;
|
|
947
949
|
/** No uncommitted or unpushed work at the push (record 0064): the fact
|
|
948
950
|
* the plane's soft stop reads. Absent where the measure was missing. */
|
|
949
951
|
clean?: boolean;
|
|
950
952
|
seq?: number;
|
|
951
953
|
at?: number;
|
|
952
954
|
}
|
|
955
|
+
/** The server gate admitted this exact leased push, not a child's claim. */
|
|
956
|
+
| {
|
|
957
|
+
type: "publication_push_authorized";
|
|
958
|
+
callId: string;
|
|
959
|
+
ref: string;
|
|
960
|
+
expectedHeadSha: string;
|
|
961
|
+
seq?: number;
|
|
962
|
+
at?: number;
|
|
963
|
+
}
|
|
953
964
|
/** The coordinator tag as a fact of the run (docs/reference/specs/run-history.md
|
|
954
965
|
* item 48a): the instance the run is a child of, the unit its idempotency
|
|
955
966
|
* key named, and the base branch its pull request targets — published by
|
|
@@ -1998,10 +1998,12 @@ function settleCoding(
|
|
|
1998
1998
|
// final observed head. Only then may a fresh remote read decide readiness.
|
|
1999
1999
|
if (round.kind === "findings" && facts.status === "completed") {
|
|
2000
2000
|
const issued = next.findingsByRound[round.index] ?? [];
|
|
2001
|
-
const matched = matchDispositions(issued, facts.dispositions ?? [])
|
|
2001
|
+
const { matched, dropped } = matchDispositions(issued, facts.dispositions ?? []);
|
|
2002
2002
|
const missingOutputs = issued
|
|
2003
2003
|
.filter((finding) => !matched.some((disposition) => disposition.findingId === finding.id))
|
|
2004
2004
|
.map((finding) => `missing disposition for ${finding.id}`);
|
|
2005
|
+
if (missingOutputs.length > 0 && dropped.length > 0)
|
|
2006
|
+
missingOutputs.push(`submitted IDs with no matching finding: ${dropped.join(", ")}`);
|
|
2005
2007
|
if (facts.description !== true) missingOutputs.push("missing updated pull request description");
|
|
2006
2008
|
const observedHead = fullHead(facts.headSha);
|
|
2007
2009
|
if (observedHead === undefined) missingOutputs.push("missing final observed commit");
|
package/dist/cli.js
CHANGED
|
@@ -36249,6 +36249,8 @@ function activityLine(e) {
|
|
|
36249
36249
|
case "pr_opened":
|
|
36250
36250
|
return "PR opened";
|
|
36251
36251
|
// published straight to the registry — never arrives here
|
|
36252
|
+
case "publication_push_authorized":
|
|
36253
|
+
return "push authorized";
|
|
36252
36254
|
case "pushed_head":
|
|
36253
36255
|
return `\u2B06 pushed ${e.ref} @ ${e.sha.slice(0, 7)}`;
|
|
36254
36256
|
case "coordinator_tag":
|
|
@@ -36722,13 +36724,16 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
36722
36724
|
rejected = void 0;
|
|
36723
36725
|
unverified = void 0;
|
|
36724
36726
|
}
|
|
36727
|
+
const bareNumberText = !s.pr && repo === records?.pr?.repo ? /\b(?:PR|pull request)\s*#(\d+)\b/i.exec(unwrapSlack(msg.text).replace(CODE_SPAN, " "))?.[1] : void 0;
|
|
36728
|
+
const bareNumber = bareNumberText === void 0 ? void 0 : Number(bareNumberText);
|
|
36729
|
+
const namedPr = s.pr ?? (repo && bareNumber !== void 0 && Number.isSafeInteger(bareNumber) && bareNumber > 0 ? { repo, number: bareNumber } : void 0);
|
|
36725
36730
|
let headSha;
|
|
36726
36731
|
let baseRef;
|
|
36727
36732
|
let prSize;
|
|
36728
36733
|
let facts2;
|
|
36729
36734
|
let closedPr;
|
|
36730
|
-
if (
|
|
36731
|
-
const head = await prHead(
|
|
36735
|
+
if (namedPr && repo === namedPr.repo) {
|
|
36736
|
+
const head = await prHead(namedPr).catch(() => void 0);
|
|
36732
36737
|
if (head?.ref && head.state === "open") {
|
|
36733
36738
|
ref = head.ref;
|
|
36734
36739
|
refFromPr = true;
|
|
@@ -36739,7 +36744,7 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
36739
36744
|
facts2 = head?.facts;
|
|
36740
36745
|
if (head?.state === "closed")
|
|
36741
36746
|
closedPr = {
|
|
36742
|
-
number:
|
|
36747
|
+
number: namedPr.number,
|
|
36743
36748
|
merged: head.merged,
|
|
36744
36749
|
...head.mergedAt !== void 0 ? { mergedAt: head.mergedAt } : {}
|
|
36745
36750
|
};
|
|
@@ -36750,16 +36755,16 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
|
|
|
36750
36755
|
else if (rejected) out.rejectedRepo = rejected;
|
|
36751
36756
|
if (ref) out.ref = ref;
|
|
36752
36757
|
if (refFromPr) out.refFromPr = true;
|
|
36753
|
-
if (repo &&
|
|
36754
|
-
out.pr =
|
|
36758
|
+
if (repo && namedPr && repo === namedPr.repo) {
|
|
36759
|
+
out.pr = namedPr.number;
|
|
36755
36760
|
out.prFromMessage = true;
|
|
36756
|
-
if (isThreadOwnPr(records, repo,
|
|
36761
|
+
if (isThreadOwnPr(records, repo, namedPr.number)) out.prIsThreadOwn = true;
|
|
36757
36762
|
if (headSha) out.headSha = headSha;
|
|
36758
36763
|
if (baseRef) out.baseRef = baseRef;
|
|
36759
36764
|
if (prSize) out.prSize = prSize;
|
|
36760
36765
|
if (facts2) out.prDescription = facts2;
|
|
36761
36766
|
if (closedPr) out.closedPr = closedPr;
|
|
36762
|
-
} else if (repo && !
|
|
36767
|
+
} else if (repo && !namedPr) {
|
|
36763
36768
|
const inherited = inheritedPr(thread, records, repo);
|
|
36764
36769
|
if (inherited) {
|
|
36765
36770
|
const head = await openPrHeadSha(inherited);
|
|
@@ -40793,6 +40798,7 @@ async function ownerOf2(runs, unitsOf, threadKey) {
|
|
|
40793
40798
|
if (live !== void 0) return { kind: "live", run: live };
|
|
40794
40799
|
const hosted = runs.find((r) => !r.finished && r.hosted === true);
|
|
40795
40800
|
const instanceId = instanceOf(runs);
|
|
40801
|
+
let releasedPr;
|
|
40796
40802
|
if (instanceId !== void 0) {
|
|
40797
40803
|
const units = await unitsOf(instanceId).catch(() => []);
|
|
40798
40804
|
const unit = units.find((u) => u.threadKey === threadKey && u.ending === void 0);
|
|
@@ -40807,11 +40813,19 @@ async function ownerOf2(runs, unitsOf, threadKey) {
|
|
|
40807
40813
|
if (ended.length === 1 && ship !== void 0) return { kind: "pipeline", instanceId, run: ship, unit: ended[0] };
|
|
40808
40814
|
if (ended.length > 1 && ship !== void 0)
|
|
40809
40815
|
return { kind: "pipeline_ambiguous", instanceId, run: ship, units: ended };
|
|
40816
|
+
const released = units.filter((u) => {
|
|
40817
|
+
const binding = u.publication;
|
|
40818
|
+
return u.threadKey === threadKey && u.instanceId === instanceId && u.ending?.kind === "merge_ready" && binding !== void 0 && u.pr?.number === binding.pr && u.branch === binding.headRef && u.branch === binding.publicationRef && binding.owner.instanceId === instanceId && binding.owner.unit === u.unit && (u.lastPush === void 0 || u.lastPush === binding.expectedHeadSha);
|
|
40819
|
+
});
|
|
40820
|
+
if (released.length === 1) {
|
|
40821
|
+
const unit2 = released[0];
|
|
40822
|
+
releasedPr = { repo: unit2.publication.repo, number: unit2.publication.pr, at: unit2.ending.at };
|
|
40823
|
+
}
|
|
40810
40824
|
}
|
|
40811
40825
|
if (hosted !== void 0) return { kind: "live", run: hosted };
|
|
40812
40826
|
const agent = stickyAgentOf(runs);
|
|
40813
|
-
if (agent !== void 0) return { kind: "session", agent };
|
|
40814
|
-
return { kind: "none" };
|
|
40827
|
+
if (agent !== void 0) return { kind: "session", agent, ...releasedPr ? { releasedPr } : {} };
|
|
40828
|
+
return { kind: "none", ...releasedPr ? { releasedPr } : {} };
|
|
40815
40829
|
}
|
|
40816
40830
|
async function shipRequestOf(service, runId3) {
|
|
40817
40831
|
const result = await service.getRun(runId3, { include: "messages" }).catch(() => void 0);
|
|
@@ -46062,7 +46076,7 @@ var init_submit = __esm({
|
|
|
46062
46076
|
submitDispositionsTool = {
|
|
46063
46077
|
name: "submit_dispositions",
|
|
46064
46078
|
failsInText: true,
|
|
46065
|
-
description: "Record one disposition per review finding after addressing them: `fixed` (the finding is addressed in your pushed code) or `declined` (deliberately not doing it \u2014 the note says why). `findingId` is the finding's stable id
|
|
46079
|
+
description: "Record one disposition per review finding after addressing them: `fixed` (the finding is addressed in your pushed code) or `declined` (deliberately not doing it \u2014 the note says why). `findingId` is the finding's stable id printed in the findings. Copy it exactly, including check ids such as `check:CI / checks (test)`; do not replace a check id with `F1`. An id the review never issued answers nothing and is dropped when the plan runner reads your record. Every finding gets exactly one entry, every severity included (nits too). Call it once with the complete set after your last push; a later call replaces the earlier one. The set rides this run's record, where the plan runner reads it for the re-review.",
|
|
46066
46080
|
inputSchema: {
|
|
46067
46081
|
type: "object",
|
|
46068
46082
|
properties: {
|
|
@@ -46072,7 +46086,10 @@ var init_submit = __esm({
|
|
|
46072
46086
|
items: {
|
|
46073
46087
|
type: "object",
|
|
46074
46088
|
properties: {
|
|
46075
|
-
findingId: {
|
|
46089
|
+
findingId: {
|
|
46090
|
+
type: "string",
|
|
46091
|
+
description: 'Copy the exact id in the findings (e.g. "F1" or "check:CI / checks (test)")'
|
|
46092
|
+
},
|
|
46076
46093
|
disposition: { type: "string", enum: ["fixed", "declined"], description: "fixed | declined" },
|
|
46077
46094
|
note: { type: "string", description: "One line: what was done, or why it was declined" }
|
|
46078
46095
|
},
|
|
@@ -46088,7 +46105,7 @@ var init_submit = __esm({
|
|
|
46088
46105
|
if (!ctx.onDispositions) return "no run is recording dispositions here";
|
|
46089
46106
|
ctx.onDispositions(parsed.dispositions);
|
|
46090
46107
|
const drops = parsed.dropped.length ? ` (dropped: ${parsed.dropped.join("; ")})` : "";
|
|
46091
|
-
return `dispositions recorded: ${parsed.dispositions.length}${drops}; a later call replaces this one`;
|
|
46108
|
+
return `dispositions recorded: ${parsed.dispositions.length}${drops}; IDs are checked against the findings by the plan runner; a later call replaces this one`;
|
|
46092
46109
|
}
|
|
46093
46110
|
};
|
|
46094
46111
|
submitPrDescriptionTool = {
|
|
@@ -50246,6 +50263,11 @@ function reachFor(identity2) {
|
|
|
50246
50263
|
if (identity2 === "read") return READ_REACH;
|
|
50247
50264
|
return NONE_REACH;
|
|
50248
50265
|
}
|
|
50266
|
+
function publicationAttributionRefusal(rules, callId) {
|
|
50267
|
+
const pending2 = rules.publication?.attributingCallId;
|
|
50268
|
+
if (pending2 !== void 0 && pending2 !== callId)
|
|
50269
|
+
return "push attribution is still pending; no later tool may change the checkout until its result is recorded";
|
|
50270
|
+
}
|
|
50249
50271
|
function openCodeToolWord(action) {
|
|
50250
50272
|
const mapped = OPENCODE_ACTION_TO_TOOL_WORD[action];
|
|
50251
50273
|
if (mapped !== void 0) return mapped;
|
|
@@ -50278,19 +50300,36 @@ function judgeBashCommand(command2, ctx) {
|
|
|
50278
50300
|
if (MERGE_OR_APPROVE.test(command2)) {
|
|
50279
50301
|
return refused4("merge/approve \u2014 a coding run never merges or approves a pull request");
|
|
50280
50302
|
}
|
|
50303
|
+
const pushCommand = withoutShellContinuations(command2);
|
|
50281
50304
|
if (ctx.identity !== "write") {
|
|
50282
|
-
if (ANY_PUSH.test(
|
|
50305
|
+
if (ANY_PUSH.test(pushCommand)) return refused4("read-only \u2014 a read-identity run never pushes");
|
|
50283
50306
|
if (GH_WRITE_VERB.test(command2) || GH_API_WRITE.test(command2) || CURL_GITHUB_WRITE.test(command2)) {
|
|
50284
50307
|
return refused4("read-only \u2014 a read-identity run never writes to GitHub");
|
|
50285
50308
|
}
|
|
50286
50309
|
return allowed;
|
|
50287
50310
|
}
|
|
50288
|
-
for (const match of
|
|
50311
|
+
for (const match of pushCommand.matchAll(GIT_PUSH)) {
|
|
50289
50312
|
const verdict = judgePush(match[1], ctx);
|
|
50290
50313
|
if (verdict.verdict !== "allowed") return verdict;
|
|
50291
50314
|
}
|
|
50292
50315
|
return allowed;
|
|
50293
50316
|
}
|
|
50317
|
+
function withoutShellContinuations(command2) {
|
|
50318
|
+
let result = "";
|
|
50319
|
+
let quote3;
|
|
50320
|
+
for (let i = 0; i < command2.length; i++) {
|
|
50321
|
+
const char = command2[i];
|
|
50322
|
+
if (char === "\\" && quote3 !== "'" && i + 1 < command2.length) {
|
|
50323
|
+
const next = command2[++i];
|
|
50324
|
+
if (next !== "\n") result += char + next;
|
|
50325
|
+
continue;
|
|
50326
|
+
}
|
|
50327
|
+
if (char === quote3) quote3 = void 0;
|
|
50328
|
+
else if (quote3 === void 0 && (char === "'" || char === '"')) quote3 = char;
|
|
50329
|
+
result += char;
|
|
50330
|
+
}
|
|
50331
|
+
return result;
|
|
50332
|
+
}
|
|
50294
50333
|
function judgeBashTimeout(timeout, ctx) {
|
|
50295
50334
|
if (!ctx.loopEndsIn || typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) return allowed;
|
|
50296
50335
|
const leftMs = ctx.loopEndsIn();
|
|
@@ -50401,8 +50440,11 @@ var init_toolRules = __esm({
|
|
|
50401
50440
|
ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
|
|
50402
50441
|
CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
|
|
50403
50442
|
MERGE_OR_APPROVE = /\bgh\s+pr\s+(merge|review)\b|\/pulls\/\d+\/(merge|reviews)\b/;
|
|
50404
|
-
GIT_THEN_PUSH = String.raw`\bgit(
|
|
50405
|
-
GIT_PUSH = new RegExp(
|
|
50443
|
+
GIT_THEN_PUSH = String.raw`\bgit(?:[ \t]+(?:-C[ \t]+\S+|--git-dir=\S+|--work-tree=\S+|-c[ \t]+\S+|--no-pager))*[ \t]+push\b`;
|
|
50444
|
+
GIT_PUSH = new RegExp(
|
|
50445
|
+
GIT_THEN_PUSH + String.raw`((?:"(?:\\[\s\S]|[^"\\])*"|'[^']*'|\\[^\n]|[^\n;&|'"\\]|(?<=[<>])&|&(?=>))*)`,
|
|
50446
|
+
"g"
|
|
50447
|
+
);
|
|
50406
50448
|
ANY_PUSH = new RegExp(GIT_THEN_PUSH);
|
|
50407
50449
|
GH_WRITE_VERB = /\bgh\s+(pr|issue)\s+(comment|create|edit|close|reopen|ready|lock|unlock|review|merge|delete|transfer|pin|unpin)\b/;
|
|
50408
50450
|
WRITE_METHOD = "(POST|post|PATCH|patch|PUT|put|DELETE|delete)";
|
|
@@ -50429,6 +50471,98 @@ var init_toolRules = __esm({
|
|
|
50429
50471
|
}
|
|
50430
50472
|
});
|
|
50431
50473
|
|
|
50474
|
+
// ../../src/core/publicationPush.ts
|
|
50475
|
+
function leasedPushCommand(command2) {
|
|
50476
|
+
if (command2.length >= COMMAND_CAP) return;
|
|
50477
|
+
const literal = command2.replace(/[ \t]+2>&1[ \t]*$/, "");
|
|
50478
|
+
if (!/^git push\s/.test(literal) || /[\n\r'"`$;|<>\\&]/.test(literal)) return;
|
|
50479
|
+
const words = literal.trim().split(/\s+/).slice(2);
|
|
50480
|
+
const leases = words.filter((w) => w.startsWith("--force-with-lease="));
|
|
50481
|
+
if (leases.length !== 1) return;
|
|
50482
|
+
const lease = /^--force-with-lease=refs\/heads\/([^:]+):([a-f0-9]{40})$/.exec(leases[0]);
|
|
50483
|
+
if (!lease || !refName(lease[1])) return;
|
|
50484
|
+
const args = words.filter((w) => w !== leases[0] && w !== "-u" && w !== "--set-upstream");
|
|
50485
|
+
if (args.length !== 2 || args[0] !== "origin") return;
|
|
50486
|
+
const refs = args[1].split(":");
|
|
50487
|
+
if (refs.length > 2 || shortRef(refs[0]) !== lease[1] || shortRef(refs[1] ?? refs[0]) !== lease[1]) return;
|
|
50488
|
+
return { ref: lease[1], expectedHeadSha: lease[2] };
|
|
50489
|
+
}
|
|
50490
|
+
function pairedPublicationPush(events, binding, head, callId) {
|
|
50491
|
+
if (!fullSha(head) || !fullSha(binding.expectedHeadSha) || head === binding.expectedHeadSha) return;
|
|
50492
|
+
const calls = events.filter(
|
|
50493
|
+
(e) => e.type === "tool_call" && e.tool === "bash" && (callId === void 0 ? /\bgit\s+push\b/.test(e.command ?? e.summary) : e.callId === callId)
|
|
50494
|
+
);
|
|
50495
|
+
if (calls.length !== 1) return;
|
|
50496
|
+
const call2 = calls[0];
|
|
50497
|
+
if (!call2.callId || !call2.command || events.filter((e) => e.type === "tool_call" && e.callId === call2.callId).length !== 1)
|
|
50498
|
+
return;
|
|
50499
|
+
const command2 = leasedPushCommand(call2.command);
|
|
50500
|
+
if (command2?.ref !== binding.publicationRef || command2.expectedHeadSha !== binding.expectedHeadSha) return;
|
|
50501
|
+
const results = events.filter(
|
|
50502
|
+
(e) => e.type === "tool_result" && e.callId === call2.callId
|
|
50503
|
+
);
|
|
50504
|
+
if (results.length !== 1) return;
|
|
50505
|
+
const result = results[0];
|
|
50506
|
+
if (events.indexOf(result) <= events.indexOf(call2) || result.tool !== "bash" || !result.ok || result.exitCode !== 0 || result.cut || !result.output || result.output.length >= TOOL_OUTPUT_CAP)
|
|
50507
|
+
return;
|
|
50508
|
+
let lines = result.output.trim().split(/\r?\n/);
|
|
50509
|
+
const tracking = `branch '${binding.publicationRef}' set up to track 'origin/${binding.publicationRef}'.`;
|
|
50510
|
+
if (/(?:^|\s)(?:-u|--set-upstream)(?:\s|$)/.test(call2.command) && lines.filter((line4) => line4 === tracking).length === 1)
|
|
50511
|
+
lines = lines.filter((line4) => line4 !== tracking);
|
|
50512
|
+
if (lines.length !== 2) return;
|
|
50513
|
+
const remote = /^To (?:https:\/\/github\.com\/|git@github\.com:|github\.com:)([^\s]+?)(?:\.git)?$/.exec(lines[0]);
|
|
50514
|
+
const update = /^\s*\+?\s*([a-f0-9]{7,40})(\.{2,3})([a-f0-9]{7,40})\s+(\S+) -> (\S+)( \(forced update\))?$/.exec(
|
|
50515
|
+
lines[1]
|
|
50516
|
+
);
|
|
50517
|
+
if (!remote || remote[1].toLowerCase() !== binding.repo.toLowerCase() || !update || !binding.expectedHeadSha.startsWith(update[1]) || !head.startsWith(update[3]) || update[1] === update[3] || shortRef(update[4]) !== binding.publicationRef || shortRef(update[5]) !== binding.publicationRef)
|
|
50518
|
+
return;
|
|
50519
|
+
return {
|
|
50520
|
+
type: "pushed_head",
|
|
50521
|
+
ref: binding.publicationRef,
|
|
50522
|
+
sha: head,
|
|
50523
|
+
by: "push",
|
|
50524
|
+
receipt: {
|
|
50525
|
+
callId: call2.callId,
|
|
50526
|
+
previousHeadSha: binding.expectedHeadSha,
|
|
50527
|
+
repo: binding.repo,
|
|
50528
|
+
pr: binding.pr,
|
|
50529
|
+
owner: { ...binding.owner }
|
|
50530
|
+
}
|
|
50531
|
+
};
|
|
50532
|
+
}
|
|
50533
|
+
function publicationReceiptsFromState(value) {
|
|
50534
|
+
if (!Array.isArray(value)) return [];
|
|
50535
|
+
const valid = value.every((v) => {
|
|
50536
|
+
if (typeof v !== "object" || v === null) return false;
|
|
50537
|
+
const r = v.receipt;
|
|
50538
|
+
return v.type === "pushed_head" && v.by === "push" && typeof v.ref === "string" && refName(v.ref) && typeof v.sha === "string" && fullSha(v.sha) && typeof r === "object" && r !== null && typeof r.callId === "string" && r.callId.length > 0 && typeof r.previousHeadSha === "string" && fullSha(r.previousHeadSha) && typeof r.repo === "string" && Number.isSafeInteger(r.pr) && r.pr > 0 && typeof r.owner === "object" && r.owner !== null && typeof r.owner.instanceId === "string" && typeof r.owner.unit === "string";
|
|
50539
|
+
});
|
|
50540
|
+
return valid ? [...value] : [];
|
|
50541
|
+
}
|
|
50542
|
+
function restoredPublicationHead(events, binding) {
|
|
50543
|
+
let head = binding.expectedHeadSha;
|
|
50544
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50545
|
+
for (const e of events) {
|
|
50546
|
+
if (e.type !== "pushed_head" || e.receipt === void 0) continue;
|
|
50547
|
+
const r = e.receipt;
|
|
50548
|
+
if (e.ref !== binding.publicationRef || r.repo !== binding.repo || r.pr !== binding.pr || r.owner.instanceId !== binding.owner.instanceId || r.owner.unit !== binding.owner.unit || r.previousHeadSha !== head || !fullSha(e.sha) || seen.has(r.callId))
|
|
50549
|
+
return;
|
|
50550
|
+
seen.add(r.callId);
|
|
50551
|
+
head = e.sha;
|
|
50552
|
+
}
|
|
50553
|
+
return head === binding.expectedHeadSha ? void 0 : head;
|
|
50554
|
+
}
|
|
50555
|
+
var fullSha, refName, shortRef;
|
|
50556
|
+
var init_publicationPush = __esm({
|
|
50557
|
+
"../../src/core/publicationPush.ts"() {
|
|
50558
|
+
"use strict";
|
|
50559
|
+
init_runEvents();
|
|
50560
|
+
fullSha = (s) => /^[a-f0-9]{40}$/.test(s);
|
|
50561
|
+
refName = (s) => /^[a-zA-Z0-9_/-]+(?:\.[a-zA-Z0-9_/-]+)*$/.test(s) && !s.includes("..") && !s.startsWith("-");
|
|
50562
|
+
shortRef = (s) => s.replace(/^refs\/heads\//, "");
|
|
50563
|
+
}
|
|
50564
|
+
});
|
|
50565
|
+
|
|
50432
50566
|
// ../../src/core/harness/pi/relay.ts
|
|
50433
50567
|
function stillRunningNote(tool) {
|
|
50434
50568
|
return `This ${tool} call is still running in the bot: the process that made it was relaunched while the call was in flight, and the call keeps running there rather than being re-run \u2014 do not run it again; its result is kept on the relay under the same call id.`;
|
|
@@ -50459,9 +50593,18 @@ function authorizeToolCall(harness, ask) {
|
|
|
50459
50593
|
return { allow: false, reason: reason2 };
|
|
50460
50594
|
};
|
|
50461
50595
|
if (blocked2 !== void 0) return refuse3(blocked2);
|
|
50596
|
+
const pending2 = publicationAttributionRefusal(harness.rules, ask.toolCallId);
|
|
50597
|
+
if (pending2 !== void 0) return refuse3(pending2);
|
|
50462
50598
|
if (harness.tools.some((t) => t.name === ask.tool)) return { allow: true };
|
|
50463
50599
|
const verdict = judgeToolCall(ask.tool, ask.input, harness.rules);
|
|
50464
|
-
if (verdict.verdict === "allowed")
|
|
50600
|
+
if (verdict.verdict === "allowed") {
|
|
50601
|
+
const authority = harness.rules.publication?.authority;
|
|
50602
|
+
const input = ask.input;
|
|
50603
|
+
const push = ask.tool === "bash" && typeof input?.command === "string" ? leasedPushCommand(input.command) : void 0;
|
|
50604
|
+
if (push !== void 0 && authority !== void 0 && !("blocked" in authority) && push.ref === authority.ref && push.expectedHeadSha === authority.expectedHeadSha)
|
|
50605
|
+
harness.emit({ type: "publication_push_authorized", callId: ask.toolCallId, ...push });
|
|
50606
|
+
return { allow: true };
|
|
50607
|
+
}
|
|
50465
50608
|
return refuse3(verdict.reason);
|
|
50466
50609
|
}
|
|
50467
50610
|
function answerCompaction(harness, ask) {
|
|
@@ -50528,6 +50671,7 @@ var init_relay = __esm({
|
|
|
50528
50671
|
init_runEvents();
|
|
50529
50672
|
init_compactionFallback();
|
|
50530
50673
|
init_toolRules();
|
|
50674
|
+
init_publicationPush();
|
|
50531
50675
|
HarnessRegistry = class {
|
|
50532
50676
|
live = /* @__PURE__ */ new Map();
|
|
50533
50677
|
/** The run drives a pi from here, its relayed calls kept beside it; the
|
|
@@ -52341,19 +52485,19 @@ var init_resume = __esm({
|
|
|
52341
52485
|
// ../../src/core/existingPrPublication.ts
|
|
52342
52486
|
function verifyExistingPrPublication(durable, local, remote) {
|
|
52343
52487
|
if (durable === void 0) return blocked("the durable existing-PR binding is missing");
|
|
52344
|
-
if (!
|
|
52488
|
+
if (!fullSha2(durable.expectedHeadSha)) return blocked("the durable expected head is not a full commit SHA");
|
|
52345
52489
|
if (durable.headRef !== durable.publicationRef)
|
|
52346
52490
|
return blocked("the durable head ref and owned publication ref do not agree");
|
|
52347
52491
|
if (local.repo !== durable.repo) return blocked("the resolved repository does not match the durable binding");
|
|
52348
52492
|
if (local.pr !== durable.pr) return blocked("the resolved pull request does not match the durable binding");
|
|
52349
52493
|
if (local.ref !== durable.headRef) return blocked("the resolved head ref does not match the durable binding");
|
|
52350
52494
|
if (local.baseRef !== durable.baseRef) return blocked("the resolved base ref does not match the durable binding");
|
|
52351
|
-
if (!
|
|
52495
|
+
if (!fullSha2(local.requestHeadSha)) return blocked("the request head is missing or not a full commit SHA");
|
|
52352
52496
|
if (local.requestHeadSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
|
|
52353
52497
|
return blocked("the request head does not match the durable expected commit");
|
|
52354
52498
|
if (local.workspaceRef !== durable.publicationRef)
|
|
52355
52499
|
return blocked("the checked-out publication ref does not match the durable binding");
|
|
52356
|
-
if (!
|
|
52500
|
+
if (!fullSha2(local.workspaceHeadSha)) return blocked("the checked-out head is missing or not a full commit SHA");
|
|
52357
52501
|
if (local.workspaceHeadSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
|
|
52358
52502
|
return blocked("the checked-out head does not match the durable expected commit");
|
|
52359
52503
|
if (!sameOwner(local.owner, durable.owner)) return blocked("the publication owner changed");
|
|
@@ -52363,7 +52507,7 @@ function verifyExistingPrPublication(durable, local, remote) {
|
|
|
52363
52507
|
if (remote.headBranchExists !== true) return blocked("the pull request head branch is missing or unknown");
|
|
52364
52508
|
if (remote.headRef !== durable.headRef) return blocked("the pull request head ref moved");
|
|
52365
52509
|
if (remote.baseRef !== durable.baseRef) return blocked("the pull request base ref moved");
|
|
52366
|
-
if (!
|
|
52510
|
+
if (!fullSha2(remote.headSha)) return blocked("the fresh pull-request head is missing or not a full commit SHA");
|
|
52367
52511
|
if (remote.headSha.toLowerCase() !== durable.expectedHeadSha.toLowerCase())
|
|
52368
52512
|
return blocked("the pull request head moved from the durable expected commit");
|
|
52369
52513
|
return {
|
|
@@ -52371,11 +52515,11 @@ function verifyExistingPrPublication(durable, local, remote) {
|
|
|
52371
52515
|
publication: { ref: durable.publicationRef, expectedHeadSha: durable.expectedHeadSha.toLowerCase() }
|
|
52372
52516
|
};
|
|
52373
52517
|
}
|
|
52374
|
-
var
|
|
52518
|
+
var fullSha2, sameOwner, blocked;
|
|
52375
52519
|
var init_existingPrPublication = __esm({
|
|
52376
52520
|
"../../src/core/existingPrPublication.ts"() {
|
|
52377
52521
|
"use strict";
|
|
52378
|
-
|
|
52522
|
+
fullSha2 = (value) => value !== void 0 && /^[0-9a-f]{40}$/i.test(value);
|
|
52379
52523
|
sameOwner = (left, right) => left?.instanceId === right.instanceId && left.unit === right.unit;
|
|
52380
52524
|
blocked = (reason2) => ({ ok: false, reason: reason2 });
|
|
52381
52525
|
}
|
|
@@ -53402,6 +53546,14 @@ async function runLoop(deps, ctx) {
|
|
|
53402
53546
|
let reviewHead = ctx.reviewHead;
|
|
53403
53547
|
let lastActivityAt = ctx.loopStartedAt;
|
|
53404
53548
|
const restored = resume?.row.state ?? {};
|
|
53549
|
+
const restoredReceipts = publicationReceiptsFromState(restored.publicationReceipts);
|
|
53550
|
+
const publicationReceipts = coordinator?.publication !== void 0 && restoredPublicationHead(restoredReceipts, coordinator.publication) !== void 0 ? restoredReceipts : [];
|
|
53551
|
+
const retainPublicationReceipts = () => {
|
|
53552
|
+
const history = registry.snapshot(run2.id, run2.token)?.events ?? [];
|
|
53553
|
+
const latest = publicationReceipts.at(-1);
|
|
53554
|
+
if (latest !== void 0 && !history.some((e) => e.type === "pushed_head" && e.ref === latest.ref))
|
|
53555
|
+
registry.publish(run2.id, latest);
|
|
53556
|
+
};
|
|
53405
53557
|
let checklist = typeof restored.checklist === "string" ? restored.checklist : void 0;
|
|
53406
53558
|
let lastActivity;
|
|
53407
53559
|
let inFlightTool;
|
|
@@ -53438,6 +53590,23 @@ async function runLoop(deps, ctx) {
|
|
|
53438
53590
|
let budgetEnded = false;
|
|
53439
53591
|
let recordedPushedBranch;
|
|
53440
53592
|
let modelBudgetEndsAt;
|
|
53593
|
+
const receiptAtToolResult = async (e) => {
|
|
53594
|
+
const binding2 = coordinator?.publication;
|
|
53595
|
+
const authority = existingPrPublicationFence?.authority;
|
|
53596
|
+
if (e.type !== "tool_result" || e.tool !== "bash" || !e.ok || e.exitCode !== 0 || e.cut || binding2 === void 0 || authority === void 0 || "blocked" in authority)
|
|
53597
|
+
return;
|
|
53598
|
+
const history = registry.snapshot(run2.id, run2.token)?.events ?? [];
|
|
53599
|
+
const gates = history.filter((event) => event.type === "publication_push_authorized" && event.callId === e.callId);
|
|
53600
|
+
if (gates.length !== 1 || gates[0]?.type !== "publication_push_authorized" || gates[0].ref !== authority.ref || gates[0].expectedHeadSha !== authority.expectedHeadSha)
|
|
53601
|
+
return;
|
|
53602
|
+
try {
|
|
53603
|
+
const head = (await executor.exec(`git rev-parse refs/heads/${authority.ref}`)).trim();
|
|
53604
|
+
const current = { ...binding2, expectedHeadSha: authority.expectedHeadSha };
|
|
53605
|
+
return pairedPublicationPush([...history, e], current, head, e.callId);
|
|
53606
|
+
} catch {
|
|
53607
|
+
return;
|
|
53608
|
+
}
|
|
53609
|
+
};
|
|
53441
53610
|
const publishEvent = (e, startsRunBudget) => {
|
|
53442
53611
|
registry.publish(run2.id, e);
|
|
53443
53612
|
if (isSpanRecord(e)) return;
|
|
@@ -53462,12 +53631,16 @@ async function runLoop(deps, ctx) {
|
|
|
53462
53631
|
card.update(currentFrame());
|
|
53463
53632
|
};
|
|
53464
53633
|
const onEvent = (e) => {
|
|
53634
|
+
if (e.type === "publication_push_authorized" && existingPrPublicationFence !== void 0)
|
|
53635
|
+
existingPrPublicationFence.attributingCallId = e.callId;
|
|
53465
53636
|
const startsRunBudget = e.type === "lease";
|
|
53466
53637
|
if (startsRunBudget) {
|
|
53467
53638
|
modelBudgetEndsAt = e.endsAt;
|
|
53468
53639
|
run2.control.startLease(() => e.endsAt - clock4());
|
|
53469
53640
|
}
|
|
53470
53641
|
void events.write(async () => {
|
|
53642
|
+
const receipt = await receiptAtToolResult(e);
|
|
53643
|
+
let receiptCommitted = false;
|
|
53471
53644
|
if ((e.type === "tool_call" || e.type === "tool_result") && typeof registry.commitLiveState === "function") {
|
|
53472
53645
|
const summary = registry.getById(run2.id);
|
|
53473
53646
|
if (summary?.liveState?.state === "working") {
|
|
@@ -53481,12 +53654,16 @@ async function runLoop(deps, ctx) {
|
|
|
53481
53654
|
state: "working",
|
|
53482
53655
|
bound,
|
|
53483
53656
|
detail: e.type === "tool_call" ? "running a tool" : "model turn",
|
|
53484
|
-
sourceEvents: [{ ...e, seq: sourceSeq }]
|
|
53657
|
+
sourceEvents: [{ ...e, seq: sourceSeq }, ...receipt ? [{ ...receipt, seq: sourceSeq + 1, at }] : []],
|
|
53658
|
+
...receipt ? { statePatch: { publicationReceipts: [...publicationReceipts, receipt] } } : {}
|
|
53485
53659
|
};
|
|
53486
53660
|
try {
|
|
53487
53661
|
if (ledgerRun?.tracked()) {
|
|
53488
53662
|
const committed = await ledgerRun.assignLiveState(assignment);
|
|
53489
|
-
if (committed.ok)
|
|
53663
|
+
if (committed.ok) {
|
|
53664
|
+
registry.commitLiveState(run2.id, committed);
|
|
53665
|
+
receiptCommitted = true;
|
|
53666
|
+
}
|
|
53490
53667
|
} else {
|
|
53491
53668
|
const local = assignRunLiveState(summary.liveState, summary.liveStateSeq ?? 0, assignment);
|
|
53492
53669
|
if (local.ok) registry.commitLiveState(run2.id, { ...local, liveStateSeq: sourceSeq });
|
|
@@ -53500,6 +53677,16 @@ async function runLoop(deps, ctx) {
|
|
|
53500
53677
|
}
|
|
53501
53678
|
}
|
|
53502
53679
|
publishEvent(e, startsRunBudget);
|
|
53680
|
+
if (receipt !== void 0) {
|
|
53681
|
+
if (receiptCommitted) {
|
|
53682
|
+
publicationReceipts.push(receipt);
|
|
53683
|
+
publishEvent(receipt, false);
|
|
53684
|
+
existingPrPublication = { ref: receipt.ref, expectedHeadSha: receipt.sha };
|
|
53685
|
+
if (existingPrPublicationFence !== void 0) existingPrPublicationFence.authority = existingPrPublication;
|
|
53686
|
+
} else blockExistingPrPublication("the successful push receipt could not be committed durably");
|
|
53687
|
+
}
|
|
53688
|
+
if (e.type === "tool_result" && existingPrPublicationFence !== void 0 && existingPrPublicationFence.attributingCallId === e.callId)
|
|
53689
|
+
delete existingPrPublicationFence.attributingCallId;
|
|
53503
53690
|
});
|
|
53504
53691
|
};
|
|
53505
53692
|
for (const s of mcpForRun?.servers ?? []) {
|
|
@@ -53651,6 +53838,8 @@ async function runLoop(deps, ctx) {
|
|
|
53651
53838
|
};
|
|
53652
53839
|
const preserveCodingChildWork = async (cue) => {
|
|
53653
53840
|
if (workSalvageAttempted || !isCodingPrRun || coordinator === void 0) return;
|
|
53841
|
+
await events.drain();
|
|
53842
|
+
retainPublicationReceipts();
|
|
53654
53843
|
if (!workspaceObserved) await observeWorkspaceNow();
|
|
53655
53844
|
const target = coordinatorBranch === void 0 ? { skipped: "the ending checkpoint was skipped: the unit's branch is unavailable" } : salvageTargetOf({
|
|
53656
53845
|
pushedBranch: pushes.branch(),
|
|
@@ -53804,6 +53993,40 @@ async function runLoop(deps, ctx) {
|
|
|
53804
53993
|
};
|
|
53805
53994
|
let workspaceBinding = workspaceBindingFor(round2.selection, profile.machine);
|
|
53806
53995
|
try {
|
|
53996
|
+
const prBase = repoCtx.baseRef ?? await coordinatorBase;
|
|
53997
|
+
if (coordinator?.publication !== void 0) {
|
|
53998
|
+
const original = coordinator.publication;
|
|
53999
|
+
const restoredHead = resume === void 0 ? void 0 : restoredPublicationHead(publicationReceipts, original);
|
|
54000
|
+
const publication = restoredHead === void 0 ? original : { ...original, expectedHeadSha: restoredHead };
|
|
54001
|
+
const fresh = await (deps.fetchPrFacts ?? fetchPullRequestFacts)({
|
|
54002
|
+
repo: publication.repo,
|
|
54003
|
+
number: publication.pr
|
|
54004
|
+
}).catch(() => void 0);
|
|
54005
|
+
const verified = verifyExistingPrPublication(
|
|
54006
|
+
publication,
|
|
54007
|
+
{
|
|
54008
|
+
repo: repoCtx.repo,
|
|
54009
|
+
pr: repoCtx.pr,
|
|
54010
|
+
ref: repoCtx.ref,
|
|
54011
|
+
baseRef: prBase,
|
|
54012
|
+
requestHeadSha: restoredHead ?? repoCtx.headSha,
|
|
54013
|
+
workspaceRef: binding?.ref,
|
|
54014
|
+
workspaceHeadSha: binding?.sha,
|
|
54015
|
+
owner: {
|
|
54016
|
+
instanceId: coordinator.parentInstanceId,
|
|
54017
|
+
unit: unitOfIdempotencyKey(coordinator.idempotencyKey) ?? ""
|
|
54018
|
+
}
|
|
54019
|
+
},
|
|
54020
|
+
fresh
|
|
54021
|
+
);
|
|
54022
|
+
existingPrPublication = verified.ok ? verified.publication : { blocked: verified.reason };
|
|
54023
|
+
existingPrPublicationFence = { authority: existingPrPublication };
|
|
54024
|
+
if (!verified.ok)
|
|
54025
|
+
shell.note(
|
|
54026
|
+
"quiet",
|
|
54027
|
+
`Existing-PR publication is blocked: ${verified.reason}. Work stays on the bound checkout; no alternate branch or pull request will be published.`
|
|
54028
|
+
);
|
|
54029
|
+
}
|
|
53807
54030
|
if (finish2) {
|
|
53808
54031
|
onEvent({
|
|
53809
54032
|
type: "run_note",
|
|
@@ -53869,38 +54092,6 @@ async function runLoop(deps, ctx) {
|
|
|
53869
54092
|
const providerCfg = deps.config.config.providers[providerName];
|
|
53870
54093
|
if (!providerCfg)
|
|
53871
54094
|
throw new Error(`the ${harness.name} harness found no provider named ${providerName} in the config`);
|
|
53872
|
-
const prBase = repoCtx.baseRef ?? await coordinatorBase;
|
|
53873
|
-
if (coordinator?.publication !== void 0) {
|
|
53874
|
-
const publication = coordinator.publication;
|
|
53875
|
-
const fresh = await (deps.fetchPrFacts ?? fetchPullRequestFacts)({
|
|
53876
|
-
repo: publication.repo,
|
|
53877
|
-
number: publication.pr
|
|
53878
|
-
}).catch(() => void 0);
|
|
53879
|
-
const verified = verifyExistingPrPublication(
|
|
53880
|
-
publication,
|
|
53881
|
-
{
|
|
53882
|
-
repo: repoCtx.repo,
|
|
53883
|
-
pr: repoCtx.pr,
|
|
53884
|
-
ref: repoCtx.ref,
|
|
53885
|
-
baseRef: prBase,
|
|
53886
|
-
requestHeadSha: repoCtx.headSha,
|
|
53887
|
-
workspaceRef: binding?.ref,
|
|
53888
|
-
workspaceHeadSha: binding?.sha,
|
|
53889
|
-
owner: {
|
|
53890
|
-
instanceId: coordinator.parentInstanceId,
|
|
53891
|
-
unit: unitOfIdempotencyKey(coordinator.idempotencyKey) ?? ""
|
|
53892
|
-
}
|
|
53893
|
-
},
|
|
53894
|
-
fresh
|
|
53895
|
-
);
|
|
53896
|
-
existingPrPublication = verified.ok ? verified.publication : { blocked: verified.reason };
|
|
53897
|
-
existingPrPublicationFence = { authority: existingPrPublication };
|
|
53898
|
-
if (!verified.ok)
|
|
53899
|
-
shell.note(
|
|
53900
|
-
"quiet",
|
|
53901
|
-
`Existing-PR publication is blocked: ${verified.reason}. Work stays on the bound checkout; no alternate branch or pull request will be published.`
|
|
53902
|
-
);
|
|
53903
|
-
}
|
|
53904
54095
|
const ownBranch = coordinator?.publication !== void 0 ? coordinator.publication.publicationRef : prBase !== void 0 ? binding?.ref ?? repoCtx.ref : void 0;
|
|
53905
54096
|
const protectedBranches = [
|
|
53906
54097
|
...new Set(
|
|
@@ -54161,6 +54352,8 @@ async function runLoop(deps, ctx) {
|
|
|
54161
54352
|
};
|
|
54162
54353
|
const endingNeedsPreservation = isCodingPrRun && coordinator !== void 0 && (budgetEnded || run2.control.requested !== void 0);
|
|
54163
54354
|
if (endingNeedsPreservation && run2.control.requested === "hard") await endHarness();
|
|
54355
|
+
await events.drain();
|
|
54356
|
+
retainPublicationReceipts();
|
|
54164
54357
|
if (isCodingPrRun && !tailSkipped()) await observeWorkspaceNow();
|
|
54165
54358
|
if (endingNeedsPreservation) await preserveCodingChildWork(budgetEnded ? "budget" : "ending");
|
|
54166
54359
|
else if (isCodingPrRun && coordinator !== void 0 && !tailSkipped()) await preserveCodingChildWork("completion");
|
|
@@ -54376,6 +54569,7 @@ async function runLoop(deps, ctx) {
|
|
|
54376
54569
|
} finally {
|
|
54377
54570
|
clearInterval(heartbeat);
|
|
54378
54571
|
await events.drain();
|
|
54572
|
+
retainPublicationReceipts();
|
|
54379
54573
|
const status3 = statusNow();
|
|
54380
54574
|
if (typeof registry.commitLiveState === "function") {
|
|
54381
54575
|
const commitBoundary = async (state, at2) => {
|
|
@@ -54539,6 +54733,7 @@ var init_runLoop = __esm({
|
|
|
54539
54733
|
init_githubPulls();
|
|
54540
54734
|
init_repoContext();
|
|
54541
54735
|
init_existingPrPublication();
|
|
54736
|
+
init_publicationPush();
|
|
54542
54737
|
init_prDescription();
|
|
54543
54738
|
init_handoff();
|
|
54544
54739
|
init_reviewVerdict();
|
|
@@ -56782,7 +56977,8 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56782
56977
|
);
|
|
56783
56978
|
};
|
|
56784
56979
|
const stickyAgent = thread ? stickyAgentOf(thread) : void 0;
|
|
56785
|
-
const
|
|
56980
|
+
const laterPr = (runPr, releasedPr) => releasedPr !== void 0 && (runPr === void 0 || releasedPr.at > runPr.at) ? releasedPr : runPr;
|
|
56981
|
+
let threadPr = laterPr(thread ? threadPrOf(thread) : void 0, pageOwner?.releasedPr);
|
|
56786
56982
|
const inheritedRepo = operatorRepo ?? (thread ? newestFinishedRunOf(thread)?.repo : void 0) ?? deps.config.scopes(msg.channelId, msg.userId).channel.repo;
|
|
56787
56983
|
const historicalRoutePreset = restart?.row.meta.route?.preset;
|
|
56788
56984
|
const resolveCurrent = () => resolveRun(deps, {
|
|
@@ -56803,6 +56999,7 @@ async function dispatch(deps, msg, io, opts = {}) {
|
|
|
56803
56999
|
const threadLive = admission.get(msg.threadKey) !== void 0 || !resume && !restart && deps.threadsElsewhere.get(msg.threadKey) !== void 0;
|
|
56804
57000
|
if (thread && !threadLive && deps.coordinatorInstances !== void 0) {
|
|
56805
57001
|
const owner = pageOwner ?? await ownerOf2(thread, (id) => deps.coordinatorInstances.listUnits(id), msg.threadKey);
|
|
57002
|
+
threadPr = laterPr(threadPr, owner.releasedPr);
|
|
56806
57003
|
if (owner.kind === "live" && owner.run.hosted === true) {
|
|
56807
57004
|
const units = owner.run.instanceId === void 0 ? [] : await deps.coordinatorInstances.listUnits(owner.run.instanceId).catch(() => []);
|
|
56808
57005
|
const open2 = units.filter((u) => u.ending === void 0);
|
|
@@ -60811,7 +61008,7 @@ function isBudgetRefusal(error) {
|
|
|
60811
61008
|
if (error.status !== void 0 && error.status !== 403) return false;
|
|
60812
61009
|
return TURN_BUDGET_REFUSAL.test(error.message);
|
|
60813
61010
|
}
|
|
60814
|
-
function judgeOpenCodeAsk(action, resources, rules, relayedToolNames) {
|
|
61011
|
+
function judgeOpenCodeAsk(action, resources, rules, relayedToolNames, callId) {
|
|
60815
61012
|
if (action === "doom_loop")
|
|
60816
61013
|
return {
|
|
60817
61014
|
reply: "reject",
|
|
@@ -60820,6 +61017,8 @@ function judgeOpenCodeAsk(action, resources, rules, relayedToolNames) {
|
|
|
60820
61017
|
};
|
|
60821
61018
|
const word = openCodeToolWord(action);
|
|
60822
61019
|
const tool = TOOL_NAME_WORD[action] ?? word;
|
|
61020
|
+
const pending2 = publicationAttributionRefusal(rules, callId);
|
|
61021
|
+
if (pending2 !== void 0) return { reply: "reject", message: pending2, tool };
|
|
60823
61022
|
if (relayedToolNames.has(action) || relayedToolNames.has(word)) return { reply: "once", tool: action };
|
|
60824
61023
|
const refuse3 = (reason2) => ({ reply: "reject", message: reason2, tool });
|
|
60825
61024
|
if (action === "external_directory") {
|
|
@@ -61640,6 +61839,7 @@ var init_bridge3 = __esm({
|
|
|
61640
61839
|
"use strict";
|
|
61641
61840
|
init_budgets();
|
|
61642
61841
|
init_runEvents();
|
|
61842
|
+
init_publicationPush();
|
|
61643
61843
|
init_contract4();
|
|
61644
61844
|
init_container();
|
|
61645
61845
|
init_reattach();
|
|
@@ -62507,9 +62707,14 @@ ${data.text}` : data.text;
|
|
|
62507
62707
|
request2.action,
|
|
62508
62708
|
Array.isArray(request2.resources) ? request2.resources : [],
|
|
62509
62709
|
this.deps.rules,
|
|
62510
|
-
this.deps.relayedToolNames
|
|
62710
|
+
this.deps.relayedToolNames,
|
|
62711
|
+
callId
|
|
62511
62712
|
);
|
|
62512
62713
|
if (this.answered.get(callId) !== "reject") this.answered.set(callId, verdict.reply);
|
|
62714
|
+
const authority = this.deps.rules.publication?.authority;
|
|
62715
|
+
const push = openCodeToolWord(request2.action) === "bash" && request2.resources?.length === 1 ? leasedPushCommand(request2.resources[0]) : void 0;
|
|
62716
|
+
if (this.answered.get(callId) === "once" && push !== void 0 && authority !== void 0 && !("blocked" in authority) && push.ref === authority.ref && push.expectedHeadSha === authority.expectedHeadSha)
|
|
62717
|
+
this.emit({ type: "publication_push_authorized", callId, ...push });
|
|
62513
62718
|
this.decidedReplies.set(request2.id, { reply: verdict.reply, callId, echoed: false });
|
|
62514
62719
|
const stepID = typeof request2.source?.messageID === "string" ? request2.source.messageID : void 0;
|
|
62515
62720
|
if (verdict.reply === "reject") {
|
|
@@ -72943,7 +73148,7 @@ function findingsRequest(input) {
|
|
|
72943
73148
|
|
|
72944
73149
|
A person answered the human-gated finding. Treat the finding and this answer together as the fix brief:
|
|
72945
73150
|
${input.answers.join("\n\n")}` : "";
|
|
72946
|
-
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note),
|
|
73151
|
+
return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note). Copy each finding ID exactly, including any \`check:\u2026\` ID; never substitute an \`F1\` ID for a check. Squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
|
|
72947
73152
|
|
|
72948
73153
|
Findings:
|
|
72949
73154
|
${findings}${answers}
|
|
@@ -74011,6 +74216,36 @@ function publicationStepPrefix(row2) {
|
|
|
74011
74216
|
}
|
|
74012
74217
|
return prefix;
|
|
74013
74218
|
}
|
|
74219
|
+
async function reconcileMissingFindingsPush(deps, instance, row2, runId3, head, siblings, after) {
|
|
74220
|
+
const binding = row2.publication;
|
|
74221
|
+
if (binding === void 0) return false;
|
|
74222
|
+
const read = await deps.runs.getRun(runId3, { include: "messages" });
|
|
74223
|
+
if (!read.ok) return false;
|
|
74224
|
+
const run2 = read.value;
|
|
74225
|
+
const complete2 = (r) => {
|
|
74226
|
+
const models = Object.values(r.usage?.byModel ?? {});
|
|
74227
|
+
return r.finished && r.persisted === true && !r.provisional && !r.restarting && r.truncated === false && r.events !== void 0 && r.eventCount === r.events.length && r.storedEventCount === r.events.length && r.events.every(
|
|
74228
|
+
(e) => e.type !== "tool_call" || e.tool !== "bash" || e.command !== void 0 && e.command.length < COMMAND_CAP && !e.command.includes("\u2026")
|
|
74229
|
+
) && !r.events.some((e) => e.type === "tool_result" && e.cut || e.type === "pushed_head") && models.length > 0 && models.every((m) => typeof m.usd === "number" && Number.isFinite(m.usd) && m.usd >= 0);
|
|
74230
|
+
};
|
|
74231
|
+
const tags = run2.events?.filter((e) => e.type === "coordinator_tag") ?? [];
|
|
74232
|
+
const tag = tags.length === 1 ? tags[0] : void 0;
|
|
74233
|
+
if (!complete2(run2) || run2.status !== "completed" || (run2.pushed?.length ?? 0) !== 0 || run2.headSha !== head || run2.parentInstanceId !== instance.id || run2.userId !== instance.userId || run2.repo?.toLowerCase() !== instance.repo.toLowerCase() || run2.threadKey !== (row2.threadKey ?? instance.threadKey) || run2.agent !== "coding" || run2.startedAt < after || run2.finishedAt === void 0 || run2.finishedAt < run2.startedAt || tag?.parentInstanceId !== instance.id || tag.unit !== row2.unit || tag.base !== instance.base || !samePublicationBinding(tag.publication, binding) || run2.pr !== void 0 && (run2.pr.number !== binding.pr || run2.pr.head !== void 0 && run2.pr.head !== row2.branch) || pairedPublicationPush(run2.events, binding, head) === void 0)
|
|
74234
|
+
return false;
|
|
74235
|
+
for (const sibling of siblings.filter(
|
|
74236
|
+
(r) => r.id !== runId3 && r.agent === "coding" && (r.finishedAt === void 0 || r.finishedAt >= after)
|
|
74237
|
+
)) {
|
|
74238
|
+
if (!sibling.finished || sibling.status !== "failed" && sibling.status !== "interrupted" || sibling.parentInstanceId !== instance.id || sibling.userId !== instance.userId || sibling.repo?.toLowerCase() !== instance.repo.toLowerCase() || sibling.threadKey !== run2.threadKey || sibling.startedAt < after || sibling.finishedAt === void 0 || sibling.finishedAt > run2.startedAt || (sibling.pushed?.length ?? 0) > 0)
|
|
74239
|
+
return false;
|
|
74240
|
+
const prior = await deps.runs.getRun(sibling.id, { include: "messages" });
|
|
74241
|
+
if (!prior.ok || !complete2(prior.value) || prior.value.events.some(
|
|
74242
|
+
(e) => e.type === "pushed_head" || e.type === "tool_call" && e.tool === "bash" && /\bgit\s+push\b/.test(e.command ?? e.summary)
|
|
74243
|
+
))
|
|
74244
|
+
return false;
|
|
74245
|
+
}
|
|
74246
|
+
const fresh = await deps.fetchPrFacts({ repo: instance.repo, number: binding.pr }).catch(() => void 0);
|
|
74247
|
+
return fresh?.state === "open" && fresh.sameRepoHead === true && fresh.headBranchExists === true && fresh.headRef === binding.headRef && fresh.baseRef === binding.baseRef && fresh.headSha === head && fresh.verifiedHead?.repo.toLowerCase() === instance.repo.toLowerCase() && fresh.verifiedHead.ref === binding.publicationRef && fresh.verifiedHead.sha === head;
|
|
74248
|
+
}
|
|
74014
74249
|
async function advanceFindingsPublication(deps, instance, row2, facts2, runId3) {
|
|
74015
74250
|
if (row2.publication === void 0 || facts2.headSha === row2.publication.expectedHeadSha) return row2;
|
|
74016
74251
|
const child = await deps.runs.getRun(runId3, { include: "messages" });
|
|
@@ -74018,6 +74253,7 @@ async function advanceFindingsPublication(deps, instance, row2, facts2, runId3)
|
|
|
74018
74253
|
const latestReview = [...row2.rounds].reverse().find((round2) => round2.agent === "review");
|
|
74019
74254
|
const prefix = row2.recovery !== void 0 ? `${row2.unit}/recovery` : publicationStepPrefix(row2);
|
|
74020
74255
|
const findingsPrefix = `${instance.id}:${prefix}/${latestReview?.index}/findings`;
|
|
74256
|
+
let reconciledPush = false;
|
|
74021
74257
|
if (row2.recovery === void 0) {
|
|
74022
74258
|
const tags = child.ok ? child.value.events?.filter((event) => event.type === "coordinator_tag") ?? [] : [];
|
|
74023
74259
|
const tag = tags.length === 1 ? tags[0] : void 0;
|
|
@@ -74036,8 +74272,18 @@ async function advanceFindingsPublication(deps, instance, row2, facts2, runId3)
|
|
|
74036
74272
|
(run2) => run2.id !== runId3 && !(run2.finished && (run2.status === "failed" || run2.status === "interrupted") && run2.agent === "coding" && run2.userId === instance.userId && run2.repo?.toLowerCase() === instance.repo.toLowerCase() && run2.threadKey === (row2.threadKey ?? instance.threadKey) && run2.startedAt >= latestReview.at && run2.finishedAt !== void 0 && run2.finishedAt >= run2.startedAt && run2.finishedAt <= child.value.startedAt && (run2.pushed?.length ?? 0) === 0)
|
|
74037
74273
|
))
|
|
74038
74274
|
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
74275
|
+
if (child.ok && (child.value.pushed?.length ?? 0) === 0 && fullHead(facts2.headSha))
|
|
74276
|
+
reconciledPush = await reconcileMissingFindingsPush(
|
|
74277
|
+
deps,
|
|
74278
|
+
instance,
|
|
74279
|
+
row2,
|
|
74280
|
+
runId3,
|
|
74281
|
+
facts2.headSha,
|
|
74282
|
+
listing.runs,
|
|
74283
|
+
latestReview.at
|
|
74284
|
+
);
|
|
74039
74285
|
}
|
|
74040
|
-
if (!child.ok || child.value.finished !== true || child.value.status !== "completed" || child.value.agent !== "coding" || child.value.parentInstanceId !== instance.id || child.value.userId !== instance.userId || child.value.repo?.toLowerCase() !== instance.repo.toLowerCase() || child.value.threadKey !== (row2.threadKey ?? instance.threadKey) || (row2.recovery !== void 0 ? child.value.idempotencyKey?.startsWith(`${instance.id}:${row2.unit}/recovery/`) !== true || !/\/findings(?:\/a[1-9][0-9]*)?$/.test(child.value.idempotencyKey ?? "") : !isStepAttempt(child.value.idempotencyKey, findingsPrefix)) || !fullHead(facts2.headSha) || child.value.headSha !== facts2.headSha || child.value.pushed?.some((push) => push.ref === row2.branch && push.sha === facts2.headSha) !== true || facts2.state !== "open" || facts2.sameRepoHead !== true || facts2.headBranchExists !== true || facts2.headRef !== row2.publication.headRef || facts2.baseRef !== row2.publication.baseRef || facts2.verifiedHead?.repo.toLowerCase() !== instance.repo.toLowerCase() || facts2.verifiedHead.ref !== row2.publication.publicationRef || facts2.verifiedHead.sha !== facts2.headSha || row2.publication.repo.toLowerCase() !== instance.repo.toLowerCase() || row2.publication.pr !== row2.pr?.number || row2.publication.baseRef !== instance.base || row2.publication.headRef !== row2.branch || row2.publication.publicationRef !== row2.branch || !samePublicationOwner(row2.publication.owner, owner))
|
|
74286
|
+
if (!child.ok || child.value.finished !== true || child.value.status !== "completed" || child.value.agent !== "coding" || child.value.parentInstanceId !== instance.id || child.value.userId !== instance.userId || child.value.repo?.toLowerCase() !== instance.repo.toLowerCase() || child.value.threadKey !== (row2.threadKey ?? instance.threadKey) || (row2.recovery !== void 0 ? child.value.idempotencyKey?.startsWith(`${instance.id}:${row2.unit}/recovery/`) !== true || !/\/findings(?:\/a[1-9][0-9]*)?$/.test(child.value.idempotencyKey ?? "") : !isStepAttempt(child.value.idempotencyKey, findingsPrefix)) || !fullHead(facts2.headSha) || child.value.headSha !== facts2.headSha || !reconciledPush && child.value.pushed?.some((push) => push.ref === row2.branch && push.sha === facts2.headSha) !== true || facts2.state !== "open" || facts2.sameRepoHead !== true || facts2.headBranchExists !== true || facts2.headRef !== row2.publication.headRef || facts2.baseRef !== row2.publication.baseRef || facts2.verifiedHead?.repo.toLowerCase() !== instance.repo.toLowerCase() || facts2.verifiedHead.ref !== row2.publication.publicationRef || facts2.verifiedHead.sha !== facts2.headSha || row2.publication.repo.toLowerCase() !== instance.repo.toLowerCase() || row2.publication.pr !== row2.pr?.number || row2.publication.baseRef !== instance.base || row2.publication.headRef !== row2.branch || row2.publication.publicationRef !== row2.branch || !samePublicationOwner(row2.publication.owner, owner))
|
|
74041
74287
|
throw new PublicationBindingRefusal("publication_facts_mismatch");
|
|
74042
74288
|
try {
|
|
74043
74289
|
if (row2.recovery !== void 0 && deps.runnerOwnership?.claim(instance.repo, row2.publication.pr, owner) !== true)
|
|
@@ -74334,7 +74580,16 @@ async function recoverOriginalUnit(body, deps, caller) {
|
|
|
74334
74580
|
const checkedAttempts = attempts.filter((run2) => run2.idempotencyKey === `${instance.id}:${checkedStep}`);
|
|
74335
74581
|
const completed = checkedAttempts.length === 1 ? checkedAttempts[0] : void 0;
|
|
74336
74582
|
const reviewFinishedAt = review.finishedAt;
|
|
74337
|
-
|
|
74583
|
+
const reconciledPush = completed !== void 0 && headMoved && reviewFinishedAt !== void 0 && (completed.pushed?.length ?? 0) === 0 && await reconcileMissingFindingsPush(
|
|
74584
|
+
deps,
|
|
74585
|
+
instance,
|
|
74586
|
+
row2,
|
|
74587
|
+
completed.id,
|
|
74588
|
+
facts2.headSha,
|
|
74589
|
+
unitListing.runs,
|
|
74590
|
+
reviewFinishedAt
|
|
74591
|
+
);
|
|
74592
|
+
if (reviewFinishedAt === void 0 || completed === void 0 || !completed.finished || completed.status !== "completed" || completed.agent !== "coding" || completed.userId !== instance.userId || completed.repo?.toLowerCase() !== instance.repo.toLowerCase() || completed.threadKey !== unitThreadKey || completed.pr !== void 0 && (completed.pr.number !== pr.number || completed.pr.head !== void 0 && completed.pr.head !== row2.branch) || completed.headSha !== facts2.headSha || expectedHead !== reviewedHead && expectedHead !== facts2.headSha || completed.pushed?.some((push) => push.ref !== row2.branch) === true || headMoved && !reconciledPush && completed.pushed?.some((push) => push.ref === row2.branch && push.sha === facts2.headSha) !== true || completed.startedAt < reviewFinishedAt || completed.finishedAt === void 0 || completed.finishedAt < completed.startedAt || completed.finishedAt > row2.ending.at || // Inspect every attempt, not just runs matching the current PR head:
|
|
74338
74593
|
// any competing success or push remains ambiguous after a later push.
|
|
74339
74594
|
attempts.some(
|
|
74340
74595
|
(run2) => run2.id !== completed.id && !(run2.finished && (run2.status === "failed" || run2.status === "interrupted") && run2.agent === "coding" && run2.userId === instance.userId && run2.repo?.toLowerCase() === instance.repo.toLowerCase() && run2.threadKey === unitThreadKey && run2.startedAt >= reviewFinishedAt && run2.finishedAt !== void 0 && run2.finishedAt >= run2.startedAt && run2.finishedAt <= completed.startedAt && (run2.pushed?.length ?? 0) === 0)
|
|
@@ -75998,6 +76253,7 @@ var init_adminCoordinator = __esm({
|
|
|
75998
76253
|
"use strict";
|
|
75999
76254
|
init_budgets();
|
|
76000
76255
|
init_verbosity();
|
|
76256
|
+
init_publicationPush();
|
|
76001
76257
|
init_prDescription();
|
|
76002
76258
|
init_reply();
|
|
76003
76259
|
init_registry();
|
|
@@ -76012,6 +76268,7 @@ var init_adminCoordinator = __esm({
|
|
|
76012
76268
|
init_effort();
|
|
76013
76269
|
init_reviewVerdict();
|
|
76014
76270
|
init_runFriction();
|
|
76271
|
+
init_runEvents();
|
|
76015
76272
|
init_runRecord();
|
|
76016
76273
|
init_coordinator();
|
|
76017
76274
|
init_renewal();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreplane/switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.265.1",
|
|
4
4
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://openswitchboard.dev",
|