@proagentstore/cli 0.4.42 → 0.4.43
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.
|
@@ -94,6 +94,70 @@ function prRef(segment) {
|
|
|
94
94
|
const num = segment.match(/\bgh\s+pr\s+(?:merge|create)\b(?:\s+-{1,2}\S+(?:=\S+)?)*\s+(\d+)\b/);
|
|
95
95
|
return num ? `#${num[1]}` : null;
|
|
96
96
|
}
|
|
97
|
+
/** A pull request URL anywhere in a command's own output. Anchored on a literal `http` prefix. */
|
|
98
|
+
const PR_URL = /https?:\/\/\S*?\/pull\/(\d+)/;
|
|
99
|
+
/**
|
|
100
|
+
* Flatten a `tool_result` block's content to text — the RAW result, not the display line.
|
|
101
|
+
*
|
|
102
|
+
* `headless.ts`'s `toolResult()` collapses whitespace and truncates to 240 characters for the
|
|
103
|
+
* transcript, which is right for a human-readable pane and wrong here: `gh pr create` prints its
|
|
104
|
+
* URL after whatever else the compound command wrote, so the one token that matters is exactly what
|
|
105
|
+
* a 240-character cap would drop.
|
|
106
|
+
*/
|
|
107
|
+
export function resultText(content) {
|
|
108
|
+
if (typeof content === "string")
|
|
109
|
+
return content;
|
|
110
|
+
if (Array.isArray(content)) {
|
|
111
|
+
return content
|
|
112
|
+
.map((b) => (b && typeof b === "object" && "text" in b ? String(b.text ?? "") : ""))
|
|
113
|
+
.join("\n");
|
|
114
|
+
}
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
/** An act that names a pull request but does not yet say WHICH one. */
|
|
118
|
+
function wantsPrNumber(act) {
|
|
119
|
+
return (act.kind === "pr.open" || act.kind === "pr.merge") && act.target === null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Fill in the PR number from the command's OWN output, for the acts that lack one (#417).
|
|
123
|
+
*
|
|
124
|
+
* `gh pr create --fill` is the common form and carries no number on the command line, so
|
|
125
|
+
* {@link prRef} returns null and `pull-attribution.ts` leaves the row unbadged. That file states the
|
|
126
|
+
* rule — **ATTRIBUTION IS EXACT OR ABSENT** — and rejects the tempting repair of pairing an
|
|
127
|
+
* unnumbered `pr.open` with whichever PR appeared around the same time, because a badge that is
|
|
128
|
+
* right most of the time is worse than no badge.
|
|
129
|
+
*
|
|
130
|
+
* This is not that repair. `gh pr create` prints the new PR's URL on stdout, and that output comes
|
|
131
|
+
* back as the `tool_result` for the SAME `tool_use_id` as the command that was classified. It is the
|
|
132
|
+
* command's own answer, in the same class of fact as the `ok` flag already taken from that block —
|
|
133
|
+
* not a temporal guess about what appeared nearby. When the output names no PR the target stays
|
|
134
|
+
* null, so absent stays absent.
|
|
135
|
+
*
|
|
136
|
+
* Deliberately narrow, because the one failure this module exists to avoid is a confident wrong
|
|
137
|
+
* number: only `pr.open`/`pr.merge` are eligible, only when their target is still null, and only
|
|
138
|
+
* against the result correlated to them by `tool_use_id`. A PR URL quoted incidentally in some other
|
|
139
|
+
* command's output reaches no act.
|
|
140
|
+
*
|
|
141
|
+
* **The "already exists" case is decided, not overlooked.** `gh pr create` on a branch that already
|
|
142
|
+
* has a PR fails with "a pull request for branch X already exists:" and that PR's URL, and this
|
|
143
|
+
* attributes the act to it. That is a judgement: it IS the pull request for this branch and the
|
|
144
|
+
* agent did just act on it, and dropping a real signal to avoid a case where the answer is still
|
|
145
|
+
* true costs more than it saves. The act still carries `ok: false`, so the record remains honest
|
|
146
|
+
* that the command failed.
|
|
147
|
+
*
|
|
148
|
+
* Only reachable from the structured stream-json path. A Codex/Grok raw spawn has no
|
|
149
|
+
* `tool_use`/`tool_result` framing at all, so its PRs stay unattributed — regexing its transcript
|
|
150
|
+
* instead would reintroduce exactly the temporal guess this design refuses.
|
|
151
|
+
*/
|
|
152
|
+
export function fillTargetFromResult(acts, content) {
|
|
153
|
+
if (!acts.some(wantsPrNumber))
|
|
154
|
+
return acts;
|
|
155
|
+
const m = resultText(content).match(PR_URL);
|
|
156
|
+
if (!m)
|
|
157
|
+
return acts;
|
|
158
|
+
const target = `#${m[1]}`;
|
|
159
|
+
return acts.map((a) => (wantsPrNumber(a) ? { ...a, target } : a));
|
|
160
|
+
}
|
|
97
161
|
function pushTarget(segment) {
|
|
98
162
|
// `git push [flags] <remote> <refspec>` — take the first two non-flag words after `push`.
|
|
99
163
|
const after = segment.split(/\bgit\s+push\b/)[1] ?? "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { classifyCommand, commandFromToolInput } from "./engine-acts.js";
|
|
2
|
+
import { classifyCommand, commandFromToolInput, fillTargetFromResult } from "./engine-acts.js";
|
|
3
3
|
import { parseEngineUsage } from "./engine-usage.js";
|
|
4
4
|
/**
|
|
5
5
|
* Merge the platform's resolved engine env over the machine's, where an EMPTY value means
|
|
@@ -604,7 +604,19 @@ export class HeadlessSession {
|
|
|
604
604
|
return;
|
|
605
605
|
this.awaitingResult.set(toolUseId, acts);
|
|
606
606
|
}
|
|
607
|
-
/**
|
|
607
|
+
/**
|
|
608
|
+
* The matching `tool_result` arrived — stamp the outcome and publish.
|
|
609
|
+
*
|
|
610
|
+
* The result carries more than the outcome: `gh pr create --fill` states its PR number nowhere
|
|
611
|
+
* but its own stdout, so an unnumbered `pr.open`/`pr.merge` takes it from here (#417). It is read
|
|
612
|
+
* from the RAW `block.content`, never from `toolResult()`'s display line — that truncates to 240
|
|
613
|
+
* characters for the transcript and would cut the URL off a verbose result.
|
|
614
|
+
*
|
|
615
|
+
* This path (and `noteAct`) is reachable ONLY from the structured stream-json handling above
|
|
616
|
+
* (`assistant` → `tool_use`, `user` → `tool_result`). A Codex/Grok session is a raw spawn with no
|
|
617
|
+
* such framing, so its PRs stay unattributed by construction; scraping its transcript instead
|
|
618
|
+
* would be the temporal guess `pull-attribution.ts` refuses.
|
|
619
|
+
*/
|
|
608
620
|
settleAct(block) {
|
|
609
621
|
const id = typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
|
610
622
|
const acts = this.awaitingResult.get(id);
|
|
@@ -612,7 +624,7 @@ export class HeadlessSession {
|
|
|
612
624
|
return;
|
|
613
625
|
this.awaitingResult.delete(id);
|
|
614
626
|
const ok = block.is_error !== true;
|
|
615
|
-
for (const a of acts)
|
|
627
|
+
for (const a of fillTargetFromResult(acts, block.content))
|
|
616
628
|
this.publishAct({ ...a, ok });
|
|
617
629
|
}
|
|
618
630
|
/** Publish everything still waiting, with an unknown outcome. */
|