halfcycle 0.5.2 → 0.5.3
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/.claude-plugin/plugin.json +1 -1
- package/bin/bin.bundle.mjs +262 -3
- package/dist/cli.js +136 -100
- package/dist/cli.js.map +3 -3
- package/dist/index.js +72 -40
- package/dist/index.js.map +3 -3
- package/dist/review.d.ts +3 -3
- package/dist/review.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halfcycle",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
|
|
5
5
|
"commands": [
|
|
6
6
|
{
|
package/bin/bin.bundle.mjs
CHANGED
|
@@ -18103,6 +18103,30 @@ var phaseReviewViewSchema = external_exports.discriminatedUnion("state", [
|
|
|
18103
18103
|
phaseReviewOpenSchema,
|
|
18104
18104
|
phaseReviewClosedSchema
|
|
18105
18105
|
]);
|
|
18106
|
+
function reviewPartAwaitingAnswer(part) {
|
|
18107
|
+
return part.questions.some((q) => q.agentAnswer === void 0);
|
|
18108
|
+
}
|
|
18109
|
+
function reviewPartSentBackIn(part, round) {
|
|
18110
|
+
return part.sentBack.some((s) => s.round === round);
|
|
18111
|
+
}
|
|
18112
|
+
function shownReviewEntryOf(view, shownAt) {
|
|
18113
|
+
const parts = view.parts.map((p) => ({
|
|
18114
|
+
part: p.part,
|
|
18115
|
+
name: p.name,
|
|
18116
|
+
built: p.built,
|
|
18117
|
+
decidable: p.decidable,
|
|
18118
|
+
awaitingAnswer: reviewPartAwaitingAnswer(p),
|
|
18119
|
+
sentBack: reviewPartSentBackIn(p, view.round)
|
|
18120
|
+
}));
|
|
18121
|
+
return {
|
|
18122
|
+
phase: view.phase,
|
|
18123
|
+
number: view.number,
|
|
18124
|
+
round: view.round,
|
|
18125
|
+
reviewVersion: view.reviewVersion,
|
|
18126
|
+
shownAt,
|
|
18127
|
+
parts
|
|
18128
|
+
};
|
|
18129
|
+
}
|
|
18106
18130
|
|
|
18107
18131
|
// ../events/dist/client-version.js
|
|
18108
18132
|
var CLIENT_VERSION_HEADER = "x-halfcycle-client";
|
|
@@ -18563,6 +18587,42 @@ var ownerWaitDeclarationSchema = external_exports.object({
|
|
|
18563
18587
|
var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
|
|
18564
18588
|
var OWNER_WAIT_TOO_MANY = `This project already has ${String(OWNER_WAIT_ADDITIONS_MAX)} things added to what waits for the owner. Ask the owner to take one off on their settings page in the console first; it cannot be done by typing in chat.`;
|
|
18565
18589
|
|
|
18590
|
+
// ../events/dist/owner-wait-answer.js
|
|
18591
|
+
var OWNER_WAIT_SHORT_ID_LENGTH = 8;
|
|
18592
|
+
var ownerWaitShortIdSchema = external_exports.string().regex(/^[0-9a-f]{8}$/);
|
|
18593
|
+
function ownerWaitShortIdOf(declarationId) {
|
|
18594
|
+
return declarationId.slice(0, OWNER_WAIT_SHORT_ID_LENGTH).toLowerCase();
|
|
18595
|
+
}
|
|
18596
|
+
var OWNER_WAIT_ANSWER_ACTS = ["approve", "decline"];
|
|
18597
|
+
var ownerWaitAnswerActSchema = external_exports.enum(OWNER_WAIT_ANSWER_ACTS);
|
|
18598
|
+
function ownerWaitApproveLineOf(declarationId) {
|
|
18599
|
+
return `approve wait ${ownerWaitShortIdOf(declarationId)}`;
|
|
18600
|
+
}
|
|
18601
|
+
function ownerWaitDeclineLineOf(declarationId) {
|
|
18602
|
+
return `decline wait ${ownerWaitShortIdOf(declarationId)}: <why>`;
|
|
18603
|
+
}
|
|
18604
|
+
var ownerWaitChatRequestSchema = external_exports.discriminatedUnion("act", [
|
|
18605
|
+
external_exports.object({ act: external_exports.literal("approve"), wait: ownerWaitShortIdSchema }).strict(),
|
|
18606
|
+
external_exports.object({ act: external_exports.literal("decline"), wait: ownerWaitShortIdSchema, note: scopeNoteWireSchema }).strict()
|
|
18607
|
+
]);
|
|
18608
|
+
function bareOutcome3(outcome) {
|
|
18609
|
+
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
18610
|
+
}
|
|
18611
|
+
var ownerWaitChatResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
18612
|
+
external_exports.object({
|
|
18613
|
+
outcome: external_exports.literal("recorded"),
|
|
18614
|
+
act: ownerWaitAnswerActSchema,
|
|
18615
|
+
question: external_exports.string().min(1)
|
|
18616
|
+
}).strict(),
|
|
18617
|
+
external_exports.object({ outcome: external_exports.literal("refused"), problems: external_exports.array(systemDescriptionProblemSchema).min(1) }).strict(),
|
|
18618
|
+
bareOutcome3("not-owner"),
|
|
18619
|
+
bareOutcome3("no-owner"),
|
|
18620
|
+
bareOutcome3("not-found"),
|
|
18621
|
+
bareOutcome3("ambiguous"),
|
|
18622
|
+
bareOutcome3("not-offered"),
|
|
18623
|
+
bareOutcome3("unavailable")
|
|
18624
|
+
]);
|
|
18625
|
+
|
|
18566
18626
|
// ../events/dist/pacing.js
|
|
18567
18627
|
var CLAUDE_PLANS = Object.freeze([
|
|
18568
18628
|
Object.freeze({ id: "pro", words: "Pro", startingAgents: 2 }),
|
|
@@ -18765,6 +18825,8 @@ var autonomyActivityRowSchema = external_exports.object({
|
|
|
18765
18825
|
// whether the agent says it can be undone
|
|
18766
18826
|
how: statementText(STATEMENT_MAX).optional(),
|
|
18767
18827
|
// how to undo it, when it can be
|
|
18828
|
+
wait: ownerWaitShortIdSchema.optional(),
|
|
18829
|
+
// the short id the owner types to answer a waiting decision
|
|
18768
18830
|
at: utcIso8601
|
|
18769
18831
|
// when it happened
|
|
18770
18832
|
}).strict().superRefine((row, ctx) => {
|
|
@@ -18791,6 +18853,9 @@ var autonomyActivityRowSchema = external_exports.object({
|
|
|
18791
18853
|
});
|
|
18792
18854
|
}
|
|
18793
18855
|
}
|
|
18856
|
+
if (row.kind !== "waiting" && row.wait !== void 0) {
|
|
18857
|
+
ctx.addIssue({ code: "custom", message: "only a waiting decision is answered by typing", path: ["wait"] });
|
|
18858
|
+
}
|
|
18794
18859
|
if (row.kind === "waiting" && (row.undoable !== void 0 || row.how !== void 0)) {
|
|
18795
18860
|
ctx.addIssue({ code: "custom", message: "a waiting decision has not been made yet", path: ["undoable"] });
|
|
18796
18861
|
}
|
|
@@ -31287,6 +31352,14 @@ function partShape(act, head, words) {
|
|
|
31287
31352
|
return null;
|
|
31288
31353
|
return { kind: "part", act, reference, part, words };
|
|
31289
31354
|
}
|
|
31355
|
+
function bulkAcceptTargetOf(reference, record2) {
|
|
31356
|
+
const phases = /* @__PURE__ */ new Map();
|
|
31357
|
+
for (const shown2 of [...record2.reviews, ...record2.entries]) {
|
|
31358
|
+
if (!phases.has(shown2.phase))
|
|
31359
|
+
phases.set(shown2.phase, { phase: shown2.phase, number: shown2.number });
|
|
31360
|
+
}
|
|
31361
|
+
return resolvePhaseReference(reference, [...phases.values()]);
|
|
31362
|
+
}
|
|
31290
31363
|
function matchReviewPhraseShape(shape, reviews) {
|
|
31291
31364
|
const resolved = resolvePhaseReference(shape.reference, reviews);
|
|
31292
31365
|
const act = shape.kind === "phase" ? "accept" : shape.kind === "reject" ? "reject" : shape.act;
|
|
@@ -31377,6 +31450,28 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
31377
31450
|
}
|
|
31378
31451
|
}
|
|
31379
31452
|
|
|
31453
|
+
// dist/hooks/wait-phrase.js
|
|
31454
|
+
var APPROVE = /^approve wait ([0-9a-f]{8})$/i;
|
|
31455
|
+
var DECLINE = /^decline wait ([0-9a-f]{8})(?:\s*:(.*))?$/is;
|
|
31456
|
+
function waitPhraseShape(prompt) {
|
|
31457
|
+
if (typeof prompt !== "string")
|
|
31458
|
+
return null;
|
|
31459
|
+
const line2 = prompt.trim();
|
|
31460
|
+
if (LINE_BREAK.test(line2))
|
|
31461
|
+
return null;
|
|
31462
|
+
const approve = APPROVE.exec(line2);
|
|
31463
|
+
if (approve !== null)
|
|
31464
|
+
return shapeOf("approve", approve[1], null);
|
|
31465
|
+
const decline = DECLINE.exec(line2);
|
|
31466
|
+
if (decline !== null)
|
|
31467
|
+
return shapeOf("decline", decline[1], decline[2] === void 0 ? null : decline[2].trim());
|
|
31468
|
+
return null;
|
|
31469
|
+
}
|
|
31470
|
+
function shapeOf(act, typed, words) {
|
|
31471
|
+
const wait = typed.toLowerCase();
|
|
31472
|
+
return ownerWaitShortIdSchema.safeParse(wait).success ? { act, wait, words } : null;
|
|
31473
|
+
}
|
|
31474
|
+
|
|
31380
31475
|
// dist/hooks/user-prompt-submit-lines.js
|
|
31381
31476
|
var TELL_THE_PERSON = "Tell the person.";
|
|
31382
31477
|
function line(text) {
|
|
@@ -31744,6 +31839,69 @@ function codexReviewLine(act) {
|
|
|
31744
31839
|
function codexAutonomyLine() {
|
|
31745
31840
|
return codexLine(`${AUTONOMY_NOTHING_CHANGED}: ${CODEX_TYPING_NEVER_COUNTS}. Turn it on or off with "${AUTONOMY_A1_TITLE}" in the console's settings.`);
|
|
31746
31841
|
}
|
|
31842
|
+
function waitTypedLine(context) {
|
|
31843
|
+
return context.act === "approve" ? ownerWaitApproveLineOf(context.wait) : ownerWaitDeclineLineOf(context.wait);
|
|
31844
|
+
}
|
|
31845
|
+
function waitRecordedLine(act, question) {
|
|
31846
|
+
return act === "approve" ? line(`Recorded in your session: you approved "${question}". The agent can go ahead with it.`) : line(`Recorded in your session: you declined "${question}", with your words. The agent must not go ahead with it.`);
|
|
31847
|
+
}
|
|
31848
|
+
function waitNotAttendedLine() {
|
|
31849
|
+
return line(`${NOTHING_RECORDED}: an answer counts only when a person types it into an open session.`);
|
|
31850
|
+
}
|
|
31851
|
+
function waitNotSignedInLine() {
|
|
31852
|
+
return line(`${NOTHING_RECORDED}: this project is not signed in on this computer. ${HOOK_REMEDY}`);
|
|
31853
|
+
}
|
|
31854
|
+
function waitNotOwnerLine() {
|
|
31855
|
+
return line(`${NOTHING_RECORDED}: only the project's owner can answer a decision that waits for them.`);
|
|
31856
|
+
}
|
|
31857
|
+
function waitNoOwnerLine() {
|
|
31858
|
+
return line(`${NOTHING_RECORDED}: this project has no owner, so nobody can answer its decisions.`);
|
|
31859
|
+
}
|
|
31860
|
+
function waitNotFoundLine(context) {
|
|
31861
|
+
return line(`${NOTHING_RECORDED}: no decision waiting for you has the id ${context.wait}. Type the line the agent gave you with its question, such as ${waitTypedLine(context)}.`);
|
|
31862
|
+
}
|
|
31863
|
+
function waitAmbiguousLine(context) {
|
|
31864
|
+
return line(`${NOTHING_RECORDED}: more than one decision waiting for you starts with ${context.wait}. Ask the agent to put the question to you again.`);
|
|
31865
|
+
}
|
|
31866
|
+
function waitNoWordsLine(context) {
|
|
31867
|
+
return line(`${NOTHING_RECORDED}: a decline needs your reason after the colon, as in ${ownerWaitDeclineLineOf(context.wait)}.`);
|
|
31868
|
+
}
|
|
31869
|
+
function waitRefusedLine() {
|
|
31870
|
+
return line(`${NOTHING_RECORDED}: Halfcycle did not accept the words. Reword them in plain words and type the line again.`);
|
|
31871
|
+
}
|
|
31872
|
+
function waitSwitchedOffLine() {
|
|
31873
|
+
return line(`${NOTHING_RECORDED}. ${TYPED_APPROVAL_OFF_SENTENCE}`);
|
|
31874
|
+
}
|
|
31875
|
+
function waitCannotReachLine() {
|
|
31876
|
+
return line(`${NOTHING_RECORDED}: Halfcycle could not be reached just now. Type the line again in a minute.`);
|
|
31877
|
+
}
|
|
31878
|
+
function codexWaitLine(context) {
|
|
31879
|
+
return codexLine(`${NOTHING_RECORDED}: ${CODEX_TYPING_NEVER_COUNTS}. To answer, type ${waitTypedLine(context)} in a Claude Code session on this project.`);
|
|
31880
|
+
}
|
|
31881
|
+
function waitLineForAnswer(context, answer) {
|
|
31882
|
+
switch (answer.outcome) {
|
|
31883
|
+
case "recorded":
|
|
31884
|
+
return waitRecordedLine(answer.act, answer.question);
|
|
31885
|
+
case "refused":
|
|
31886
|
+
return waitRefusedLine();
|
|
31887
|
+
case "not-owner":
|
|
31888
|
+
return waitNotOwnerLine();
|
|
31889
|
+
case "no-owner":
|
|
31890
|
+
return waitNoOwnerLine();
|
|
31891
|
+
case "not-found":
|
|
31892
|
+
return waitNotFoundLine(context);
|
|
31893
|
+
case "ambiguous":
|
|
31894
|
+
return waitAmbiguousLine(context);
|
|
31895
|
+
case "not-offered":
|
|
31896
|
+
return waitSwitchedOffLine();
|
|
31897
|
+
case "unavailable":
|
|
31898
|
+
return waitCannotReachLine();
|
|
31899
|
+
default: {
|
|
31900
|
+
const unreachable = answer;
|
|
31901
|
+
return unreachable;
|
|
31902
|
+
}
|
|
31903
|
+
}
|
|
31904
|
+
}
|
|
31747
31905
|
|
|
31748
31906
|
// dist/hooks/user-prompt-submit.js
|
|
31749
31907
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
@@ -31768,6 +31926,7 @@ function agentOf(opts) {
|
|
|
31768
31926
|
async function decideLine(opts) {
|
|
31769
31927
|
let act = null;
|
|
31770
31928
|
let autonomyAct = null;
|
|
31929
|
+
let waitRecognised = false;
|
|
31771
31930
|
const review = { recognised: false };
|
|
31772
31931
|
try {
|
|
31773
31932
|
const prompt = promptOf(opts.input);
|
|
@@ -31778,6 +31937,12 @@ async function decideLine(opts) {
|
|
|
31778
31937
|
if (autonomyShape !== null) {
|
|
31779
31938
|
autonomyAct = autonomyShape;
|
|
31780
31939
|
return `${await decideAutonomy(autonomyShape, opts)}
|
|
31940
|
+
`;
|
|
31941
|
+
}
|
|
31942
|
+
const waitShape = waitPhraseShape(prompt);
|
|
31943
|
+
if (waitShape !== null) {
|
|
31944
|
+
waitRecognised = true;
|
|
31945
|
+
return `${await decideWait(waitShape, opts)}
|
|
31781
31946
|
`;
|
|
31782
31947
|
}
|
|
31783
31948
|
const reviewShape = reviewPhraseShape(prompt);
|
|
@@ -31795,6 +31960,9 @@ async function decideLine(opts) {
|
|
|
31795
31960
|
} catch {
|
|
31796
31961
|
if (autonomyAct !== null)
|
|
31797
31962
|
return `${autonomyCannotReachLine()}
|
|
31963
|
+
`;
|
|
31964
|
+
if (waitRecognised)
|
|
31965
|
+
return `${waitCannotReachLine()}
|
|
31798
31966
|
`;
|
|
31799
31967
|
if (review.recognised)
|
|
31800
31968
|
return `${reviewCannotReachLine()}
|
|
@@ -31840,6 +32008,57 @@ async function decideAutonomy(act, opts) {
|
|
|
31840
32008
|
const { guardServiceToken, guardEngagementId } = read.config;
|
|
31841
32009
|
return postAutonomy(act, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
31842
32010
|
}
|
|
32011
|
+
async function decideWait(shape, opts) {
|
|
32012
|
+
const env = opts.env ?? process.env;
|
|
32013
|
+
const context = { act: shape.act, wait: shape.wait };
|
|
32014
|
+
const typed = whoTyped(opts);
|
|
32015
|
+
if (typed === "cannot-tell")
|
|
32016
|
+
return codexWaitLine(context);
|
|
32017
|
+
if (typed === "not-attended")
|
|
32018
|
+
return waitNotAttendedLine();
|
|
32019
|
+
const read = readConfig(env);
|
|
32020
|
+
if (read.config === null) {
|
|
32021
|
+
return readNeverConfiguredReason(env) === "not-this-account" ? waitNotOwnerLine() : waitNotSignedInLine();
|
|
32022
|
+
}
|
|
32023
|
+
let candidate = { act: "approve", wait: shape.wait };
|
|
32024
|
+
if (shape.act === "decline") {
|
|
32025
|
+
const words = shape.words ?? "";
|
|
32026
|
+
if (words === "")
|
|
32027
|
+
return waitNoWordsLine(context);
|
|
32028
|
+
if (!scopeNoteWireSchema.safeParse(words).success)
|
|
32029
|
+
return reviewWordsTooLongLine();
|
|
32030
|
+
if (looksLikeSecret(words))
|
|
32031
|
+
return reviewWordsLookSecretLine();
|
|
32032
|
+
candidate = { act: "decline", wait: shape.wait, note: words };
|
|
32033
|
+
}
|
|
32034
|
+
const request = ownerWaitChatRequestSchema.parse(candidate);
|
|
32035
|
+
const { guardServiceToken, guardEngagementId } = read.config;
|
|
32036
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(guardEngagementId)}/owner-waits/chat`;
|
|
32037
|
+
const controller = new AbortController();
|
|
32038
|
+
const timer = setTimeout(() => controller.abort(), CHAT_REQUEST_TIMEOUT_MS);
|
|
32039
|
+
try {
|
|
32040
|
+
const response = await (opts.fetchFn ?? globalThis.fetch)(url2, {
|
|
32041
|
+
method: "POST",
|
|
32042
|
+
headers: { Authorization: `Bearer ${guardServiceToken}`, "Content-Type": "application/json" },
|
|
32043
|
+
body: JSON.stringify(request),
|
|
32044
|
+
signal: controller.signal
|
|
32045
|
+
});
|
|
32046
|
+
if (response.status === 401)
|
|
32047
|
+
return waitNotSignedInLine();
|
|
32048
|
+
if (response.status === 429 || response.status >= 500)
|
|
32049
|
+
return waitCannotReachLine();
|
|
32050
|
+
const parsed = ownerWaitChatResultSchema.safeParse(await response.json());
|
|
32051
|
+
if (!parsed.success)
|
|
32052
|
+
return waitCannotReachLine();
|
|
32053
|
+
if (parsed.data.outcome === "recorded" && response.status !== 200)
|
|
32054
|
+
return waitCannotReachLine();
|
|
32055
|
+
return waitLineForAnswer(context, parsed.data);
|
|
32056
|
+
} catch {
|
|
32057
|
+
return waitCannotReachLine();
|
|
32058
|
+
} finally {
|
|
32059
|
+
clearTimeout(timer);
|
|
32060
|
+
}
|
|
32061
|
+
}
|
|
31843
32062
|
async function postAutonomy(act, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
31844
32063
|
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/autonomy/chat`;
|
|
31845
32064
|
const controller = new AbortController();
|
|
@@ -31873,8 +32092,12 @@ async function decideReview(shape, opts, progress) {
|
|
|
31873
32092
|
const read2 = readConfig(env);
|
|
31874
32093
|
if (read2.config === null)
|
|
31875
32094
|
return "";
|
|
31876
|
-
const
|
|
31877
|
-
|
|
32095
|
+
const { guardEngagementId: engagementId, guardServiceToken: credential } = read2.config;
|
|
32096
|
+
const record2 = readShownRecord(engagementId, opts.home);
|
|
32097
|
+
const listed = matchReviewPhraseShape(shape, record2.reviews);
|
|
32098
|
+
const fresh = listed.kind === "accept" && listedRecently(listed.entry.shownAt, (opts.now ?? Date.now)());
|
|
32099
|
+
const target = listed.kind === "ambiguous" || fresh ? null : bulkAcceptTargetOf(shape.reference, record2);
|
|
32100
|
+
if (target?.kind === "none")
|
|
31878
32101
|
return "";
|
|
31879
32102
|
progress.recognised = true;
|
|
31880
32103
|
const typed2 = whoTyped(opts);
|
|
@@ -31882,7 +32105,11 @@ async function decideReview(shape, opts, progress) {
|
|
|
31882
32105
|
return codexReviewLine("accept");
|
|
31883
32106
|
if (typed2 === "not-attended")
|
|
31884
32107
|
return reviewNotAttendedLine();
|
|
31885
|
-
|
|
32108
|
+
if (target === null)
|
|
32109
|
+
return checkAndSendReview(listed, engagementId, credential, env, opts);
|
|
32110
|
+
if (target.kind === "ambiguous")
|
|
32111
|
+
return reviewAmbiguousLine(target.entries.map((e) => e.phase));
|
|
32112
|
+
return readThenAccept(shape, target.entry.phase, engagementId, credential, env, opts);
|
|
31886
32113
|
}
|
|
31887
32114
|
progress.recognised = true;
|
|
31888
32115
|
const typed = whoTyped(opts);
|
|
@@ -31897,6 +32124,38 @@ async function decideReview(shape, opts, progress) {
|
|
|
31897
32124
|
const match = matchReviewPhraseShape(shape, readShownRecord(read.config.guardEngagementId, opts.home).reviews);
|
|
31898
32125
|
return checkAndSendReview(match, read.config.guardEngagementId, read.config.guardServiceToken, env, opts);
|
|
31899
32126
|
}
|
|
32127
|
+
var REVIEW_READ_TIMEOUT_MS = 3e3;
|
|
32128
|
+
async function readThenAccept(shape, phase, engagementId, credential, env, opts) {
|
|
32129
|
+
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
32130
|
+
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/phases/${encodeURIComponent(phase)}/review`;
|
|
32131
|
+
const controller = new AbortController();
|
|
32132
|
+
const timer = setTimeout(() => controller.abort(), REVIEW_READ_TIMEOUT_MS);
|
|
32133
|
+
let raw;
|
|
32134
|
+
try {
|
|
32135
|
+
const response = await fetchFn(url2, {
|
|
32136
|
+
method: "GET",
|
|
32137
|
+
headers: { Authorization: `Bearer ${credential}` },
|
|
32138
|
+
signal: controller.signal
|
|
32139
|
+
});
|
|
32140
|
+
if (response.status === 401)
|
|
32141
|
+
return reviewNotSignedInLine();
|
|
32142
|
+
if (response.status !== 200)
|
|
32143
|
+
return reviewCannotReachLine();
|
|
32144
|
+
raw = await response.json();
|
|
32145
|
+
} catch {
|
|
32146
|
+
return reviewCannotReachLine();
|
|
32147
|
+
} finally {
|
|
32148
|
+
clearTimeout(timer);
|
|
32149
|
+
}
|
|
32150
|
+
const parsed = phaseReviewViewSchema.safeParse(raw);
|
|
32151
|
+
if (!parsed.success)
|
|
32152
|
+
return reviewCannotReachLine();
|
|
32153
|
+
const view = parsed.data;
|
|
32154
|
+
if (view.state !== "open")
|
|
32155
|
+
return reviewNoReviewLine(phase);
|
|
32156
|
+
const entry = shownReviewEntryOf(view, new Date((opts.now ?? Date.now)()).toISOString());
|
|
32157
|
+
return checkAndSendReview(matchReviewPhraseShape(shape, [entry]), engagementId, credential, env, opts);
|
|
32158
|
+
}
|
|
31900
32159
|
async function checkAndSendReview(match, engagementId, credential, env, opts) {
|
|
31901
32160
|
if (match.kind === "nothing" || match.kind === "show-first")
|
|
31902
32161
|
return reviewShowFirstLine();
|