@trim21/personal-pi-extensions 0.0.126 → 0.0.128

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.
Files changed (2) hide show
  1. package/package.json +3 -2
  2. package/src/gh-readonly.ts +66 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.0.126",
3
+ "version": "0.0.128",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -50,8 +50,9 @@
50
50
  "src/bwrap/index.ts",
51
51
  "src/workspace-guard.ts",
52
52
  "src/opencode-edit.ts",
53
+ "src/opencode-read.ts",
54
+ "src/opencode-write.ts",
53
55
  "src/bash-default-timeout.ts",
54
- "src/agents-md-user-message.ts",
55
56
  "src/gh-readonly.ts",
56
57
  "src/todo-pendant.ts"
57
58
  ]
@@ -141,6 +141,60 @@ export class GhError extends Error {
141
141
  }
142
142
  }
143
143
 
144
+ /** How long `read-github-pr-status` waits for pending checks to resolve. */
145
+ const POLL_INTERVAL_MS = 30_000;
146
+ const POLL_TIMEOUT_MS = 30 * 60_000;
147
+
148
+ /** Sleep for `ms`, resolving early if `signal` is aborted. */
149
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
150
+ return new Promise((resolve) => {
151
+ if (signal?.aborted) {
152
+ resolve();
153
+ return;
154
+ }
155
+ const onAbort = () => {
156
+ clearTimeout(timer);
157
+ resolve();
158
+ };
159
+ const timer = setTimeout(() => {
160
+ signal?.removeEventListener("abort", onAbort);
161
+ resolve();
162
+ }, ms);
163
+ signal?.addEventListener("abort", onAbort, { once: true });
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Poll a `gh pr checks` query until it reaches a final state.
169
+ *
170
+ * `gh pr checks` exit codes: 0 = all passed, 1 = some failed, 8 = some pending.
171
+ * Pending (8) is polled every `intervalMs` until `timeoutMs` elapses, at which
172
+ * point the current result is returned as-is. Any other code is returned
173
+ * immediately; the caller decides whether it is an error.
174
+ */
175
+ export async function pollChecksResult<R extends { code: number; stdout: string }>(
176
+ query: () => Promise<R>,
177
+ opts: { signal?: AbortSignal; intervalMs?: number; timeoutMs?: number } = {},
178
+ ): Promise<R> {
179
+ const { signal, intervalMs = POLL_INTERVAL_MS, timeoutMs = POLL_TIMEOUT_MS } = opts;
180
+ const deadline = Date.now() + timeoutMs;
181
+ for (;;) {
182
+ if (signal?.aborted) {
183
+ throw new Error("read-github-pr-status aborted");
184
+ }
185
+ const result = await query();
186
+ if (result.code !== 8) {
187
+ // 0 = all passed, 1 = some failed, anything else is a real error
188
+ return result;
189
+ }
190
+ // Still pending — keep waiting unless the overall timeout expired
191
+ if (Date.now() >= deadline) {
192
+ return result;
193
+ }
194
+ await sleep(intervalMs, signal);
195
+ }
196
+ }
197
+
144
198
  /** Run `gh` and return stdout. On non-zero exit, throws a `GhError` carrying the toolcall input and raw command. */
145
199
  async function ghExec(
146
200
  args: string[],
@@ -592,13 +646,18 @@ export default function (pi: ExtensionAPI) {
592
646
  }),
593
647
  async execute(_id, params, signal, _onUpdate, ctx) {
594
648
  const { number, repo } = params;
595
- return toToolResult(
596
- await ghExec(["pr", "checks", String(number), ...repoArgs(repo)], {
597
- cwd: ctx.cwd,
598
- signal,
599
- input: params,
600
- }),
601
- );
649
+ const args = ["pr", "checks", String(number), ...repoArgs(repo)];
650
+
651
+ // Pending is not an error — poll until checks fail or all pass.
652
+ const final = await pollChecksResult(() => runGh(args, { cwd: ctx.cwd, signal }), {
653
+ signal,
654
+ });
655
+
656
+ if (final.code !== 0 && final.code !== 1 && final.code !== 8) {
657
+ // Anything else is a real error (cancelled, auth, network, ...)
658
+ throw new GhError(args, final, params);
659
+ }
660
+ return toToolResult(final.stdout);
602
661
  },
603
662
  });
604
663