terminalhire 0.40.12 → 0.41.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/bin/jpi-claim.js +378 -160
- package/dist/bin/jpi-dispatch.js +401 -113
- package/dist/bin/jpi-mcp.js +402 -170
- package/dist/bin/jpi-statusline-launch.js +6 -0
- package/dist/bin/jpi-statusline.js +1 -1
- package/package.json +1 -1
- package/statusline-install.js +64 -3
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -26056,6 +26056,13 @@ function openInBrowser(url) {
|
|
|
26056
26056
|
}
|
|
26057
26057
|
}
|
|
26058
26058
|
|
|
26059
|
+
// bin/sanitize.js
|
|
26060
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
26061
|
+
function sanitizeText(s) {
|
|
26062
|
+
if (s == null) return "";
|
|
26063
|
+
return String(s).replace(CONTROL_CHARS, "");
|
|
26064
|
+
}
|
|
26065
|
+
|
|
26059
26066
|
// src/policy-acks.ts
|
|
26060
26067
|
init_state_dir();
|
|
26061
26068
|
import { lstatSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
@@ -26129,7 +26136,6 @@ var TERMINALHIRE_DIR5 = process.env.TERMINALHIRE_DIR || join7(homedir5(), ".term
|
|
|
26129
26136
|
var CLAIM_PUSH_AUTO_MARKER = join7(TERMINALHIRE_DIR5, "claim-push-auto.json");
|
|
26130
26137
|
var CLAIM_PUSH_TOKEN_FILE = join7(TERMINALHIRE_DIR5, "claim-push-token.enc");
|
|
26131
26138
|
var CLAIM_PUSH_MANUAL_MARKER = join7(TERMINALHIRE_DIR5, "claim-push.json");
|
|
26132
|
-
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
26133
26139
|
var AUTO_CONSENT_VERSION = 2;
|
|
26134
26140
|
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
26135
26141
|
async function writePushTokenEnc(rawToken) {
|
|
@@ -26174,28 +26180,6 @@ function clearAutoMarker() {
|
|
|
26174
26180
|
function computeSnapshotHash(pushed) {
|
|
26175
26181
|
return createHash3("sha256").update(JSON.stringify(pushed)).digest("hex");
|
|
26176
26182
|
}
|
|
26177
|
-
function backgroundPushGate(params) {
|
|
26178
|
-
const {
|
|
26179
|
-
autoMarkerExists,
|
|
26180
|
-
tokenFileExists,
|
|
26181
|
-
lastPushedAt,
|
|
26182
|
-
now,
|
|
26183
|
-
throttleMs,
|
|
26184
|
-
currentHash,
|
|
26185
|
-
lastSnapshotHash
|
|
26186
|
-
} = params;
|
|
26187
|
-
if (!autoMarkerExists || !tokenFileExists) {
|
|
26188
|
-
return { push: false, reason: "not-opted-in" };
|
|
26189
|
-
}
|
|
26190
|
-
const last = lastPushedAt ? Date.parse(lastPushedAt) : NaN;
|
|
26191
|
-
if (!Number.isNaN(last) && now - last < throttleMs) {
|
|
26192
|
-
return { push: false, reason: "throttled" };
|
|
26193
|
-
}
|
|
26194
|
-
if (lastSnapshotHash && lastSnapshotHash === currentHash) {
|
|
26195
|
-
return { push: false, reason: "unchanged" };
|
|
26196
|
-
}
|
|
26197
|
-
return { push: true, reason: "ok" };
|
|
26198
|
-
}
|
|
26199
26183
|
function unpushedNudgeGate(params) {
|
|
26200
26184
|
const {
|
|
26201
26185
|
autoMarkerExists,
|
|
@@ -26233,50 +26217,107 @@ async function shouldNudgeUnpushed() {
|
|
|
26233
26217
|
return false;
|
|
26234
26218
|
}
|
|
26235
26219
|
}
|
|
26236
|
-
|
|
26220
|
+
|
|
26221
|
+
// bin/founder-verdict-sync.js
|
|
26222
|
+
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
26223
|
+
var TERMINAL = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
26224
|
+
function verdictState(verdict) {
|
|
26225
|
+
return verdict === "rejected" ? "abandoned" : "merged";
|
|
26226
|
+
}
|
|
26227
|
+
async function fetchFounderVerdicts(pushToken, fetchImpl = fetch) {
|
|
26228
|
+
if (typeof pushToken !== "string" || pushToken.length === 0) return null;
|
|
26237
26229
|
try {
|
|
26238
|
-
|
|
26239
|
-
return { pushed: false, reason: "not-opted-in" };
|
|
26240
|
-
}
|
|
26241
|
-
const marker = readAutoMarker();
|
|
26242
|
-
if (!marker || !marker.autoConsentedAt) return { pushed: false, reason: "not-opted-in" };
|
|
26243
|
-
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2, PUSHED_CLAIM_FIELDS: PUSHED_CLAIM_FIELDS2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
26244
|
-
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
26245
|
-
const currentHash = computeSnapshotHash(pushed);
|
|
26246
|
-
const gate = backgroundPushGate({
|
|
26247
|
-
autoMarkerExists: true,
|
|
26248
|
-
tokenFileExists: true,
|
|
26249
|
-
lastPushedAt: marker.lastPushedAt ?? null,
|
|
26250
|
-
now,
|
|
26251
|
-
throttleMs: AUTO_PUSH_THROTTLE_MS,
|
|
26252
|
-
currentHash,
|
|
26253
|
-
lastSnapshotHash: marker.lastSnapshotHash ?? null
|
|
26254
|
-
});
|
|
26255
|
-
if (!gate.push) return { pushed: false, reason: gate.reason };
|
|
26256
|
-
const token = await readPushTokenEnc();
|
|
26257
|
-
if (!token) return { pushed: false, reason: "unreadable-token" };
|
|
26258
|
-
const consentReceipt = {
|
|
26259
|
-
consentedAt: marker.autoConsentedAt,
|
|
26260
|
-
version: AUTO_CONSENT_VERSION,
|
|
26261
|
-
fields: PUSHED_CLAIM_FIELDS2
|
|
26262
|
-
};
|
|
26263
|
-
const res = await fetch(`${CLAIM_SYNC_BASE}/api/claim-sync`, {
|
|
26230
|
+
const res = await fetchImpl(`${CLAIM_SYNC_BASE}/api/claim/verdicts`, {
|
|
26264
26231
|
method: "POST",
|
|
26265
26232
|
headers: { "Content-Type": "application/json" },
|
|
26266
|
-
body: JSON.stringify({
|
|
26267
|
-
signal: AbortSignal.timeout(
|
|
26233
|
+
body: JSON.stringify({ pushToken }),
|
|
26234
|
+
signal: AbortSignal.timeout(15e3)
|
|
26268
26235
|
});
|
|
26269
|
-
if (!res
|
|
26270
|
-
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26274
|
-
|
|
26275
|
-
|
|
26236
|
+
if (!res?.ok) return null;
|
|
26237
|
+
const body = await res.json();
|
|
26238
|
+
if (!body || !Array.isArray(body.verdicts)) return null;
|
|
26239
|
+
const verdicts = body.verdicts.filter(
|
|
26240
|
+
(v) => v && typeof v.claimId === "string" && v.claimId !== "" && (v.verdict === "accepted" || v.verdict === "rejected")
|
|
26241
|
+
);
|
|
26242
|
+
return { verdicts, latestAt: typeof body.latestAt === "string" ? body.latestAt : null };
|
|
26243
|
+
} catch {
|
|
26244
|
+
return null;
|
|
26245
|
+
}
|
|
26246
|
+
}
|
|
26247
|
+
function planVerdictTransitions(claims, verdicts, nextPolledState2) {
|
|
26248
|
+
if (!Array.isArray(claims) || !Array.isArray(verdicts)) return [];
|
|
26249
|
+
const byClaimId = /* @__PURE__ */ new Map();
|
|
26250
|
+
for (const v of verdicts) {
|
|
26251
|
+
if (v && typeof v.claimId === "string" && v.claimId !== "") byClaimId.set(v.claimId, v);
|
|
26252
|
+
}
|
|
26253
|
+
const plan = [];
|
|
26254
|
+
for (const c of claims) {
|
|
26255
|
+
const claimId = c?.approval?.claimId;
|
|
26256
|
+
if (!claimId) continue;
|
|
26257
|
+
const v = byClaimId.get(claimId);
|
|
26258
|
+
if (!v) continue;
|
|
26259
|
+
if (TERMINAL.has(c.state)) continue;
|
|
26260
|
+
const to = verdictState(v.verdict);
|
|
26261
|
+
const next = nextPolledState2(c.state, to);
|
|
26262
|
+
if (next === c.state) continue;
|
|
26263
|
+
plan.push({
|
|
26264
|
+
id: c.id,
|
|
26265
|
+
claimId,
|
|
26266
|
+
from: c.state,
|
|
26267
|
+
to: next,
|
|
26268
|
+
verdict: v.verdict,
|
|
26269
|
+
settled: v.settled === true,
|
|
26270
|
+
amountUSD: typeof c.amountUSD === "number" ? c.amountUSD : null,
|
|
26271
|
+
title: typeof c.title === "string" ? c.title : ""
|
|
26276
26272
|
});
|
|
26277
|
-
|
|
26273
|
+
}
|
|
26274
|
+
return plan;
|
|
26275
|
+
}
|
|
26276
|
+
function buildVerdictNotice(t) {
|
|
26277
|
+
if (!t || typeof t.to !== "string") return null;
|
|
26278
|
+
const amount = typeof t.amountUSD === "number" && t.amountUSD > 0 ? `$${t.amountUSD}` : null;
|
|
26279
|
+
if (t.verdict === "rejected") {
|
|
26280
|
+
return ` \u2717 founder rejected${amount ? ` \u2014 ${amount}` : ""} \xB7 claim moved to ${t.to}`;
|
|
26281
|
+
}
|
|
26282
|
+
const paid = t.settled ? " \xB7 paid" : "";
|
|
26283
|
+
return ` \u2713 founder accepted${amount ? ` \u2014 ${amount}` : ""}${paid} \xB7 claim moved to ${t.to}`;
|
|
26284
|
+
}
|
|
26285
|
+
async function syncFounderVerdicts({
|
|
26286
|
+
claimsModule,
|
|
26287
|
+
targets,
|
|
26288
|
+
readPushTokenEnc: readPushTokenEnc2,
|
|
26289
|
+
fetchImpl = fetch,
|
|
26290
|
+
log = console.log
|
|
26291
|
+
} = {}) {
|
|
26292
|
+
const quiet = { checked: false, unavailable: false, applied: [] };
|
|
26293
|
+
try {
|
|
26294
|
+
const founderTargets = (targets ?? []).filter(
|
|
26295
|
+
(c) => Boolean(c?.approval) && !TERMINAL.has(c.state)
|
|
26296
|
+
);
|
|
26297
|
+
if (founderTargets.length === 0) return quiet;
|
|
26298
|
+
let pushToken = null;
|
|
26299
|
+
try {
|
|
26300
|
+
pushToken = await readPushTokenEnc2();
|
|
26301
|
+
} catch {
|
|
26302
|
+
pushToken = null;
|
|
26303
|
+
}
|
|
26304
|
+
if (!pushToken) return quiet;
|
|
26305
|
+
const res = await fetchFounderVerdicts(pushToken, fetchImpl);
|
|
26306
|
+
if (!res) return { checked: false, unavailable: true, applied: [] };
|
|
26307
|
+
const plan = planVerdictTransitions(founderTargets, res.verdicts, claimsModule.nextPolledState);
|
|
26308
|
+
const applied = [];
|
|
26309
|
+
for (const t of plan) {
|
|
26310
|
+
try {
|
|
26311
|
+
claimsModule.updateClaim(t.id, { state: t.to });
|
|
26312
|
+
applied.push(t);
|
|
26313
|
+
const line = buildVerdictNotice(t);
|
|
26314
|
+
if (line) log(line);
|
|
26315
|
+
} catch {
|
|
26316
|
+
}
|
|
26317
|
+
}
|
|
26318
|
+
return { checked: true, unavailable: false, applied };
|
|
26278
26319
|
} catch {
|
|
26279
|
-
return
|
|
26320
|
+
return quiet;
|
|
26280
26321
|
}
|
|
26281
26322
|
}
|
|
26282
26323
|
|
|
@@ -27218,15 +27259,35 @@ async function localLoginForPaidBrowserClaim() {
|
|
|
27218
27259
|
if (timer) clearTimeout(timer);
|
|
27219
27260
|
}
|
|
27220
27261
|
}
|
|
27262
|
+
var PUSH_TOKEN_REFUSAL = Object.freeze({
|
|
27263
|
+
/** The credential is unknown or revoked — dead. Clear it. */
|
|
27264
|
+
INVALID: "invalid-push-token",
|
|
27265
|
+
/** The credential is LIVE, just not a registration credential. NEVER clear it. */
|
|
27266
|
+
INSUFFICIENT: "insufficient-push-token",
|
|
27267
|
+
/**
|
|
27268
|
+
* TERM-325. Minted before tokens were bound to a GitHub account id, so it can no
|
|
27269
|
+
* longer prove who holds it. Unrevoked and real, and it fails closed at every scope
|
|
27270
|
+
* — the slice route answers `invalid-push-token` for the same row — so the ACTION is
|
|
27271
|
+
* the revoked one: clear it and verify in the browser.
|
|
27272
|
+
*
|
|
27273
|
+
* The WORDING is not. The server is explicit that nothing was revoked and the
|
|
27274
|
+
* requirement changed on our side, so telling this developer they revoked something
|
|
27275
|
+
* is false. Sharing a branch must not mean sharing a sentence.
|
|
27276
|
+
*/
|
|
27277
|
+
LEGACY: "legacy-push-token"
|
|
27278
|
+
});
|
|
27279
|
+
var SYNC_BACKGROUND_PUSH_ACTIVE_FIELD = "backgroundPushActive";
|
|
27221
27280
|
async function registerFounderClaim(b) {
|
|
27222
27281
|
const postingId = b.bountyId.replace(/^bounty:founder:/, "");
|
|
27282
|
+
let clearedLocalCredential = false;
|
|
27283
|
+
let refusedForPurpose = false;
|
|
27223
27284
|
const refuse = (reason) => {
|
|
27224
27285
|
console.error(
|
|
27225
27286
|
`
|
|
27226
27287
|
terminalhire claim: refusing to record \u2014 ${reason}
|
|
27227
|
-
|
|
27288
|
+
No CLAIM was recorded: a founder-posting claim registers with terminalhire BEFORE
|
|
27228
27289
|
it is recorded locally (fail-closed), so a posting that is gone, taken, or
|
|
27229
|
-
unverifiable is never claimed on stale cache data.`
|
|
27290
|
+
unverifiable is never claimed on stale cache data.` + (clearedLocalCredential ? "\n One local change was kept: the stored push token was deleted from this\n machine (the server no longer accepts it, so keeping it would fail every\n later claim)." : "")
|
|
27230
27291
|
);
|
|
27231
27292
|
process.exit(1);
|
|
27232
27293
|
};
|
|
@@ -27241,7 +27302,7 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27241
27302
|
} catch {
|
|
27242
27303
|
}
|
|
27243
27304
|
let expectLogin;
|
|
27244
|
-
|
|
27305
|
+
const acquireProofAuth = async () => {
|
|
27245
27306
|
try {
|
|
27246
27307
|
expectLogin = await localLoginForPaidBrowserClaim();
|
|
27247
27308
|
} catch (err) {
|
|
@@ -27275,7 +27336,8 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27275
27336
|
);
|
|
27276
27337
|
}
|
|
27277
27338
|
auth = { proofToken };
|
|
27278
|
-
}
|
|
27339
|
+
};
|
|
27340
|
+
if (!auth) await acquireProofAuth();
|
|
27279
27341
|
console.log("\n Registering this claim with terminalhire (founder posting)...");
|
|
27280
27342
|
const sendRegistration = async (includeExpectation) => fetch(`${CLAIM_SYNC_BASE2}/api/claim/register`, {
|
|
27281
27343
|
method: "POST",
|
|
@@ -27287,6 +27349,14 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27287
27349
|
}),
|
|
27288
27350
|
signal: AbortSignal.timeout(1e4)
|
|
27289
27351
|
});
|
|
27352
|
+
const readRefusal = async (r) => {
|
|
27353
|
+
if (r.ok) return null;
|
|
27354
|
+
try {
|
|
27355
|
+
return await r.json();
|
|
27356
|
+
} catch {
|
|
27357
|
+
return null;
|
|
27358
|
+
}
|
|
27359
|
+
};
|
|
27290
27360
|
let res;
|
|
27291
27361
|
try {
|
|
27292
27362
|
res = await sendRegistration(true);
|
|
@@ -27295,23 +27365,54 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27295
27365
|
`terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
|
|
27296
27366
|
);
|
|
27297
27367
|
}
|
|
27298
|
-
let refusalBody =
|
|
27299
|
-
|
|
27368
|
+
let refusalBody = await readRefusal(res);
|
|
27369
|
+
const pushTokenRefusal = storedPushToken && res.status === 403 && (refusalBody?.error === PUSH_TOKEN_REFUSAL.INVALID || refusalBody?.error === PUSH_TOKEN_REFUSAL.LEGACY || refusalBody?.error === PUSH_TOKEN_REFUSAL.INSUFFICIENT) ? refusalBody.error : null;
|
|
27370
|
+
if (pushTokenRefusal) {
|
|
27371
|
+
if (pushTokenRefusal !== PUSH_TOKEN_REFUSAL.INSUFFICIENT) {
|
|
27372
|
+
if (pushTokenRefusal === PUSH_TOKEN_REFUSAL.LEGACY) {
|
|
27373
|
+
console.log("\n The push token on this machine was issued before terminalhire tied");
|
|
27374
|
+
console.log(" tokens to a GitHub account, so it can no longer prove who holds it.");
|
|
27375
|
+
console.log(" Nothing was revoked \u2014 the requirement changed on our side.");
|
|
27376
|
+
console.log(" Registering this claim needs only a ONE-TIME identity check, so");
|
|
27377
|
+
console.log(" falling back to browser verification.");
|
|
27378
|
+
} else {
|
|
27379
|
+
console.log("\n The push token stored on this machine is no longer valid.");
|
|
27380
|
+
console.log(" Registering this claim needs only a ONE-TIME identity check, so");
|
|
27381
|
+
console.log(" falling back to browser verification. (Re-enrolling background");
|
|
27382
|
+
console.log(" push is a separate thing you need only for repeatable reads \u2014");
|
|
27383
|
+
console.log(" fetching your granted slice and polling CI.)");
|
|
27384
|
+
}
|
|
27385
|
+
storedPushToken = null;
|
|
27386
|
+
const hadAutoMarker = Boolean(readAutoMarker());
|
|
27387
|
+
clearPushTokenEnc();
|
|
27388
|
+
clearedLocalCredential = true;
|
|
27389
|
+
if (hadAutoMarker) {
|
|
27390
|
+
clearAutoMarker();
|
|
27391
|
+
console.log("\n Background dashboard updates are now OFF \u2014 that token was the");
|
|
27392
|
+
console.log(" credential they ran on. Turn them back on any time:");
|
|
27393
|
+
console.log(" terminalhire claim --push --keep-updated");
|
|
27394
|
+
}
|
|
27395
|
+
} else {
|
|
27396
|
+
refusedForPurpose = true;
|
|
27397
|
+
console.log("\n Registering a claim takes a one-time browser check, which the");
|
|
27398
|
+
console.log(" credential stored on this machine is not for. Falling back to it now.");
|
|
27399
|
+
console.log(" It was not revoked or changed \u2014 this refusal was about which");
|
|
27400
|
+
console.log(" action the credential is for, not whether it is still good.");
|
|
27401
|
+
}
|
|
27402
|
+
await acquireProofAuth();
|
|
27300
27403
|
try {
|
|
27301
|
-
|
|
27302
|
-
} catch {
|
|
27404
|
+
res = await sendRegistration(true);
|
|
27405
|
+
} catch (err) {
|
|
27406
|
+
refuse(
|
|
27407
|
+
`terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
|
|
27408
|
+
);
|
|
27303
27409
|
}
|
|
27410
|
+
refusalBody = await readRefusal(res);
|
|
27304
27411
|
}
|
|
27305
27412
|
if (expectLogin && res.status === 422 && refusalBody?.error === "unknown field(s): expectLogin") {
|
|
27306
27413
|
try {
|
|
27307
27414
|
res = await sendRegistration(false);
|
|
27308
|
-
refusalBody =
|
|
27309
|
-
if (!res.ok) {
|
|
27310
|
-
try {
|
|
27311
|
-
refusalBody = await res.json();
|
|
27312
|
-
} catch {
|
|
27313
|
-
}
|
|
27314
|
-
}
|
|
27415
|
+
refusalBody = await readRefusal(res);
|
|
27315
27416
|
} catch (err) {
|
|
27316
27417
|
refuse(
|
|
27317
27418
|
`terminalhire is unreachable (${err instanceof Error ? err.message : String(err)}), so the posting could not be revalidated.`
|
|
@@ -27333,43 +27434,61 @@ terminalhire claim: refusing to record \u2014 ${reason}
|
|
|
27333
27434
|
if (!body || body.ok !== true) {
|
|
27334
27435
|
refuse("malformed registration response from the server.");
|
|
27335
27436
|
}
|
|
27437
|
+
const mintedToken = typeof body.pushToken === "string" && body.pushToken.length > 0 ? body.pushToken : null;
|
|
27438
|
+
if (refusedForPurpose && mintedToken) {
|
|
27439
|
+
try {
|
|
27440
|
+
await writePushTokenEnc(mintedToken);
|
|
27441
|
+
console.log("\n Your stored credential was refreshed \u2014 registering this claim");
|
|
27442
|
+
console.log(" rotated it, so the previous one is no longer valid.");
|
|
27443
|
+
} catch (err) {
|
|
27444
|
+
console.log("\n \u26A0 Registered, but the refreshed credential could not be stored here.");
|
|
27445
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
27446
|
+
for (const line of reason.split("\n")) console.log(` ${line}`);
|
|
27447
|
+
console.log(" Re-enrol before fetching a slice: terminalhire claim --push");
|
|
27448
|
+
}
|
|
27449
|
+
}
|
|
27336
27450
|
return {
|
|
27337
27451
|
claimId: typeof body.claimId === "string" ? body.claimId : null,
|
|
27338
27452
|
claimantLogin: typeof body.claimantLogin === "string" ? body.claimantLogin : null,
|
|
27339
|
-
// Existing-token auth deliberately gets no replacement from the server
|
|
27340
|
-
//
|
|
27341
|
-
//
|
|
27342
|
-
pushToken:
|
|
27453
|
+
// Existing-token auth deliberately gets no replacement from the server: reuse the
|
|
27454
|
+
// encrypted value we just authenticated with. The one exception is the rotation
|
|
27455
|
+
// above — there the stored value is the superseded one, so it must not win.
|
|
27456
|
+
pushToken: refusedForPurpose && mintedToken ? mintedToken : storedPushToken ?? mintedToken
|
|
27343
27457
|
};
|
|
27344
27458
|
}
|
|
27459
|
+
function readCredentialDisposition({
|
|
27460
|
+
markerExists,
|
|
27461
|
+
enrolledTokenExists,
|
|
27462
|
+
enrollmentRevoked = false
|
|
27463
|
+
}) {
|
|
27464
|
+
if (!markerExists) return "store";
|
|
27465
|
+
if (enrollmentRevoked) return "store-and-clear-stale-marker";
|
|
27466
|
+
if (enrolledTokenExists) return "keep-enrolled";
|
|
27467
|
+
return "store-and-clear-stale-marker";
|
|
27468
|
+
}
|
|
27345
27469
|
async function bootstrapFounderClaimEnrollment(claim, registration) {
|
|
27346
|
-
if (!(claim.amountUSD > 0)) return {
|
|
27470
|
+
if (!(claim.amountUSD > 0)) return { stored: false, reason: "free" };
|
|
27347
27471
|
if (!registration.pushToken || !registration.claimantLogin) {
|
|
27348
|
-
return {
|
|
27472
|
+
return { stored: false, reason: "token-unavailable" };
|
|
27473
|
+
}
|
|
27474
|
+
const disposition = readCredentialDisposition({
|
|
27475
|
+
markerExists: Boolean(readAutoMarker()),
|
|
27476
|
+
enrolledTokenExists: Boolean(await readPushTokenEnc().catch(() => null))
|
|
27477
|
+
});
|
|
27478
|
+
if (disposition === "keep-enrolled") {
|
|
27479
|
+
return { stored: false, reason: "kept-enrolled" };
|
|
27349
27480
|
}
|
|
27350
27481
|
try {
|
|
27351
27482
|
await writePushTokenEnc(registration.pushToken);
|
|
27352
|
-
|
|
27353
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27354
|
-
writeAutoMarker({
|
|
27355
|
-
...prior ?? {},
|
|
27356
|
-
autoConsentedAt: prior?.autoConsentedAt ?? now,
|
|
27357
|
-
version: AUTO_CONSENT_VERSION,
|
|
27358
|
-
login: registration.claimantLogin
|
|
27359
|
-
// Do not invent lastPushedAt/lastSnapshotHash here. If this is a fresh
|
|
27360
|
-
// marker the immediate existing background path must push the newly
|
|
27361
|
-
// recorded local claim; if it is an existing marker those fields remain
|
|
27362
|
-
// truthful and the changed snapshot makes the normal gate fire.
|
|
27363
|
-
});
|
|
27483
|
+
if (disposition === "store-and-clear-stale-marker") clearAutoMarker();
|
|
27364
27484
|
} catch (err) {
|
|
27365
27485
|
return {
|
|
27366
|
-
|
|
27486
|
+
stored: false,
|
|
27367
27487
|
reason: "local-write-failed",
|
|
27368
27488
|
detail: err instanceof Error ? err.message : String(err)
|
|
27369
27489
|
};
|
|
27370
27490
|
}
|
|
27371
|
-
|
|
27372
|
-
return pushed?.pushed ? { enrolled: true, reason: "ok" } : { enrolled: true, reason: pushed?.reason ?? "sync-failed" };
|
|
27491
|
+
return { stored: true, reason: "ok" };
|
|
27373
27492
|
}
|
|
27374
27493
|
async function cmdRecord(arg, flags = {}) {
|
|
27375
27494
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
@@ -27580,21 +27699,19 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
27580
27699
|
` registered with terminalhire${claim.approval.claimId ? ` (server claim ${claim.approval.claimId})` : ""}`
|
|
27581
27700
|
);
|
|
27582
27701
|
if (claim.amountUSD > 0) {
|
|
27583
|
-
if (enrollment?.
|
|
27584
|
-
console.log(" \u2713
|
|
27585
|
-
console.log("
|
|
27586
|
-
console.log("
|
|
27587
|
-
|
|
27588
|
-
|
|
27589
|
-
console.log(
|
|
27590
|
-
console.log(" Retry: terminalhire claim --push --keep-updated");
|
|
27591
|
-
console.log(" Revoke any time: terminalhire claim --push --revoke");
|
|
27702
|
+
if (enrollment?.stored) {
|
|
27703
|
+
console.log(" \u2713 credential for your granted slice + CI results stored encrypted here");
|
|
27704
|
+
console.log(" Background dashboard updates are NOT on (claiming does not enable them).");
|
|
27705
|
+
console.log(" Want them? terminalhire claim --push --keep-updated");
|
|
27706
|
+
console.log(" Remove the stored credential any time: terminalhire claim --push --revoke");
|
|
27707
|
+
} else if (enrollment?.reason === "kept-enrolled") {
|
|
27708
|
+
console.log(" \u2713 your existing enrolled credential is kept and covers this claim");
|
|
27592
27709
|
} else {
|
|
27593
|
-
console.log(" \u26A0 Claim recorded, but
|
|
27710
|
+
console.log(" \u26A0 Claim recorded, but the credential could not be stored on this machine.");
|
|
27594
27711
|
if (enrollment?.detail) {
|
|
27595
27712
|
for (const line of String(enrollment.detail).split("\n")) console.log(` ${line}`);
|
|
27596
27713
|
}
|
|
27597
|
-
console.log("
|
|
27714
|
+
console.log(" Store it before fetching your slice: terminalhire claim --push");
|
|
27598
27715
|
}
|
|
27599
27716
|
}
|
|
27600
27717
|
if (claim.approval.state === "pending") {
|
|
@@ -27726,6 +27843,17 @@ async function cmdList(active) {
|
|
|
27726
27843
|
}
|
|
27727
27844
|
await syncFounderApprovals(claims, list);
|
|
27728
27845
|
list = claims.listClaims({ active });
|
|
27846
|
+
await syncFounderVerdicts({
|
|
27847
|
+
claimsModule: claims,
|
|
27848
|
+
targets: list,
|
|
27849
|
+
readPushTokenEnc
|
|
27850
|
+
});
|
|
27851
|
+
list = claims.listClaims({ active });
|
|
27852
|
+
if (list.length === 0) {
|
|
27853
|
+
console.log(active ? "\nNo active claims." : "\nNo claims yet.");
|
|
27854
|
+
printMetric(claims.acceptedPRRate());
|
|
27855
|
+
return;
|
|
27856
|
+
}
|
|
27729
27857
|
console.log(`
|
|
27730
27858
|
${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
|
|
27731
27859
|
`);
|
|
@@ -27874,6 +28002,12 @@ async function cmdStatus(id) {
|
|
|
27874
28002
|
approvalsChecked = syncRes.approvalsChecked;
|
|
27875
28003
|
approvalsUnavailable = syncRes.approvalsUnavailable;
|
|
27876
28004
|
targets = id ? [claims.findClaim(id)].filter(Boolean) : claims.listClaims();
|
|
28005
|
+
await syncFounderVerdicts({
|
|
28006
|
+
claimsModule: claims,
|
|
28007
|
+
targets,
|
|
28008
|
+
readPushTokenEnc
|
|
28009
|
+
});
|
|
28010
|
+
targets = id ? [claims.findClaim(id)].filter(Boolean) : claims.listClaims();
|
|
27877
28011
|
console.log("\n Founder claims:");
|
|
27878
28012
|
for (const c of targets.filter((claim) => Boolean(claim.approval))) {
|
|
27879
28013
|
console.log(` ${founderClaimStanding(c, approvalsChecked)} \u2014 ${c.title}`);
|
|
@@ -28180,35 +28314,61 @@ async function ensureForkExists(repoFullName, ghUser) {
|
|
|
28180
28314
|
if (!isFork) throw new Error(`fork ${forkFullName} created but could not be verified as a fork`);
|
|
28181
28315
|
return forkFullName;
|
|
28182
28316
|
}
|
|
28317
|
+
function shouldStatePending(claim, approvalsChecked) {
|
|
28318
|
+
return Boolean(claim?.approval) && claim.approval.state === "pending" && Boolean(approvalsChecked);
|
|
28319
|
+
}
|
|
28320
|
+
function startableRow(c) {
|
|
28321
|
+
const bits = [fmtClaimAmount(c), sanitizeText(c.title)];
|
|
28322
|
+
if (c.repoFullName) bits.push(sanitizeText(c.repoFullName));
|
|
28323
|
+
return bits.join(" \xB7 ");
|
|
28324
|
+
}
|
|
28325
|
+
async function pickStartableClaim(claims, { prompt = ask, isTTY = process.stdin.isTTY } = {}) {
|
|
28326
|
+
const active = claims.listClaims({ active: true });
|
|
28327
|
+
await syncFounderApprovals(claims, active);
|
|
28328
|
+
const startable = claims.listClaims({ active: true }).filter((c) => c.state === "claimed");
|
|
28329
|
+
if (startable.length === 0) {
|
|
28330
|
+
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
28331
|
+
return null;
|
|
28332
|
+
}
|
|
28333
|
+
const interactive = Boolean(isTTY);
|
|
28334
|
+
console.log(`
|
|
28335
|
+
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
28336
|
+
`);
|
|
28337
|
+
const width = String(startable.length).length;
|
|
28338
|
+
startable.forEach((c, i) => {
|
|
28339
|
+
console.log(` ${String(i + 1).padStart(width)}) ${startableRow(c)}`);
|
|
28340
|
+
if (!interactive) console.log(` terminalhire claim start ${c.id}`);
|
|
28341
|
+
});
|
|
28342
|
+
if (!interactive) {
|
|
28343
|
+
console.log("\nRun the command under the one you want to start.");
|
|
28344
|
+
return null;
|
|
28345
|
+
}
|
|
28346
|
+
const answer = await prompt(`
|
|
28347
|
+
Which one? (1-${startable.length}, or q to quit) `);
|
|
28348
|
+
if (answer === "" || /^q(uit)?$/i.test(answer)) return null;
|
|
28349
|
+
const n = Number.parseInt(answer, 10);
|
|
28350
|
+
if (!/^\d+$/.test(answer) || !Number.isInteger(n) || n < 1 || n > startable.length) {
|
|
28351
|
+
console.log(`
|
|
28352
|
+
Nothing started \u2014 '${answer}' is not one of 1-${startable.length}.`);
|
|
28353
|
+
return null;
|
|
28354
|
+
}
|
|
28355
|
+
return startable[n - 1].id;
|
|
28356
|
+
}
|
|
28183
28357
|
async function cmdStart(id, flags = {}) {
|
|
28184
28358
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
28185
28359
|
if (!id) {
|
|
28186
|
-
|
|
28187
|
-
|
|
28188
|
-
|
|
28189
|
-
if (startable.length === 0) {
|
|
28190
|
-
console.log("No claims ready to start. Claim one first: terminalhire claim <ref>");
|
|
28191
|
-
return;
|
|
28192
|
-
}
|
|
28193
|
-
console.log(
|
|
28194
|
-
`
|
|
28195
|
-
${startable.length} claim${startable.length === 1 ? "" : "s"} ready to start:
|
|
28196
|
-
`
|
|
28197
|
-
);
|
|
28198
|
-
for (const c of startable) {
|
|
28199
|
-
console.log(` ${c.title}`);
|
|
28200
|
-
console.log(` terminalhire claim start ${c.id}`);
|
|
28201
|
-
}
|
|
28202
|
-
console.log("\nRun the command under the one you want to start.");
|
|
28203
|
-
return;
|
|
28360
|
+
const picked = await pickStartableClaim(claims);
|
|
28361
|
+
if (!picked) return;
|
|
28362
|
+
id = picked;
|
|
28204
28363
|
}
|
|
28205
28364
|
let claim = claims.findClaim(id);
|
|
28206
28365
|
if (!claim) {
|
|
28207
28366
|
console.error(`terminalhire claim: no claim with id '${id}'.`);
|
|
28208
28367
|
process.exit(1);
|
|
28209
28368
|
}
|
|
28369
|
+
let approvalsChecked = false;
|
|
28210
28370
|
if (claim.approval?.state === "pending") {
|
|
28211
|
-
await syncFounderApprovals(claims, [claim]);
|
|
28371
|
+
({ approvalsChecked } = await syncFounderApprovals(claims, [claim]));
|
|
28212
28372
|
claim = claims.findClaim(id);
|
|
28213
28373
|
}
|
|
28214
28374
|
if (claim.worktreePath) {
|
|
@@ -28229,19 +28389,14 @@ When it's done: terminalhire claim submit ${id}`);
|
|
|
28229
28389
|
}
|
|
28230
28390
|
if (claim.approval) {
|
|
28231
28391
|
console.log(`
|
|
28232
|
-
${claim.title}`);
|
|
28233
|
-
|
|
28234
|
-
|
|
28235
|
-
|
|
28236
|
-
|
|
28237
|
-
console.log("
|
|
28238
|
-
|
|
28239
|
-
|
|
28240
|
-
} else {
|
|
28241
|
-
console.log("\n No fork was attempted \u2014 founder postings are never forked or cloned. Your");
|
|
28242
|
-
console.log(" work slice is delivered through terminalhire, and your patch goes back the");
|
|
28243
|
-
console.log(" same way.");
|
|
28244
|
-
}
|
|
28392
|
+
${sanitizeText(claim.title)}`);
|
|
28393
|
+
console.log("\n No fork was attempted \u2014 founder postings are never forked or cloned. Your");
|
|
28394
|
+
console.log(" work slice is delivered through terminalhire, and your patch goes back the");
|
|
28395
|
+
console.log(" same way.");
|
|
28396
|
+
if (shouldStatePending(claim, approvalsChecked)) {
|
|
28397
|
+
console.log("\n Access is pending \u2014 the founder has not approved your claim yet.");
|
|
28398
|
+
}
|
|
28399
|
+
printNextSteps([claim]);
|
|
28245
28400
|
return;
|
|
28246
28401
|
}
|
|
28247
28402
|
if (flags.here) {
|
|
@@ -28491,10 +28646,10 @@ var CLAIM_EVENT_LABEL = {
|
|
|
28491
28646
|
rejected: "the founder rejected"
|
|
28492
28647
|
};
|
|
28493
28648
|
var LINE_BREAKS = /\r\n|[\r\n\v\f\u0085\u2028\u2029]/;
|
|
28494
|
-
var
|
|
28649
|
+
var CONTROL_CHARS2 = /[\u0000-\u001F\u007F-\u009F]/g;
|
|
28495
28650
|
function terminalSafeLines(raw) {
|
|
28496
28651
|
if (typeof raw !== "string" || raw === "") return [];
|
|
28497
|
-
return raw.split(LINE_BREAKS).map((l) => l.replace(
|
|
28652
|
+
return raw.split(LINE_BREAKS).map((l) => l.replace(CONTROL_CHARS2, ""));
|
|
28498
28653
|
}
|
|
28499
28654
|
function terminalSafeInline(raw) {
|
|
28500
28655
|
return terminalSafeLines(raw).join(" ");
|
|
@@ -28577,6 +28732,26 @@ function requireFounderLoopClaim(claims, id, verb) {
|
|
|
28577
28732
|
}
|
|
28578
28733
|
return claim;
|
|
28579
28734
|
}
|
|
28735
|
+
function retireRevokedReadCredential(status, body, what) {
|
|
28736
|
+
if (status !== 403 || body?.error !== PUSH_TOKEN_REFUSAL.INVALID) return false;
|
|
28737
|
+
const hadMarker = Boolean(readAutoMarker());
|
|
28738
|
+
clearPushTokenEnc();
|
|
28739
|
+
if (hadMarker) clearAutoMarker();
|
|
28740
|
+
console.error(
|
|
28741
|
+
`
|
|
28742
|
+
terminalhire claim: ${what} needs the stored credential, and the server says it has been revoked.`
|
|
28743
|
+
);
|
|
28744
|
+
console.error(" It has been removed from this machine, so nothing keeps retrying a");
|
|
28745
|
+
console.error(" credential that cannot work.");
|
|
28746
|
+
if (hadMarker) {
|
|
28747
|
+
console.error(" Background dashboard updates are now OFF \u2014 they ran on that credential.");
|
|
28748
|
+
}
|
|
28749
|
+
console.error(" Get a working one (browser verification): terminalhire claim --push");
|
|
28750
|
+
if (hadMarker) {
|
|
28751
|
+
console.error(" Add --keep-updated to that command to turn background updates back on.");
|
|
28752
|
+
}
|
|
28753
|
+
return true;
|
|
28754
|
+
}
|
|
28580
28755
|
async function requireReadPushToken(what) {
|
|
28581
28756
|
let stored = null;
|
|
28582
28757
|
try {
|
|
@@ -28620,7 +28795,9 @@ async function cmdSlice(id, flags = {}) {
|
|
|
28620
28795
|
} catch {
|
|
28621
28796
|
}
|
|
28622
28797
|
if (!res.ok) {
|
|
28623
|
-
|
|
28798
|
+
if (!retireRevokedReadCredential(res.status, body, "fetching your granted slice")) {
|
|
28799
|
+
console.error(`terminalhire claim: ${renderServerRefusal(res.status, body)}`);
|
|
28800
|
+
}
|
|
28624
28801
|
process.exit(1);
|
|
28625
28802
|
}
|
|
28626
28803
|
if (!body || body.ok !== true || !Array.isArray(body.files)) {
|
|
@@ -28748,7 +28925,9 @@ async function cmdRuns(id, flags = {}) {
|
|
|
28748
28925
|
process.exit(1);
|
|
28749
28926
|
}
|
|
28750
28927
|
if (r.kind === "refusal") {
|
|
28751
|
-
|
|
28928
|
+
if (!retireRevokedReadCredential(r.status, r.body, "reading CI results")) {
|
|
28929
|
+
console.error(`terminalhire claim: ${renderServerRefusal(r.status, r.body)}`);
|
|
28930
|
+
}
|
|
28752
28931
|
process.exit(1);
|
|
28753
28932
|
}
|
|
28754
28933
|
console.log(`
|
|
@@ -29527,10 +29706,11 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
29527
29706
|
}
|
|
29528
29707
|
let deleteToken = null;
|
|
29529
29708
|
let pushToken = null;
|
|
29709
|
+
let syncBody = null;
|
|
29530
29710
|
try {
|
|
29531
|
-
|
|
29532
|
-
deleteToken =
|
|
29533
|
-
pushToken =
|
|
29711
|
+
syncBody = await res.json();
|
|
29712
|
+
deleteToken = syncBody?.deleteToken || null;
|
|
29713
|
+
pushToken = syncBody?.pushToken || null;
|
|
29534
29714
|
} catch {
|
|
29535
29715
|
}
|
|
29536
29716
|
writeClaimPushMarker({
|
|
@@ -29540,23 +29720,55 @@ async function cmdPush({ keepUpdated = false } = {}) {
|
|
|
29540
29720
|
lastPushedAt: consentedAt,
|
|
29541
29721
|
lastSnapshotHash: computeSnapshotHash(pushed)
|
|
29542
29722
|
});
|
|
29543
|
-
if (
|
|
29723
|
+
if (pushToken) {
|
|
29544
29724
|
try {
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
|
|
29554
|
-
|
|
29725
|
+
if (autoConsent) {
|
|
29726
|
+
await writePushTokenEnc(pushToken);
|
|
29727
|
+
writeAutoMarker({
|
|
29728
|
+
autoConsentedAt: consentedAt,
|
|
29729
|
+
version: AUTO_CONSENT_VERSION,
|
|
29730
|
+
login,
|
|
29731
|
+
lastPushedAt: consentedAt,
|
|
29732
|
+
lastSnapshotHash: computeSnapshotHash(pushed)
|
|
29733
|
+
});
|
|
29734
|
+
console.log("\n \u2713 Background updates enabled \u2014 your dashboard will stay current");
|
|
29735
|
+
console.log(" (at most once/day). Stop any time: terminalhire claim --push --revoke");
|
|
29736
|
+
} else {
|
|
29737
|
+
const backgroundPushRevoked = syncBody?.[SYNC_BACKGROUND_PUSH_ACTIVE_FIELD] === false;
|
|
29738
|
+
const disposition = readCredentialDisposition({
|
|
29739
|
+
markerExists: Boolean(readAutoMarker()),
|
|
29740
|
+
enrolledTokenExists: Boolean(await readPushTokenEnc()),
|
|
29741
|
+
enrollmentRevoked: backgroundPushRevoked
|
|
29742
|
+
});
|
|
29743
|
+
if (disposition === "store") {
|
|
29744
|
+
await writePushTokenEnc(pushToken);
|
|
29745
|
+
console.log(
|
|
29746
|
+
"\n \u2713 Stored the credential for fetching your granted slice and CI results."
|
|
29747
|
+
);
|
|
29748
|
+
} else if (disposition === "keep-enrolled") {
|
|
29749
|
+
console.log("\n \u2713 Background updates are already active; keeping the credential");
|
|
29750
|
+
console.log(" they run on. Nothing about them changed.");
|
|
29751
|
+
} else {
|
|
29752
|
+
await writePushTokenEnc(pushToken);
|
|
29753
|
+
clearAutoMarker();
|
|
29754
|
+
console.log(
|
|
29755
|
+
"\n \u2713 Stored the credential for fetching your granted slice and CI results."
|
|
29756
|
+
);
|
|
29757
|
+
console.log(
|
|
29758
|
+
backgroundPushRevoked ? " Background dashboard updates are OFF \u2014 that enrolment was stopped or revoked." : " Background dashboard updates are OFF (their credential was gone)."
|
|
29759
|
+
);
|
|
29760
|
+
console.log(" Turn them back on any time: terminalhire claim --push --keep-updated");
|
|
29761
|
+
}
|
|
29762
|
+
}
|
|
29555
29763
|
} catch (err) {
|
|
29556
|
-
console.log(
|
|
29764
|
+
console.log(
|
|
29765
|
+
autoConsent ? "\n \u2713 Pushed, but could not enable background updates on this machine." : "\n \u2713 Pushed, but could not store the credential for slice fetch / CI polling."
|
|
29766
|
+
);
|
|
29557
29767
|
const reason = err instanceof Error ? err.message : String(err);
|
|
29558
29768
|
for (const line of reason.split("\n")) console.log(` ${line}`);
|
|
29559
|
-
console.log(
|
|
29769
|
+
console.log(
|
|
29770
|
+
autoConsent ? " Re-run `terminalhire claim --push --keep-updated` to retry." : " Re-run `terminalhire claim --push` to retry."
|
|
29771
|
+
);
|
|
29560
29772
|
}
|
|
29561
29773
|
} else if (backgroundEnableFailed(autoConsent, pushToken)) {
|
|
29562
29774
|
console.log(
|
|
@@ -29844,8 +30056,10 @@ async function run() {
|
|
|
29844
30056
|
export {
|
|
29845
30057
|
AI_DISCLOSURE_NOTE,
|
|
29846
30058
|
CLAIM_CONSENT_VERSION,
|
|
30059
|
+
PUSH_TOKEN_REFUSAL,
|
|
29847
30060
|
REVISE_RECOVERY_STATES,
|
|
29848
30061
|
SUBMIT_ACCEPTS,
|
|
30062
|
+
SYNC_BACKGROUND_PUSH_ACTIVE_FIELD,
|
|
29849
30063
|
backgroundEnableFailed,
|
|
29850
30064
|
buildAssignmentComment,
|
|
29851
30065
|
buildPatchSubmission,
|
|
@@ -29853,6 +30067,7 @@ export {
|
|
|
29853
30067
|
buildStandDownComment,
|
|
29854
30068
|
buildSubmitBody,
|
|
29855
30069
|
claimUpdatePatch,
|
|
30070
|
+
cmdPush,
|
|
29856
30071
|
cmdRecord,
|
|
29857
30072
|
cmdRuns,
|
|
29858
30073
|
cmdSlice,
|
|
@@ -29879,7 +30094,9 @@ export {
|
|
|
29879
30094
|
normalizeIntent,
|
|
29880
30095
|
pickBodySource,
|
|
29881
30096
|
pickExistingPr,
|
|
30097
|
+
pickStartableClaim,
|
|
29882
30098
|
printNextSteps,
|
|
30099
|
+
readCredentialDisposition,
|
|
29883
30100
|
renderClaimHistory,
|
|
29884
30101
|
renderRunView,
|
|
29885
30102
|
renderServerRefusal,
|
|
@@ -29893,6 +30110,7 @@ export {
|
|
|
29893
30110
|
selectCompetingPrs,
|
|
29894
30111
|
selectPushRemote,
|
|
29895
30112
|
shouldRequestAssignment,
|
|
30113
|
+
shouldStatePending,
|
|
29896
30114
|
sliceWorkDirFor,
|
|
29897
30115
|
stakeDecision,
|
|
29898
30116
|
startBranchFor,
|