halfcycle 0.5.1 → 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 +332 -33
- package/dist/cli.js +285 -104
- package/dist/cli.js.map +3 -3
- package/dist/index.js +221 -44
- package/dist/index.js.map +3 -3
- package/dist/install-plan.d.ts.map +1 -1
- package/dist/install.d.ts +2 -0
- package/dist/install.d.ts.map +1 -1
- 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
|
@@ -14665,6 +14665,9 @@ var USER_PROMPT_REMINDER_REL = ".claude/hooks/user-prompt-reminder.sh";
|
|
|
14665
14665
|
var USER_PROMPT_SCOPE_REL = ".claude/hooks/user-prompt-scope.sh";
|
|
14666
14666
|
var USER_PROMPT_FINDINGS_REL = ".claude/hooks/user-prompt-findings.sh";
|
|
14667
14667
|
var SESSION_END_MARKER_REL = ".claude/hooks/session-end-marker.sh";
|
|
14668
|
+
var CONTEXT_BUDGET_CHECK_REL = ".claude/hooks/context-budget-check.sh";
|
|
14669
|
+
var CONTEXT_DRIFT_CHECK_REL = ".claude/hooks/context-drift-check.sh";
|
|
14670
|
+
var CONTEXT_DRIFT_MATCHER = "Edit|Write";
|
|
14668
14671
|
var WRITE_CAPABLE_TOOLS = ["Edit", "MultiEdit", "NotebookEdit", "Write"];
|
|
14669
14672
|
var PRE_TOOL_USE_MATCHER = "^(Bash|Read|Grep|Write|Edit|MultiEdit|NotebookEdit|Agent|PowerShell)$";
|
|
14670
14673
|
function hookCommand(script, verb) {
|
|
@@ -14741,6 +14744,20 @@ var HALFCYCLE_HOOK_WIRING = [
|
|
|
14741
14744
|
timeout: 5,
|
|
14742
14745
|
command: hookCommand(SESSION_END_MARKER_REL),
|
|
14743
14746
|
script: SESSION_END_MARKER_REL
|
|
14747
|
+
},
|
|
14748
|
+
{
|
|
14749
|
+
event: "SessionStart",
|
|
14750
|
+
matcher: null,
|
|
14751
|
+
timeout: 10,
|
|
14752
|
+
command: hookCommand(CONTEXT_BUDGET_CHECK_REL),
|
|
14753
|
+
script: CONTEXT_BUDGET_CHECK_REL
|
|
14754
|
+
},
|
|
14755
|
+
{
|
|
14756
|
+
event: "PostToolUse",
|
|
14757
|
+
matcher: CONTEXT_DRIFT_MATCHER,
|
|
14758
|
+
timeout: 5,
|
|
14759
|
+
command: hookCommand(CONTEXT_DRIFT_CHECK_REL),
|
|
14760
|
+
script: CONTEXT_DRIFT_CHECK_REL
|
|
14744
14761
|
}
|
|
14745
14762
|
];
|
|
14746
14763
|
var RETIRED_HOOK_COMMANDS = [
|
|
@@ -14750,7 +14767,9 @@ var RETIRED_HOOK_COMMANDS = [
|
|
|
14750
14767
|
"bash ./.claude/hooks/guard-runner.sh hook post-tool-use",
|
|
14751
14768
|
"bash ./.claude/hooks/guard-runner.sh hook stop",
|
|
14752
14769
|
"bash ./.claude/hooks/guard-runner.sh hook subagent-stop",
|
|
14753
|
-
"bash ./.claude/hooks/session-end-marker.sh"
|
|
14770
|
+
"bash ./.claude/hooks/session-end-marker.sh",
|
|
14771
|
+
"bash ./.claude/hooks/context-budget-check.sh",
|
|
14772
|
+
"bash ./.claude/hooks/context-drift-check.sh"
|
|
14754
14773
|
];
|
|
14755
14774
|
var HALFCYCLE_HOOK_COMMANDS = /* @__PURE__ */ new Set([
|
|
14756
14775
|
...HALFCYCLE_HOOK_WIRING.map((entry) => entry.command),
|
|
@@ -15154,7 +15173,9 @@ var CODEX_HOOK_WIRING = [
|
|
|
15154
15173
|
codexHookRow("PostToolUse", CODEX_WRITE_CAPABLE_TOOLS.join("|"), 60, GUARD_RUNNER_REL, "hook post-tool-use --agent codex"),
|
|
15155
15174
|
codexHookRow("Stop", null, 60, GUARD_RUNNER_REL, "hook stop --agent codex"),
|
|
15156
15175
|
codexHookRow("SubagentStop", null, 60, GUARD_RUNNER_REL, "hook subagent-stop --agent codex"),
|
|
15157
|
-
codexHookRow("SessionEnd", null, 3, SESSION_END_MARKER_REL)
|
|
15176
|
+
codexHookRow("SessionEnd", null, 3, SESSION_END_MARKER_REL),
|
|
15177
|
+
codexHookRow("SessionStart", null, 10, CONTEXT_BUDGET_CHECK_REL),
|
|
15178
|
+
codexHookRow("PostToolUse", CODEX_WRITE_CAPABLE_TOOLS.join("|"), 5, CONTEXT_DRIFT_CHECK_REL)
|
|
15158
15179
|
];
|
|
15159
15180
|
var RETIRED_CODEX_HOOK_COMMANDS = [];
|
|
15160
15181
|
function codexHookCommandSet(retired) {
|
|
@@ -18082,6 +18103,30 @@ var phaseReviewViewSchema = external_exports.discriminatedUnion("state", [
|
|
|
18082
18103
|
phaseReviewOpenSchema,
|
|
18083
18104
|
phaseReviewClosedSchema
|
|
18084
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
|
+
}
|
|
18085
18130
|
|
|
18086
18131
|
// ../events/dist/client-version.js
|
|
18087
18132
|
var CLIENT_VERSION_HEADER = "x-halfcycle-client";
|
|
@@ -18542,6 +18587,42 @@ var ownerWaitDeclarationSchema = external_exports.object({
|
|
|
18542
18587
|
var ownerWaitDeclarationsSchema = external_exports.object({ declarations: external_exports.array(ownerWaitDeclarationSchema) }).strict();
|
|
18543
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.`;
|
|
18544
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
|
+
|
|
18545
18626
|
// ../events/dist/pacing.js
|
|
18546
18627
|
var CLAUDE_PLANS = Object.freeze([
|
|
18547
18628
|
Object.freeze({ id: "pro", words: "Pro", startingAgents: 2 }),
|
|
@@ -18744,6 +18825,8 @@ var autonomyActivityRowSchema = external_exports.object({
|
|
|
18744
18825
|
// whether the agent says it can be undone
|
|
18745
18826
|
how: statementText(STATEMENT_MAX).optional(),
|
|
18746
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
|
|
18747
18830
|
at: utcIso8601
|
|
18748
18831
|
// when it happened
|
|
18749
18832
|
}).strict().superRefine((row, ctx) => {
|
|
@@ -18770,6 +18853,9 @@ var autonomyActivityRowSchema = external_exports.object({
|
|
|
18770
18853
|
});
|
|
18771
18854
|
}
|
|
18772
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
|
+
}
|
|
18773
18859
|
if (row.kind === "waiting" && (row.undoable !== void 0 || row.how !== void 0)) {
|
|
18774
18860
|
ctx.addIssue({ code: "custom", message: "a waiting decision has not been made yet", path: ["undoable"] });
|
|
18775
18861
|
}
|
|
@@ -29234,7 +29320,8 @@ function refusalFor(facts, description) {
|
|
|
29234
29320
|
function heldStep(steps, description, why) {
|
|
29235
29321
|
return {
|
|
29236
29322
|
person: PERSON_HOLD_LINE,
|
|
29237
|
-
agent: `${HELD_STEP_OPENER}${steps}. ${reasonSentence(description)} ${PRECAUTION_OPENER}${why}. ${HOLD_AGENT_INSTRUCTION}
|
|
29323
|
+
agent: `${HELD_STEP_OPENER}${steps}. ${reasonSentence(description)} ${PRECAUTION_OPENER}${why}. ${HOLD_AGENT_INSTRUCTION}`,
|
|
29324
|
+
step: steps
|
|
29238
29325
|
};
|
|
29239
29326
|
}
|
|
29240
29327
|
var STEP_VERBS = {
|
|
@@ -29651,6 +29738,7 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
29651
29738
|
import { appendFileSync as appendFileSync4, readFileSync as readFileSync20, renameSync as renameSync10, unlinkSync as unlinkSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
29652
29739
|
var ALLOW_ONCE_MS = 5 * 60 * 1e3;
|
|
29653
29740
|
var HELD_STEP_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
29741
|
+
var STEP_MAX_LENGTH = 1e3;
|
|
29654
29742
|
function isAllowReply(prompt) {
|
|
29655
29743
|
return typeof prompt === "string" && prompt.trim().toLowerCase() === ALLOW_REPLY;
|
|
29656
29744
|
}
|
|
@@ -29676,11 +29764,11 @@ function canonical(value) {
|
|
|
29676
29764
|
}
|
|
29677
29765
|
return JSON.stringify(value) ?? "null";
|
|
29678
29766
|
}
|
|
29679
|
-
function recordHeld(sessionId, key, deps = {}) {
|
|
29767
|
+
function recordHeld(sessionId, key, deps = {}, step = null) {
|
|
29680
29768
|
const paths = allowOncePaths(sessionId, deps.dir);
|
|
29681
29769
|
if (paths === null)
|
|
29682
29770
|
return;
|
|
29683
|
-
const record2 = { version: 1, session: sessionId, key, at: nowOf(deps) };
|
|
29771
|
+
const record2 = { version: 1, session: sessionId, key, at: nowOf(deps), ...isStep(step) ? { step } : {} };
|
|
29684
29772
|
try {
|
|
29685
29773
|
appendFileSync4(paths.held, `${JSON.stringify(record2)}
|
|
29686
29774
|
`, { mode: 384 });
|
|
@@ -29688,23 +29776,26 @@ function recordHeld(sessionId, key, deps = {}) {
|
|
|
29688
29776
|
return;
|
|
29689
29777
|
}
|
|
29690
29778
|
}
|
|
29691
|
-
function
|
|
29779
|
+
function armAllowStep(sessionId, deps = {}) {
|
|
29692
29780
|
try {
|
|
29693
29781
|
const paths = allowOncePaths(sessionId, deps.dir);
|
|
29694
29782
|
if (paths === null)
|
|
29695
|
-
return "nothing-held";
|
|
29783
|
+
return { outcome: "nothing-held", step: null };
|
|
29696
29784
|
const now = nowOf(deps);
|
|
29697
|
-
const
|
|
29785
|
+
const held = claimHeld(paths.held, sessionId, now);
|
|
29786
|
+
const keys = [...new Set(held.map((r) => r.key))];
|
|
29698
29787
|
if (keys.length === 0)
|
|
29699
|
-
return "nothing-held";
|
|
29788
|
+
return { outcome: "nothing-held", step: null };
|
|
29700
29789
|
if (keys.length > 1)
|
|
29701
|
-
return "several-held";
|
|
29702
|
-
|
|
29790
|
+
return { outcome: "several-held", step: null };
|
|
29791
|
+
const step = held[held.length - 1].step ?? null;
|
|
29792
|
+
const saved = writeRecord(paths.allow, { version: 1, session: sessionId, key: keys[0], at: now });
|
|
29793
|
+
return saved ? { outcome: "armed", step } : { outcome: "not-saved", step: null };
|
|
29703
29794
|
} catch {
|
|
29704
|
-
return "not-saved";
|
|
29795
|
+
return { outcome: "not-saved", step: null };
|
|
29705
29796
|
}
|
|
29706
29797
|
}
|
|
29707
|
-
function
|
|
29798
|
+
function claimHeld(path, session, now) {
|
|
29708
29799
|
const claimed = `${path}.${process.pid}.${now}.claim`;
|
|
29709
29800
|
try {
|
|
29710
29801
|
renameSync10(path, claimed);
|
|
@@ -29718,13 +29809,16 @@ function claimHeldKeys(path, session, now) {
|
|
|
29718
29809
|
text = "";
|
|
29719
29810
|
}
|
|
29720
29811
|
removeQuietly(claimed);
|
|
29721
|
-
const
|
|
29812
|
+
const records = [];
|
|
29722
29813
|
for (const line2 of text.split("\n")) {
|
|
29723
29814
|
const r = parseRecord(line2, session);
|
|
29724
29815
|
if (r !== null && within(r.at, now, HELD_STEP_MAX_AGE_MS))
|
|
29725
|
-
|
|
29816
|
+
records.push(r);
|
|
29726
29817
|
}
|
|
29727
|
-
return
|
|
29818
|
+
return records;
|
|
29819
|
+
}
|
|
29820
|
+
function isStep(v) {
|
|
29821
|
+
return typeof v === "string" && v !== "" && v.length <= STEP_MAX_LENGTH;
|
|
29728
29822
|
}
|
|
29729
29823
|
function spendAllow(sessionId, key, deps = {}) {
|
|
29730
29824
|
try {
|
|
@@ -29776,7 +29870,8 @@ function parseRecord(text, session) {
|
|
|
29776
29870
|
return null;
|
|
29777
29871
|
if (typeof r["at"] !== "number")
|
|
29778
29872
|
return null;
|
|
29779
|
-
|
|
29873
|
+
const step = r["step"];
|
|
29874
|
+
return { version: 1, session, key: r["key"], at: r["at"], ...isStep(step) ? { step } : {} };
|
|
29780
29875
|
}
|
|
29781
29876
|
function writeRecord(path, record2) {
|
|
29782
29877
|
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
@@ -30696,7 +30791,7 @@ function runPreToolUse(deps) {
|
|
|
30696
30791
|
const allowOnce = { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir }, now: () => at.getTime() };
|
|
30697
30792
|
if (spendAllow(input["session_id"], key, allowOnce))
|
|
30698
30793
|
return "";
|
|
30699
|
-
recordHeld(input["session_id"], key, allowOnce);
|
|
30794
|
+
recordHeld(input["session_id"], key, allowOnce, answer.refusal.step);
|
|
30700
30795
|
}
|
|
30701
30796
|
}
|
|
30702
30797
|
return `${preToolUseReplyText("refuse", answer.refusal.person, answer.refusal.agent)}
|
|
@@ -31257,6 +31352,14 @@ function partShape(act, head, words) {
|
|
|
31257
31352
|
return null;
|
|
31258
31353
|
return { kind: "part", act, reference, part, words };
|
|
31259
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
|
+
}
|
|
31260
31363
|
function matchReviewPhraseShape(shape, reviews) {
|
|
31261
31364
|
const resolved = resolvePhaseReference(shape.reference, reviews);
|
|
31262
31365
|
const act = shape.kind === "phase" ? "accept" : shape.kind === "reject" ? "reject" : shape.act;
|
|
@@ -31347,6 +31450,28 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
31347
31450
|
}
|
|
31348
31451
|
}
|
|
31349
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
|
+
|
|
31350
31475
|
// dist/hooks/user-prompt-submit-lines.js
|
|
31351
31476
|
var TELL_THE_PERSON = "Tell the person.";
|
|
31352
31477
|
function line(text) {
|
|
@@ -31684,14 +31809,15 @@ function autonomyLineForAnswer(answer) {
|
|
|
31684
31809
|
}
|
|
31685
31810
|
var NOTHING_ALLOWED = "Nothing was allowed";
|
|
31686
31811
|
var ALLOW_MINUTES = Math.round(ALLOW_ONCE_MS / 6e4);
|
|
31687
|
-
function allowArmedLine() {
|
|
31688
|
-
|
|
31812
|
+
function allowArmedLine(step = null) {
|
|
31813
|
+
const named = step === null ? "The person allowed the paused step once." : `The person allowed this paused step once: ${step}.`;
|
|
31814
|
+
return line(`${named} Run it again now, exactly as before; it will go ahead once if it runs within ${ALLOW_MINUTES} minutes, and any other risky step is still paused.`);
|
|
31689
31815
|
}
|
|
31690
31816
|
function allowNothingHeldLine() {
|
|
31691
31817
|
return line(`${NOTHING_ALLOWED}: no step is paused in this session.`);
|
|
31692
31818
|
}
|
|
31693
31819
|
function allowSeveralHeldLine() {
|
|
31694
|
-
return
|
|
31820
|
+
return `${NOTHING_ALLOWED}: more than one step was paused, so Halfcycle could not tell which one you meant. Nothing ran. When your agent tries the step again, type ${ALLOW_REPLY} once more. ${SHOW_AS_WRITTEN}`;
|
|
31695
31821
|
}
|
|
31696
31822
|
function allowNotAttendedLine() {
|
|
31697
31823
|
return line(`${NOTHING_ALLOWED}: a paused step can be allowed by typing only when a person types it into an open session.`);
|
|
@@ -31700,14 +31826,81 @@ function allowNotSavedLine() {
|
|
|
31700
31826
|
return line(`${NOTHING_ALLOWED}: Halfcycle could not save the answer on this computer. Type ${ALLOW_REPLY} again.`);
|
|
31701
31827
|
}
|
|
31702
31828
|
var CODEX_TYPING_NEVER_COUNTS = "in Codex, Halfcycle cannot tell a line a person typed from one a program sent, so a typed decision never counts there";
|
|
31829
|
+
var SHOW_AS_WRITTEN = "Show the person this line as written.";
|
|
31830
|
+
function codexLine(text) {
|
|
31831
|
+
return `${text} ${SHOW_AS_WRITTEN}`;
|
|
31832
|
+
}
|
|
31703
31833
|
function codexScopeLine(act) {
|
|
31704
|
-
return act === "approve" ?
|
|
31834
|
+
return act === "approve" ? codexLine(`${nothingDone(act)}: ${CODEX_TYPING_NEVER_COUNTS}. To approve, choose Accept in the console.`) : codexLine(`${nothingDone(act)}: ${CODEX_TYPING_NEVER_COUNTS}. The console offers only Accept, so work cannot be sent back from Codex.`);
|
|
31705
31835
|
}
|
|
31706
31836
|
function codexReviewLine(act) {
|
|
31707
|
-
return act === "accept" ?
|
|
31837
|
+
return act === "accept" ? codexLine(`${NOTHING_RECORDED}: ${CODEX_TYPING_NEVER_COUNTS}. To accept, choose Accept in the console.`) : codexLine(`${NOTHING_RECORDED}: ${CODEX_TYPING_NEVER_COUNTS}. The console offers only Accept, so this cannot be done from Codex.`);
|
|
31708
31838
|
}
|
|
31709
31839
|
function codexAutonomyLine() {
|
|
31710
|
-
return
|
|
31840
|
+
return codexLine(`${AUTONOMY_NOTHING_CHANGED}: ${CODEX_TYPING_NEVER_COUNTS}. Turn it on or off with "${AUTONOMY_A1_TITLE}" in the console's settings.`);
|
|
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
|
+
}
|
|
31711
31904
|
}
|
|
31712
31905
|
|
|
31713
31906
|
// dist/hooks/user-prompt-submit.js
|
|
@@ -31733,6 +31926,7 @@ function agentOf(opts) {
|
|
|
31733
31926
|
async function decideLine(opts) {
|
|
31734
31927
|
let act = null;
|
|
31735
31928
|
let autonomyAct = null;
|
|
31929
|
+
let waitRecognised = false;
|
|
31736
31930
|
const review = { recognised: false };
|
|
31737
31931
|
try {
|
|
31738
31932
|
const prompt = promptOf(opts.input);
|
|
@@ -31743,6 +31937,12 @@ async function decideLine(opts) {
|
|
|
31743
31937
|
if (autonomyShape !== null) {
|
|
31744
31938
|
autonomyAct = autonomyShape;
|
|
31745
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)}
|
|
31746
31946
|
`;
|
|
31747
31947
|
}
|
|
31748
31948
|
const reviewShape = reviewPhraseShape(prompt);
|
|
@@ -31760,6 +31960,9 @@ async function decideLine(opts) {
|
|
|
31760
31960
|
} catch {
|
|
31761
31961
|
if (autonomyAct !== null)
|
|
31762
31962
|
return `${autonomyCannotReachLine()}
|
|
31963
|
+
`;
|
|
31964
|
+
if (waitRecognised)
|
|
31965
|
+
return `${waitCannotReachLine()}
|
|
31763
31966
|
`;
|
|
31764
31967
|
if (review.recognised)
|
|
31765
31968
|
return `${reviewCannotReachLine()}
|
|
@@ -31772,13 +31975,13 @@ function decideAllow(opts) {
|
|
|
31772
31975
|
const env = opts.env ?? process.env;
|
|
31773
31976
|
if ((opts.agent ?? "claude-code") === "claude-code" && !readSessionAttended(env))
|
|
31774
31977
|
return allowNotAttendedLine();
|
|
31775
|
-
const outcome =
|
|
31978
|
+
const { outcome, step } = armAllowStep(sessionIdOf(opts.input), {
|
|
31776
31979
|
...opts.tempDir === void 0 ? {} : { dir: opts.tempDir },
|
|
31777
31980
|
...opts.now === void 0 ? {} : { now: opts.now }
|
|
31778
31981
|
});
|
|
31779
31982
|
switch (outcome) {
|
|
31780
31983
|
case "armed":
|
|
31781
|
-
return allowArmedLine();
|
|
31984
|
+
return allowArmedLine(step);
|
|
31782
31985
|
case "nothing-held":
|
|
31783
31986
|
return allowNothingHeldLine();
|
|
31784
31987
|
case "several-held":
|
|
@@ -31805,6 +32008,57 @@ async function decideAutonomy(act, opts) {
|
|
|
31805
32008
|
const { guardServiceToken, guardEngagementId } = read.config;
|
|
31806
32009
|
return postAutonomy(act, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
31807
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
|
+
}
|
|
31808
32062
|
async function postAutonomy(act, engagementId, credential, env, fetchFn = globalThis.fetch) {
|
|
31809
32063
|
const url2 = `${readControlOrigin(env).origin}/engagements/${encodeURIComponent(engagementId)}/autonomy/chat`;
|
|
31810
32064
|
const controller = new AbortController();
|
|
@@ -31838,8 +32092,12 @@ async function decideReview(shape, opts, progress) {
|
|
|
31838
32092
|
const read2 = readConfig(env);
|
|
31839
32093
|
if (read2.config === null)
|
|
31840
32094
|
return "";
|
|
31841
|
-
const
|
|
31842
|
-
|
|
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")
|
|
31843
32101
|
return "";
|
|
31844
32102
|
progress.recognised = true;
|
|
31845
32103
|
const typed2 = whoTyped(opts);
|
|
@@ -31847,7 +32105,11 @@ async function decideReview(shape, opts, progress) {
|
|
|
31847
32105
|
return codexReviewLine("accept");
|
|
31848
32106
|
if (typed2 === "not-attended")
|
|
31849
32107
|
return reviewNotAttendedLine();
|
|
31850
|
-
|
|
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);
|
|
31851
32113
|
}
|
|
31852
32114
|
progress.recognised = true;
|
|
31853
32115
|
const typed = whoTyped(opts);
|
|
@@ -31862,6 +32124,38 @@ async function decideReview(shape, opts, progress) {
|
|
|
31862
32124
|
const match = matchReviewPhraseShape(shape, readShownRecord(read.config.guardEngagementId, opts.home).reviews);
|
|
31863
32125
|
return checkAndSendReview(match, read.config.guardEngagementId, read.config.guardServiceToken, env, opts);
|
|
31864
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
|
+
}
|
|
31865
32159
|
async function checkAndSendReview(match, engagementId, credential, env, opts) {
|
|
31866
32160
|
if (match.kind === "nothing" || match.kind === "show-first")
|
|
31867
32161
|
return reviewShowFirstLine();
|
|
@@ -32415,6 +32709,10 @@ function isCheckedSessionStart(input, profile) {
|
|
|
32415
32709
|
return CHECKED_SOURCES.has(source);
|
|
32416
32710
|
}
|
|
32417
32711
|
var CONNECTED_SENTENCE = `Halfcycle's sign-in works and its service answered. If its tools are missing, turn on ${MCP_SERVER_KEY} in /mcp; if it is not listed, run "claude mcp reset-project-choices" here and restart Claude Code.`;
|
|
32712
|
+
var CODEX_CONNECTED_SENTENCE = "Halfcycle's sign-in works and its service answered. If its tools are missing, start a new Codex session to reconnect them.";
|
|
32713
|
+
function connectedSentenceFor(profile) {
|
|
32714
|
+
return profile.id === "codex" ? CODEX_CONNECTED_SENTENCE : CONNECTED_SENTENCE;
|
|
32715
|
+
}
|
|
32418
32716
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
32419
32717
|
var SESSION_START_PROBE_ATTEMPTS = 2;
|
|
32420
32718
|
function registeredMcpUrl(projectRoot) {
|
|
@@ -32501,6 +32799,7 @@ async function probeDelivery(url2, bearer, fetchFn, deadlineMs, maxAttempts) {
|
|
|
32501
32799
|
}
|
|
32502
32800
|
}
|
|
32503
32801
|
async function checkConnection(renewedToken, opts) {
|
|
32802
|
+
const profile = opts.profile ?? AGENT_PROFILES["claude-code"];
|
|
32504
32803
|
const url2 = registeredMcpUrl(projectRootFromBinary(opts.binaryPath));
|
|
32505
32804
|
if (url2 === void 0)
|
|
32506
32805
|
return void 0;
|
|
@@ -32514,10 +32813,10 @@ async function checkConnection(renewedToken, opts) {
|
|
|
32514
32813
|
environmentTokenFirst: false
|
|
32515
32814
|
});
|
|
32516
32815
|
if (CREDENTIAL_PLACEHOLDERS.includes(bearer)) {
|
|
32517
|
-
return { sentence: PLACEHOLDER_SENTENCES[bearer], url: url2, bearer };
|
|
32816
|
+
return { sentence: PLACEHOLDER_SENTENCES[bearer], url: url2, bearer, connected: false };
|
|
32518
32817
|
}
|
|
32519
32818
|
const outcome = await probeDelivery(url2, bearer, opts.fetchFn ?? globalThis.fetch, opts.checkDeadlineMs ?? CONNECTION_CHECK_DEADLINE_MS, opts.maxAttempts ?? SESSION_START_PROBE_ATTEMPTS);
|
|
32520
|
-
return { sentence: outcome ??
|
|
32819
|
+
return { sentence: outcome ?? connectedSentenceFor(profile), url: url2, bearer, connected: outcome === void 0 };
|
|
32521
32820
|
}
|
|
32522
32821
|
function positionRequestBody(engagementId) {
|
|
32523
32822
|
return JSON.stringify({
|
|
@@ -32607,10 +32906,10 @@ async function runRefreshCredential(opts) {
|
|
|
32607
32906
|
renewalOpen = false;
|
|
32608
32907
|
const parts = [];
|
|
32609
32908
|
if (isCheckedSessionStart(input, profile)) {
|
|
32610
|
-
const connection = await checkConnection(renewedToken, { ...opts, writeErr });
|
|
32909
|
+
const connection = await checkConnection(renewedToken, { ...opts, profile, writeErr });
|
|
32611
32910
|
if (connection !== void 0) {
|
|
32612
32911
|
parts.push(connection.sentence);
|
|
32613
|
-
if (connection.
|
|
32912
|
+
if (connection.connected) {
|
|
32614
32913
|
const engagementId = readConfig().config?.guardEngagementId;
|
|
32615
32914
|
if (engagementId !== void 0) {
|
|
32616
32915
|
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|