halfcycle 0.4.17 → 0.5.0
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/README.md +6 -3
- package/bin/bin.bundle.mjs +1620 -632
- package/bin/pre-tool-use-filter.sh +12 -0
- package/dist/banner-facts.d.ts +11 -1
- package/dist/banner-facts.d.ts.map +1 -1
- package/dist/bin.js +0 -0
- package/dist/check-tasks.d.ts +23 -11
- package/dist/check-tasks.d.ts.map +1 -1
- package/dist/claude-version-probe.d.ts +27 -3
- package/dist/claude-version-probe.d.ts.map +1 -1
- package/dist/cli.js +1475 -120
- package/dist/cli.js.map +3 -3
- package/dist/codex-install-lines.d.ts +72 -0
- package/dist/codex-install-lines.d.ts.map +1 -0
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/index.js +1078 -24
- package/dist/index.js.map +3 -3
- package/dist/install-manifest.d.ts +41 -0
- package/dist/install-manifest.d.ts.map +1 -1
- package/dist/install-plan.d.ts +5 -1
- package/dist/install-plan.d.ts.map +1 -1
- package/dist/install.d.ts +395 -10
- package/dist/install.d.ts.map +1 -1
- package/dist/merge-settings.d.ts +56 -0
- package/dist/merge-settings.d.ts.map +1 -1
- package/dist/node-floor.d.ts +12 -3
- package/dist/node-floor.d.ts.map +1 -1
- package/dist/review.d.ts +19 -4
- package/dist/review.d.ts.map +1 -1
- package/dist/toml-lines.d.ts +59 -0
- package/dist/toml-lines.d.ts.map +1 -0
- package/dist/uninstall.d.ts +3 -1
- package/dist/uninstall.d.ts.map +1 -1
- package/package.json +13 -12
package/bin/bin.bundle.mjs
CHANGED
|
@@ -15033,6 +15033,7 @@ var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
|
|
|
15033
15033
|
// ../core/dist/task-plan.js
|
|
15034
15034
|
var TASK_PLAN_TASKS_MAX = 500;
|
|
15035
15035
|
var TASK_PLAN_ID_MAX = 64;
|
|
15036
|
+
var TASK_PLAN_BUILDS_NONE = "none";
|
|
15036
15037
|
var TASK_PLAN_KINDS = ["build", "review", "spec", "walk", "check"];
|
|
15037
15038
|
var CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/;
|
|
15038
15039
|
var planText = external_exports.string().refine((text) => !CONTROL_CHARACTER.test(text), { message: "holds a control character" });
|
|
@@ -15048,7 +15049,8 @@ var taskPlanTaskSchema = external_exports.object({
|
|
|
15048
15049
|
interfaces: external_exports.array(planText).optional()
|
|
15049
15050
|
}).strict().optional(),
|
|
15050
15051
|
wholeProject: external_exports.literal(true).optional(),
|
|
15051
|
-
ships: external_exports.enum(["deploy", "release"]).optional()
|
|
15052
|
+
ships: external_exports.enum(["deploy", "release"]).optional(),
|
|
15053
|
+
builds: external_exports.union([external_exports.literal(TASK_PLAN_BUILDS_NONE), external_exports.array(planText)]).optional()
|
|
15052
15054
|
}).strict().superRefine((task, ctx) => {
|
|
15053
15055
|
if (task.reviews !== void 0 && task.kind !== "review") {
|
|
15054
15056
|
ctx.addIssue({
|
|
@@ -15085,6 +15087,101 @@ var taskListFileSchema = external_exports.union([
|
|
|
15085
15087
|
external_exports.array(external_exports.unknown())
|
|
15086
15088
|
]);
|
|
15087
15089
|
|
|
15090
|
+
// ../core/dist/agent-profile.js
|
|
15091
|
+
var AGENT_IDS = ["claude-code", "codex"];
|
|
15092
|
+
var CLAUDE_CODE_PROFILE = {
|
|
15093
|
+
id: "claude-code",
|
|
15094
|
+
settingsFiles: [SETTINGS_REL, LOCAL_SETTINGS_REL],
|
|
15095
|
+
wiring: HALFCYCLE_HOOK_WIRING,
|
|
15096
|
+
commands: HALFCYCLE_HOOK_COMMANDS,
|
|
15097
|
+
wiringPaths: HALFCYCLE_HOOK_WIRING_PATHS,
|
|
15098
|
+
writeCapableTools: WRITE_CAPABLE_TOOLS,
|
|
15099
|
+
projectRoot: "claude-project-dir",
|
|
15100
|
+
configFolder: "claude-config",
|
|
15101
|
+
mcpRegistration: { file: MCP_REGISTRATION_REL, serversKey: "mcpServers", serverKey: MCP_SERVER_KEY },
|
|
15102
|
+
tierModels: {
|
|
15103
|
+
deep: { model: "opus" },
|
|
15104
|
+
standard: { model: "sonnet" },
|
|
15105
|
+
fast: { model: "haiku" }
|
|
15106
|
+
}
|
|
15107
|
+
};
|
|
15108
|
+
var CODEX_HOOKS_REL = ".codex/hooks.json";
|
|
15109
|
+
var CODEX_CONFIG_REL = ".codex/config.toml";
|
|
15110
|
+
function codexHookCommand(script, args2) {
|
|
15111
|
+
const invocation = `cd "$(git rev-parse --show-toplevel)" && bash ./${script}`;
|
|
15112
|
+
return args2 === void 0 ? invocation : `${invocation} ${args2}`;
|
|
15113
|
+
}
|
|
15114
|
+
var CODEX_WINDOWS_NOT_SET_UP = "Halfcycle checks did not run because Halfcycle is not set up in this folder or any folder above it; run npx halfcycle in the project folder.";
|
|
15115
|
+
var CODEX_WINDOWS_NO_GIT_BASH = "Halfcycle checks did not run because Git for Windows was not found; install Git for Windows, then open a new session.";
|
|
15116
|
+
function codexHookCommandWindows(script, args2) {
|
|
15117
|
+
const tick = (text) => "`" + text + "`";
|
|
15118
|
+
const argv = [script, ...args2 === void 0 ? [] : args2.split(" ")].map(tick).join(",");
|
|
15119
|
+
const program = [
|
|
15120
|
+
"const p=require(`path`),f=require(`fs`),c=require(`child_process`);",
|
|
15121
|
+
"let d=process.cwd();",
|
|
15122
|
+
`for(;;){if(f.existsSync(p.join(d,${tick(VENDORED_BIN_REL)})))break;`,
|
|
15123
|
+
"const u=p.dirname(d);",
|
|
15124
|
+
"if(u===d){d=``;break}",
|
|
15125
|
+
"d=u}",
|
|
15126
|
+
`if(d===\`\`){console.error(${tick(CODEX_WINDOWS_NOT_SET_UP)})}else{`,
|
|
15127
|
+
"const g=c.spawnSync(`git`,[`--exec-path`],{encoding:`utf8`});",
|
|
15128
|
+
"const b=g.status===0?p.join(g.stdout.trim(),`..`,`..`,`..`,`bin`,`bash.exe`):``;",
|
|
15129
|
+
`if(b===\`\`||!f.existsSync(b)){console.error(${tick(CODEX_WINDOWS_NO_GIT_BASH)})}else{`,
|
|
15130
|
+
`const r=c.spawnSync(b,[${argv}],{cwd:d,stdio:\`inherit\`});`,
|
|
15131
|
+
"process.exitCode=typeof r.status===`number`?r.status:1}}"
|
|
15132
|
+
].join("");
|
|
15133
|
+
return `node -e '${program}'`;
|
|
15134
|
+
}
|
|
15135
|
+
function codexHookRow(event, matcher, timeout, script, args2) {
|
|
15136
|
+
return {
|
|
15137
|
+
event,
|
|
15138
|
+
matcher,
|
|
15139
|
+
timeout,
|
|
15140
|
+
command: codexHookCommand(script, args2),
|
|
15141
|
+
script,
|
|
15142
|
+
commandWindows: codexHookCommandWindows(script, args2)
|
|
15143
|
+
};
|
|
15144
|
+
}
|
|
15145
|
+
var CODEX_WRITE_CAPABLE_TOOLS = ["apply_patch", "Edit", "Write"];
|
|
15146
|
+
var CODEX_PRE_TOOL_USE_MATCHER = "^(Bash|apply_patch)$";
|
|
15147
|
+
var CODEX_HOOK_WIRING = [
|
|
15148
|
+
codexHookRow("SessionStart", null, 5, SESSION_START_MARKER_REL),
|
|
15149
|
+
codexHookRow("SessionStart", null, 10, GUARD_RUNNER_REL, "refresh-credential --agent codex"),
|
|
15150
|
+
codexHookRow("UserPromptSubmit", null, 5, USER_PROMPT_REMINDER_REL),
|
|
15151
|
+
codexHookRow("UserPromptSubmit", null, 10, USER_PROMPT_SCOPE_REL, "--agent codex"),
|
|
15152
|
+
codexHookRow("UserPromptSubmit", null, 10, USER_PROMPT_FINDINGS_REL, "--agent codex"),
|
|
15153
|
+
codexHookRow("PreToolUse", CODEX_PRE_TOOL_USE_MATCHER, 10, GUARD_RUNNER_REL, "hook pre-tool-use --agent codex"),
|
|
15154
|
+
codexHookRow("PostToolUse", CODEX_WRITE_CAPABLE_TOOLS.join("|"), 60, GUARD_RUNNER_REL, "hook post-tool-use --agent codex"),
|
|
15155
|
+
codexHookRow("Stop", null, 60, GUARD_RUNNER_REL, "hook stop --agent codex"),
|
|
15156
|
+
codexHookRow("SubagentStop", null, 60, GUARD_RUNNER_REL, "hook subagent-stop --agent codex"),
|
|
15157
|
+
codexHookRow("SessionEnd", null, 3, SESSION_END_MARKER_REL)
|
|
15158
|
+
];
|
|
15159
|
+
var RETIRED_CODEX_HOOK_COMMANDS = [];
|
|
15160
|
+
function codexHookCommandSet(retired) {
|
|
15161
|
+
return /* @__PURE__ */ new Set([...CODEX_HOOK_WIRING.map((entry) => entry.command), ...retired]);
|
|
15162
|
+
}
|
|
15163
|
+
var CODEX_PROFILE = {
|
|
15164
|
+
id: "codex",
|
|
15165
|
+
settingsFiles: [CODEX_HOOKS_REL],
|
|
15166
|
+
wiring: CODEX_HOOK_WIRING,
|
|
15167
|
+
commands: codexHookCommandSet(RETIRED_CODEX_HOOK_COMMANDS),
|
|
15168
|
+
wiringPaths: [.../* @__PURE__ */ new Set([CODEX_HOOKS_REL, ...CODEX_HOOK_WIRING.map((entry) => entry.script), VENDORED_BIN_REL])],
|
|
15169
|
+
writeCapableTools: CODEX_WRITE_CAPABLE_TOOLS,
|
|
15170
|
+
projectRoot: "git-toplevel",
|
|
15171
|
+
configFolder: "codex-home",
|
|
15172
|
+
mcpRegistration: { file: CODEX_CONFIG_REL, serversKey: "mcp_servers", serverKey: MCP_SERVER_KEY },
|
|
15173
|
+
tierModels: {
|
|
15174
|
+
deep: { model: "gpt-6-luna", effort: "high" },
|
|
15175
|
+
standard: { model: "gpt-6-luna", effort: "medium" },
|
|
15176
|
+
fast: { model: "gpt-6-luna", effort: "low" }
|
|
15177
|
+
}
|
|
15178
|
+
};
|
|
15179
|
+
var AGENT_PROFILES = {
|
|
15180
|
+
"claude-code": CLAUDE_CODE_PROFILE,
|
|
15181
|
+
codex: CODEX_PROFILE
|
|
15182
|
+
};
|
|
15183
|
+
var ALL_HOOK_COMMANDS = new Set(AGENT_IDS.flatMap((id) => [...AGENT_PROFILES[id].commands]));
|
|
15184
|
+
|
|
15088
15185
|
// dist/control-origin.js
|
|
15089
15186
|
var DEFAULT_CONTROL_ORIGIN = "https://control.halfcycle.ai";
|
|
15090
15187
|
function resolveControlOrigin(env) {
|
|
@@ -15269,7 +15366,8 @@ var seatPathSchema = external_exports.object({
|
|
|
15269
15366
|
}).strict();
|
|
15270
15367
|
var seatConfigurationSchema = external_exports.object({
|
|
15271
15368
|
settingsFiles: external_exports.array(seatSettingsFileSchema),
|
|
15272
|
-
paths: external_exports.array(seatPathSchema)
|
|
15369
|
+
paths: external_exports.array(seatPathSchema),
|
|
15370
|
+
agent: external_exports.string().optional()
|
|
15273
15371
|
}).strict();
|
|
15274
15372
|
var evaluationRequestSchema = external_exports.object({
|
|
15275
15373
|
engagementId: external_exports.string(),
|
|
@@ -15960,7 +16058,7 @@ function crossFieldProblems(raw) {
|
|
|
15960
16058
|
const parts = Array.isArray(raw.parts) ? raw.parts : [];
|
|
15961
16059
|
const connections = Array.isArray(raw.connections) ? raw.connections : [];
|
|
15962
16060
|
const ids = /* @__PURE__ */ new Set();
|
|
15963
|
-
const
|
|
16061
|
+
const placed2 = /* @__PURE__ */ new Map();
|
|
15964
16062
|
const owners = /* @__PURE__ */ new Map();
|
|
15965
16063
|
parts.forEach((part, i) => {
|
|
15966
16064
|
if (!isRecord(part))
|
|
@@ -15973,14 +16071,14 @@ function crossFieldProblems(raw) {
|
|
|
15973
16071
|
}
|
|
15974
16072
|
if (typeof part.lane === "string" && typeof part.slot === "number") {
|
|
15975
16073
|
const place = `${part.lane}\0${part.slot}`;
|
|
15976
|
-
const earlier =
|
|
16074
|
+
const earlier = placed2.get(place);
|
|
15977
16075
|
if (earlier !== void 0) {
|
|
15978
16076
|
problems.push({
|
|
15979
16077
|
path: `parts.${i}.slot`,
|
|
15980
16078
|
message: `${quoted(parts[earlier].id)} already sits in lane ${shown(part.lane)} at slot ${part.slot}`
|
|
15981
16079
|
});
|
|
15982
16080
|
} else {
|
|
15983
|
-
|
|
16081
|
+
placed2.set(place, i);
|
|
15984
16082
|
}
|
|
15985
16083
|
}
|
|
15986
16084
|
if (typeof part.outside === "boolean" && Array.isArray(part.owns)) {
|
|
@@ -16348,7 +16446,10 @@ var buildSnapshotReportResultSchema = external_exports.object({
|
|
|
16348
16446
|
// Which phase the graph was recorded against.
|
|
16349
16447
|
phase: buildPhaseSchema,
|
|
16350
16448
|
// When it was recorded.
|
|
16351
|
-
reportedAt: utcIso8601
|
|
16449
|
+
reportedAt: utcIso8601,
|
|
16450
|
+
// One sentence to pass on to the developer about the graph just stored. Absent
|
|
16451
|
+
// when there is nothing to say.
|
|
16452
|
+
warning: external_exports.string().min(1).max(400).optional()
|
|
16352
16453
|
}).strict();
|
|
16353
16454
|
|
|
16354
16455
|
// ../events/dist/change-path-order.js
|
|
@@ -16795,8 +16896,13 @@ var METHOD_FILE_PATTERNS = [
|
|
|
16795
16896
|
".claude/commands",
|
|
16796
16897
|
".claude/hooks",
|
|
16797
16898
|
".claude/settings.json",
|
|
16899
|
+
".claude/agents",
|
|
16798
16900
|
".mcp.json",
|
|
16799
16901
|
".halfcycle",
|
|
16902
|
+
".agents/skills",
|
|
16903
|
+
".codex/hooks.json",
|
|
16904
|
+
".codex/config.toml",
|
|
16905
|
+
".codex/agents",
|
|
16800
16906
|
".workbench",
|
|
16801
16907
|
"docs/product-spec.md",
|
|
16802
16908
|
"docs/architecture-overview.md",
|
|
@@ -18295,7 +18401,8 @@ var chatReviewAcceptRequestSchema = external_exports.object({
|
|
|
18295
18401
|
act: external_exports.literal("accept"),
|
|
18296
18402
|
reference: phaseIdentitySchema,
|
|
18297
18403
|
reviewVersion: external_exports.number().int().min(0),
|
|
18298
|
-
parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX)
|
|
18404
|
+
parts: external_exports.array(systemPartIdSchema).max(REVIEW_PARTS_MAX),
|
|
18405
|
+
waitingClaims: external_exports.literal(true).optional()
|
|
18299
18406
|
}).strict();
|
|
18300
18407
|
function notedRequest(act) {
|
|
18301
18408
|
return external_exports.object({
|
|
@@ -18310,12 +18417,20 @@ var chatReviewQuestionRequestSchema = notedRequest("question");
|
|
|
18310
18417
|
var chatReviewSendBackRequestSchema = notedRequest("send-back");
|
|
18311
18418
|
var chatReviewCarryRequestSchema = notedRequest("carry");
|
|
18312
18419
|
var chatReviewDropRequestSchema = notedRequest("drop");
|
|
18420
|
+
var chatReviewRejectRequestSchema = external_exports.object({
|
|
18421
|
+
act: external_exports.literal("reject"),
|
|
18422
|
+
reference: phaseIdentitySchema,
|
|
18423
|
+
reviewVersion: external_exports.number().int().min(0),
|
|
18424
|
+
task: buildTaskIdSchema,
|
|
18425
|
+
note: reviewNoteWireSchema
|
|
18426
|
+
}).strict();
|
|
18313
18427
|
var chatReviewRequestSchema = external_exports.discriminatedUnion("act", [
|
|
18314
18428
|
chatReviewAcceptRequestSchema,
|
|
18315
18429
|
chatReviewQuestionRequestSchema,
|
|
18316
18430
|
chatReviewSendBackRequestSchema,
|
|
18317
18431
|
chatReviewCarryRequestSchema,
|
|
18318
|
-
chatReviewDropRequestSchema
|
|
18432
|
+
chatReviewDropRequestSchema,
|
|
18433
|
+
chatReviewRejectRequestSchema
|
|
18319
18434
|
]);
|
|
18320
18435
|
function bareOutcome2(outcome) {
|
|
18321
18436
|
return external_exports.object({ outcome: external_exports.literal(outcome) }).strict();
|
|
@@ -18328,7 +18443,8 @@ var chatReviewResultSchema = external_exports.discriminatedUnion("outcome", [
|
|
|
18328
18443
|
...reviewDecisionResultSchema.options.filter(notTheConsoleNotDecidable),
|
|
18329
18444
|
bareOutcome2("show-first"),
|
|
18330
18445
|
bareOutcome2("not-offered"),
|
|
18331
|
-
chatAmbiguousSchema
|
|
18446
|
+
chatAmbiguousSchema,
|
|
18447
|
+
bareOutcome2("nothing-waiting")
|
|
18332
18448
|
]);
|
|
18333
18449
|
var reviewAnswerRequestSchema = external_exports.object({
|
|
18334
18450
|
part: systemPartIdSchema,
|
|
@@ -18733,6 +18849,7 @@ var savedSchema = external_exports.object({
|
|
|
18733
18849
|
branches: external_exports.array(savedBranchSchema)
|
|
18734
18850
|
// every saved line of work
|
|
18735
18851
|
}).strict().refine((s) => s.ref === savedRefOf(s.id), { message: "must be the ref of this save", path: ["ref"] });
|
|
18852
|
+
var AUTONOMY_A1_TITLE = "Let builds run on their own";
|
|
18736
18853
|
var AUTONOMY_A4 = 'Builds in this project now run on their own. Turn it off with "autonomy off", or in the console.';
|
|
18737
18854
|
var AUTONOMY_A5 = "Builds in this project wait for you again.";
|
|
18738
18855
|
function withoutFinalStop(text) {
|
|
@@ -18760,6 +18877,28 @@ var AUTONOMY_A18 = "Halfcycle cannot check PowerShell commands while builds run
|
|
|
18760
18877
|
|
|
18761
18878
|
// dist/hook-protocol.js
|
|
18762
18879
|
import { writeSync } from "node:fs";
|
|
18880
|
+
|
|
18881
|
+
// dist/agent-argument.js
|
|
18882
|
+
var AGENT_FLAG = "--agent";
|
|
18883
|
+
function agentFromArguments(rest) {
|
|
18884
|
+
const leading = rest[0] === AGENT_FLAG;
|
|
18885
|
+
const start = leading ? 2 : 0;
|
|
18886
|
+
for (let i = start; i < rest.length; i += 1) {
|
|
18887
|
+
if ((rest[i] ?? "").startsWith(AGENT_FLAG))
|
|
18888
|
+
return null;
|
|
18889
|
+
}
|
|
18890
|
+
if (!leading)
|
|
18891
|
+
return AGENT_PROFILES["claude-code"];
|
|
18892
|
+
const id = rest[1];
|
|
18893
|
+
if (id === void 0 || !isAgentId(id))
|
|
18894
|
+
return null;
|
|
18895
|
+
return AGENT_PROFILES[id];
|
|
18896
|
+
}
|
|
18897
|
+
function isAgentId(id) {
|
|
18898
|
+
return AGENT_IDS.includes(id);
|
|
18899
|
+
}
|
|
18900
|
+
|
|
18901
|
+
// dist/hook-protocol.js
|
|
18763
18902
|
function emitBlock(reason, systemMessage) {
|
|
18764
18903
|
const body = systemMessage === void 0 ? { decision: "block", reason } : { decision: "block", reason, systemMessage };
|
|
18765
18904
|
process.stdout.write(JSON.stringify(body) + "\n");
|
|
@@ -18773,12 +18912,8 @@ function preToolUseReplyText(answer, text = "", agentLine) {
|
|
|
18773
18912
|
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: text }
|
|
18774
18913
|
});
|
|
18775
18914
|
}
|
|
18776
|
-
const
|
|
18777
|
-
|
|
18778
|
-
return JSON.stringify({
|
|
18779
|
-
systemMessage: text,
|
|
18780
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: forAgent }
|
|
18781
|
-
});
|
|
18915
|
+
const hookSpecificOutput = agentLine === void 0 || agentLine === "" ? { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: text } : { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: text, additionalContext: agentLine };
|
|
18916
|
+
return JSON.stringify({ systemMessage: text, hookSpecificOutput });
|
|
18782
18917
|
}
|
|
18783
18918
|
function preToolUseNoticeText(text) {
|
|
18784
18919
|
return JSON.stringify({ systemMessage: text });
|
|
@@ -18798,13 +18933,26 @@ function nonBlockingReply(event, text) {
|
|
|
18798
18933
|
}
|
|
18799
18934
|
return JSON.stringify({ systemMessage: text });
|
|
18800
18935
|
}
|
|
18801
|
-
function hookReply(event) {
|
|
18936
|
+
function hookReply(event, profile = AGENT_PROFILES["claude-code"]) {
|
|
18802
18937
|
const queued = [];
|
|
18938
|
+
const forPerson = [];
|
|
18803
18939
|
const forAgent = [];
|
|
18940
|
+
const jsonOnly = repliesInJsonOnly(profile);
|
|
18804
18941
|
return {
|
|
18805
18942
|
notice(text) {
|
|
18806
18943
|
queued.push(text);
|
|
18807
18944
|
},
|
|
18945
|
+
personNotice(text) {
|
|
18946
|
+
queued.push(text);
|
|
18947
|
+
forPerson.push(text);
|
|
18948
|
+
},
|
|
18949
|
+
loud(text, exitCode) {
|
|
18950
|
+
if (jsonOnly)
|
|
18951
|
+
queued.push(text);
|
|
18952
|
+
else
|
|
18953
|
+
writeSync(2, text);
|
|
18954
|
+
process.exitCode = exitCode;
|
|
18955
|
+
},
|
|
18808
18956
|
agentContext(text) {
|
|
18809
18957
|
forAgent.push(text);
|
|
18810
18958
|
},
|
|
@@ -18814,11 +18962,23 @@ function hookReply(event) {
|
|
|
18814
18962
|
},
|
|
18815
18963
|
flush() {
|
|
18816
18964
|
const text = queued.splice(0).join("\n");
|
|
18965
|
+
const person = forPerson.splice(0).join("\n");
|
|
18817
18966
|
const context = event === "PostToolUse" ? forAgent.splice(0).join("\n") : "";
|
|
18818
18967
|
const code = process.exitCode;
|
|
18819
18968
|
if (code !== void 0 && code !== null && Number(code) !== 0) {
|
|
18820
|
-
if (text
|
|
18969
|
+
if (text === "")
|
|
18970
|
+
return;
|
|
18971
|
+
if (jsonOnly)
|
|
18972
|
+
process.stdout.write(`${jsonOnlyLoudReply(event, text)}
|
|
18973
|
+
`);
|
|
18974
|
+
else
|
|
18821
18975
|
writeSync(2, `${text}
|
|
18976
|
+
`);
|
|
18977
|
+
return;
|
|
18978
|
+
}
|
|
18979
|
+
if (jsonOnly && event === "PostToolUse" && person !== "") {
|
|
18980
|
+
process.stdout.write(`${postToolUsePersonReply(person, context === "" ? text : `${text}
|
|
18981
|
+
${context}`)}
|
|
18822
18982
|
`);
|
|
18823
18983
|
return;
|
|
18824
18984
|
}
|
|
@@ -18834,6 +18994,60 @@ function hookReply(event) {
|
|
|
18834
18994
|
}
|
|
18835
18995
|
};
|
|
18836
18996
|
}
|
|
18997
|
+
function repliesInJsonOnly(profile) {
|
|
18998
|
+
return profile.id === "codex";
|
|
18999
|
+
}
|
|
19000
|
+
function hookExitCode(profile) {
|
|
19001
|
+
if (repliesInJsonOnly(profile))
|
|
19002
|
+
return 0;
|
|
19003
|
+
const code = process.exitCode;
|
|
19004
|
+
return code === void 0 || code === null ? 0 : Number(code);
|
|
19005
|
+
}
|
|
19006
|
+
function jsonOnlyLoudReply(event, text) {
|
|
19007
|
+
return event === "PostToolUse" ? postToolUsePersonReply(text, text) : nonBlockingReply(event, text);
|
|
19008
|
+
}
|
|
19009
|
+
function postToolUsePersonReply(person, agent) {
|
|
19010
|
+
return JSON.stringify({
|
|
19011
|
+
systemMessage: person,
|
|
19012
|
+
hookSpecificOutput: { hookEventName: "PostToolUse", additionalContext: agent }
|
|
19013
|
+
});
|
|
19014
|
+
}
|
|
19015
|
+
function lastResortReply(argv, text) {
|
|
19016
|
+
const [cmd, sub] = argv;
|
|
19017
|
+
let event = null;
|
|
19018
|
+
let rest = [];
|
|
19019
|
+
if (cmd === "refresh-credential") {
|
|
19020
|
+
event = "SessionStart";
|
|
19021
|
+
rest = argv.slice(1);
|
|
19022
|
+
} else if (cmd === "hook" && sub !== void 0 && Object.hasOwn(LAST_RESORT_HOOK_EVENTS, sub)) {
|
|
19023
|
+
event = LAST_RESORT_HOOK_EVENTS[sub] ?? null;
|
|
19024
|
+
rest = argv.slice(2);
|
|
19025
|
+
}
|
|
19026
|
+
if (event === null)
|
|
19027
|
+
return null;
|
|
19028
|
+
const profile = agentFromArguments(rest);
|
|
19029
|
+
if (profile === null || !repliesInJsonOnly(profile))
|
|
19030
|
+
return null;
|
|
19031
|
+
return jsonOnlyLoudReply(event, text);
|
|
19032
|
+
}
|
|
19033
|
+
function answerUnexpectedError(argv, err, out) {
|
|
19034
|
+
const text = `halfcycle-runner: unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
19035
|
+
Contact hello@halfcycle.ai if this keeps happening.
|
|
19036
|
+
`;
|
|
19037
|
+
const reply = lastResortReply(argv, text);
|
|
19038
|
+
if (reply !== null) {
|
|
19039
|
+
out.stdout(`${reply}
|
|
19040
|
+
`);
|
|
19041
|
+
return 0;
|
|
19042
|
+
}
|
|
19043
|
+
out.stderr(text);
|
|
19044
|
+
return 1;
|
|
19045
|
+
}
|
|
19046
|
+
var LAST_RESORT_HOOK_EVENTS = {
|
|
19047
|
+
"post-tool-use": "PostToolUse",
|
|
19048
|
+
stop: "Stop",
|
|
19049
|
+
"subagent-stop": "SubagentStop"
|
|
19050
|
+
};
|
|
18837
19051
|
function postToolUseReply(notice, context) {
|
|
18838
19052
|
const additionalContext = notice === "" ? context : `${notice}
|
|
18839
19053
|
${context}`;
|
|
@@ -18845,6 +19059,9 @@ function userPromptContextReply(context) {
|
|
|
18845
19059
|
function infraWarningText() {
|
|
18846
19060
|
return "[Halfcycle] WARNING: Halfcycle's check service could not be reached, so nothing was checked this time. The session continues without checks.";
|
|
18847
19061
|
}
|
|
19062
|
+
function unreadableEditText() {
|
|
19063
|
+
return "[Halfcycle] WARNING: The files this change touched could not be read, so this change was not checked. The session continues.";
|
|
19064
|
+
}
|
|
18848
19065
|
function localFaultText(message) {
|
|
18849
19066
|
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
18850
19067
|
}
|
|
@@ -18857,8 +19074,13 @@ This is not a service outage: the guard service answered. The suspect is this re
|
|
|
18857
19074
|
}
|
|
18858
19075
|
var UNCONFIGURED_EXIT_TOOL_USE = 2;
|
|
18859
19076
|
var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
|
|
18860
|
-
function emitNeverConfigured(exitCode, reason, engagementId) {
|
|
18861
|
-
|
|
19077
|
+
function emitNeverConfigured(exitCode, reason, engagementId, reply) {
|
|
19078
|
+
const text = neverConfiguredReport(reason, void 0, engagementId);
|
|
19079
|
+
if (reply !== void 0) {
|
|
19080
|
+
reply.loud(text, exitCode);
|
|
19081
|
+
return;
|
|
19082
|
+
}
|
|
19083
|
+
writeSync(2, text);
|
|
18862
19084
|
process.exitCode = exitCode;
|
|
18863
19085
|
}
|
|
18864
19086
|
function namesTheSignInCommand(text) {
|
|
@@ -18937,8 +19159,13 @@ This is not a service outage. The guard service is up and replying; ${cause}, so
|
|
|
18937
19159
|
${remedy}
|
|
18938
19160
|
`;
|
|
18939
19161
|
}
|
|
18940
|
-
function emitCredentialRejected(statusCode, serviceMessage, exitCode, remedy = HOOK_REMEDY) {
|
|
18941
|
-
|
|
19162
|
+
function emitCredentialRejected(statusCode, serviceMessage, exitCode, remedy = HOOK_REMEDY, reply) {
|
|
19163
|
+
const text = credentialRejectedMessage(statusCode, serviceMessage, remedy);
|
|
19164
|
+
if (reply !== void 0) {
|
|
19165
|
+
reply.loud(text, exitCode);
|
|
19166
|
+
return;
|
|
19167
|
+
}
|
|
19168
|
+
writeSync(2, text);
|
|
18942
19169
|
process.exitCode = exitCode;
|
|
18943
19170
|
}
|
|
18944
19171
|
function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
|
|
@@ -21158,6 +21385,18 @@ function findingsPath(sessionId) {
|
|
|
21158
21385
|
return null;
|
|
21159
21386
|
return join6(markerDir(), `${FINDINGS_PREFIX}${sessionId}${FINDINGS_SUFFIX}`);
|
|
21160
21387
|
}
|
|
21388
|
+
var HELD_STEP_PREFIX = "halfcycle-held-";
|
|
21389
|
+
var ALLOW_PREFIX = "halfcycle-allow-";
|
|
21390
|
+
var ALLOW_ONCE_SUFFIX = ".json";
|
|
21391
|
+
function allowOncePaths(sessionId, dir) {
|
|
21392
|
+
if (typeof sessionId !== "string" || !FINDINGS_ID.test(sessionId))
|
|
21393
|
+
return null;
|
|
21394
|
+
const base = dir ?? markerDir();
|
|
21395
|
+
return {
|
|
21396
|
+
held: join6(base, `${HELD_STEP_PREFIX}${sessionId}${ALLOW_ONCE_SUFFIX}`),
|
|
21397
|
+
allow: join6(base, `${ALLOW_PREFIX}${sessionId}${ALLOW_ONCE_SUFFIX}`)
|
|
21398
|
+
};
|
|
21399
|
+
}
|
|
21161
21400
|
function markerDir() {
|
|
21162
21401
|
const raw = process.env["TMPDIR"] || tmpdir();
|
|
21163
21402
|
return raw.replace(/\/+$/, "");
|
|
@@ -21314,7 +21553,9 @@ function sweepSessionFiles(keepSessionId, now) {
|
|
|
21314
21553
|
}
|
|
21315
21554
|
const kinds = [
|
|
21316
21555
|
[FINDINGS_PREFIX, FINDINGS_SUFFIX],
|
|
21317
|
-
[OWN_WORK_PREFIX, OWN_WORK_SUFFIX]
|
|
21556
|
+
[OWN_WORK_PREFIX, OWN_WORK_SUFFIX],
|
|
21557
|
+
[HELD_STEP_PREFIX, ALLOW_ONCE_SUFFIX],
|
|
21558
|
+
[ALLOW_PREFIX, ALLOW_ONCE_SUFFIX]
|
|
21318
21559
|
];
|
|
21319
21560
|
for (const name of names) {
|
|
21320
21561
|
for (const [prefix, suffix] of kinds) {
|
|
@@ -21418,9 +21659,9 @@ function removeLivenessIndexEntry(dir, sessionId, name) {
|
|
|
21418
21659
|
import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
21419
21660
|
import { homedir as homedir3 } from "node:os";
|
|
21420
21661
|
import { join as join7, relative as relative3, resolve as resolve2 } from "node:path";
|
|
21421
|
-
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
21662
|
+
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration, profile = AGENT_PROFILES["claude-code"]) {
|
|
21422
21663
|
try {
|
|
21423
|
-
const facts = read(repoRoot);
|
|
21664
|
+
const facts = read(repoRoot, profile);
|
|
21424
21665
|
if (facts === void 0)
|
|
21425
21666
|
return void 0;
|
|
21426
21667
|
const parsed = seatConfigurationSchema.safeParse(facts);
|
|
@@ -21429,8 +21670,8 @@ function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
|
21429
21670
|
return void 0;
|
|
21430
21671
|
}
|
|
21431
21672
|
}
|
|
21432
|
-
function readSeatConfiguration(repoRoot) {
|
|
21433
|
-
const given = resolve2(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
|
|
21673
|
+
function readSeatConfiguration(repoRoot, profile = AGENT_PROFILES["claude-code"]) {
|
|
21674
|
+
const given = profile.projectRoot === "git-toplevel" ? resolve2(repoRoot) : resolve2(process.env["CLAUDE_PROJECT_DIR"] || process.cwd());
|
|
21434
21675
|
const projectDir = realpathSync2.native(given);
|
|
21435
21676
|
const home = homedir3();
|
|
21436
21677
|
let realHome = home;
|
|
@@ -21442,14 +21683,15 @@ function readSeatConfiguration(repoRoot) {
|
|
|
21442
21683
|
if (projectDir === home || given === home || projectDir === realHome)
|
|
21443
21684
|
return void 0;
|
|
21444
21685
|
return {
|
|
21445
|
-
settingsFiles:
|
|
21446
|
-
paths: readPathStates(projectDir, repoRoot)
|
|
21686
|
+
settingsFiles: profile.settingsFiles.map((rel) => readSettingsFile(projectDir, rel, profile.commands)),
|
|
21687
|
+
paths: readPathStates(projectDir, repoRoot, profile.wiringPaths),
|
|
21688
|
+
agent: profile.id
|
|
21447
21689
|
};
|
|
21448
21690
|
}
|
|
21449
21691
|
function isPlainObject3(value) {
|
|
21450
21692
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21451
21693
|
}
|
|
21452
|
-
function readSettingsFile(projectDir, rel) {
|
|
21694
|
+
function readSettingsFile(projectDir, rel, ours) {
|
|
21453
21695
|
let raw;
|
|
21454
21696
|
try {
|
|
21455
21697
|
raw = readFileSync8(join7(projectDir, rel), "utf-8");
|
|
@@ -21466,9 +21708,9 @@ function readSettingsFile(projectDir, rel) {
|
|
|
21466
21708
|
}
|
|
21467
21709
|
if (!isPlainObject3(parsed))
|
|
21468
21710
|
return { path: rel, state: "unreadable", hooks: [] };
|
|
21469
|
-
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"]) };
|
|
21711
|
+
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"], ours) };
|
|
21470
21712
|
}
|
|
21471
|
-
function ourHooks(hooks) {
|
|
21713
|
+
function ourHooks(hooks, ours) {
|
|
21472
21714
|
if (!isPlainObject3(hooks))
|
|
21473
21715
|
return [];
|
|
21474
21716
|
const rows = [];
|
|
@@ -21492,7 +21734,7 @@ function ourHooks(hooks) {
|
|
|
21492
21734
|
if (!isPlainObject3(hook))
|
|
21493
21735
|
continue;
|
|
21494
21736
|
const command = hook["command"];
|
|
21495
|
-
if (typeof command !== "string" || !
|
|
21737
|
+
if (typeof command !== "string" || !ours.has(command))
|
|
21496
21738
|
continue;
|
|
21497
21739
|
let timeout = null;
|
|
21498
21740
|
if (Object.hasOwn(hook, "timeout")) {
|
|
@@ -21511,8 +21753,8 @@ function ourHooks(hooks) {
|
|
|
21511
21753
|
}
|
|
21512
21754
|
return rows;
|
|
21513
21755
|
}
|
|
21514
|
-
function readPathStates(projectDir, repoRoot) {
|
|
21515
|
-
const unknown2 = () =>
|
|
21756
|
+
function readPathStates(projectDir, repoRoot, wiringPaths) {
|
|
21757
|
+
const unknown2 = () => wiringPaths.map((path) => ({ path, state: "unknown" }));
|
|
21516
21758
|
let projectTop = projectDir;
|
|
21517
21759
|
if (repoRoot !== projectDir) {
|
|
21518
21760
|
const top = getRepoRoot(projectDir);
|
|
@@ -21521,10 +21763,10 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
21521
21763
|
projectTop = top.value;
|
|
21522
21764
|
}
|
|
21523
21765
|
const prefix = relative3(projectTop, projectDir);
|
|
21524
|
-
const status = getWiringStatus(
|
|
21766
|
+
const status = getWiringStatus(wiringPaths, projectDir);
|
|
21525
21767
|
if (!status.ok)
|
|
21526
21768
|
return unknown2();
|
|
21527
|
-
const mapped = mapWiringStatus(status.value, prefix,
|
|
21769
|
+
const mapped = mapWiringStatus(status.value, prefix, wiringPaths, (rel) => existsSync5(join7(projectDir, rel)));
|
|
21528
21770
|
return mapped ?? unknown2();
|
|
21529
21771
|
}
|
|
21530
21772
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -22243,8 +22485,8 @@ function separatorOf(path) {
|
|
|
22243
22485
|
function subagentsFolderOf(transcript) {
|
|
22244
22486
|
if (!transcript.endsWith(".jsonl") || transcript.length === ".jsonl".length)
|
|
22245
22487
|
return null;
|
|
22246
|
-
const
|
|
22247
|
-
return `${transcript.slice(0, -".jsonl".length)}${
|
|
22488
|
+
const sep6 = separatorOf(transcript);
|
|
22489
|
+
return `${transcript.slice(0, -".jsonl".length)}${sep6}subagents`;
|
|
22248
22490
|
}
|
|
22249
22491
|
function subagentRecordName(agentId) {
|
|
22250
22492
|
return /^[A-Za-z0-9_-]+$/.test(agentId) ? `agent-${agentId}.jsonl` : null;
|
|
@@ -22784,7 +23026,7 @@ async function pruneTimeLog(path, openedAt) {
|
|
|
22784
23026
|
written = true;
|
|
22785
23027
|
let seen = Buffer.byteLength(text, "utf-8");
|
|
22786
23028
|
for (; ; ) {
|
|
22787
|
-
await new Promise((
|
|
23029
|
+
await new Promise((resolve10) => setTimeout(resolve10, PRUNE_SETTLE_MS));
|
|
22788
23030
|
const after = readFileSync11(aside);
|
|
22789
23031
|
if (after.length <= seen)
|
|
22790
23032
|
break;
|
|
@@ -23419,344 +23661,109 @@ function turnEndMessage(news) {
|
|
|
23419
23661
|
return parts.length === 0 ? null : parts.join("\n");
|
|
23420
23662
|
}
|
|
23421
23663
|
|
|
23422
|
-
// dist/
|
|
23423
|
-
var
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
23429
|
-
|
|
23430
|
-
|
|
23431
|
-
|
|
23432
|
-
|
|
23433
|
-
|
|
23434
|
-
|
|
23435
|
-
|
|
23436
|
-
|
|
23437
|
-
const
|
|
23438
|
-
|
|
23439
|
-
|
|
23440
|
-
|
|
23441
|
-
|
|
23442
|
-
|
|
23443
|
-
let input;
|
|
23444
|
-
try {
|
|
23445
|
-
const raw = await readStdin();
|
|
23446
|
-
input = JSON.parse(raw);
|
|
23447
|
-
} catch {
|
|
23448
|
-
recordTimeEvent("edit", {});
|
|
23449
|
-
return;
|
|
23450
|
-
}
|
|
23451
|
-
recordTimeEvent("edit", input);
|
|
23452
|
-
const toolName = input.tool_name ?? "";
|
|
23453
|
-
if (!HANDLED_EDIT_TOOLS.has(toolName))
|
|
23454
|
-
return;
|
|
23455
|
-
const isNotebook = toolName === "NotebookEdit";
|
|
23456
|
-
const absFilePath = isNotebook ? input.tool_input?.notebook_path : input.tool_input?.file_path;
|
|
23457
|
-
if (!absFilePath || typeof absFilePath !== "string")
|
|
23458
|
-
return;
|
|
23459
|
-
recordOwnEdit(input, absFilePath);
|
|
23460
|
-
const repoRootResult = getRepoRoot(process.cwd());
|
|
23461
|
-
if (!repoRootResult.ok) {
|
|
23462
|
-
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
23463
|
-
reply.notice(noRepositoryText(repoRootResult));
|
|
23664
|
+
// dist/patch.js
|
|
23665
|
+
var PATCH_ADD_HEADER = "*** Add File: ";
|
|
23666
|
+
var PATCH_UPDATE_HEADER = "*** Update File: ";
|
|
23667
|
+
var PATCH_DELETE_HEADER = "*** Delete File: ";
|
|
23668
|
+
var PATCH_MOVE_HEADER = "*** Move to: ";
|
|
23669
|
+
function patchOperations(patch) {
|
|
23670
|
+
const ops = [];
|
|
23671
|
+
let addLines = null;
|
|
23672
|
+
let hunks = null;
|
|
23673
|
+
let lastWasUpdate = false;
|
|
23674
|
+
const hunk = () => {
|
|
23675
|
+
if (hunks.length === 0)
|
|
23676
|
+
hunks.push({ before: [], after: [] });
|
|
23677
|
+
return hunks[hunks.length - 1];
|
|
23678
|
+
};
|
|
23679
|
+
for (const raw of patch.split("\n")) {
|
|
23680
|
+
const line2 = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
|
|
23681
|
+
if (addLines !== null && line2.startsWith("+")) {
|
|
23682
|
+
lastWasUpdate = false;
|
|
23683
|
+
addLines.push(line2.slice(1));
|
|
23684
|
+
continue;
|
|
23464
23685
|
}
|
|
23465
|
-
|
|
23466
|
-
|
|
23467
|
-
|
|
23468
|
-
|
|
23469
|
-
|
|
23470
|
-
|
|
23471
|
-
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
|
-
|
|
23482
|
-
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
23483
|
-
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
23484
|
-
return;
|
|
23485
|
-
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
23486
|
-
return;
|
|
23487
|
-
}
|
|
23488
|
-
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
23489
|
-
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
23490
|
-
const evaluation = await evaluateWithRefresh({
|
|
23491
|
-
guardServiceUrl,
|
|
23492
|
-
token: guardServiceToken,
|
|
23493
|
-
engagementId: guardEngagementId,
|
|
23494
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration),
|
|
23495
|
-
warn: warnOnceThisSession("credential-renewed-for-this-run", repoRoot, input.session_id)
|
|
23496
|
-
});
|
|
23497
|
-
const clientResult = evaluation.result;
|
|
23498
|
-
if (!clientResult.ok) {
|
|
23499
|
-
const failure = classifyFailure(clientResult);
|
|
23500
|
-
await emitRunRecord({
|
|
23501
|
-
engagementId: guardEngagementId,
|
|
23502
|
-
runType: "hook",
|
|
23503
|
-
outcome: outcomeForClientFailure(clientResult.kind),
|
|
23504
|
-
failureReason: failure.reason,
|
|
23505
|
-
changeSet,
|
|
23506
|
-
credential: evaluation.token,
|
|
23507
|
-
credentialRefused: failure.kind === "credential"
|
|
23508
|
-
});
|
|
23509
|
-
if (failure.kind === "credential") {
|
|
23510
|
-
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
23511
|
-
return;
|
|
23512
|
-
emitCredentialRejected(
|
|
23513
|
-
failure.statusCode,
|
|
23514
|
-
failure.message,
|
|
23515
|
-
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
23516
|
-
// The remedy the renewal attempt earned: the sign-in when the account
|
|
23517
|
-
// credential is the thing that was refused, and otherwise the plane's own
|
|
23518
|
-
// sentence about why it would not mint.
|
|
23519
|
-
evaluation.remedy
|
|
23520
|
-
);
|
|
23521
|
-
return;
|
|
23686
|
+
if (hunks !== null && isUpdateLine(line2)) {
|
|
23687
|
+
lastWasUpdate = false;
|
|
23688
|
+
if (line2.startsWith("@@")) {
|
|
23689
|
+
hunks.push({ before: [], after: [] });
|
|
23690
|
+
continue;
|
|
23691
|
+
}
|
|
23692
|
+
const mark = line2.charAt(0);
|
|
23693
|
+
const text = line2.slice(1);
|
|
23694
|
+
if (mark === "-")
|
|
23695
|
+
hunk().before.push(text);
|
|
23696
|
+
else if (mark === "+")
|
|
23697
|
+
hunk().after.push(text);
|
|
23698
|
+
else {
|
|
23699
|
+
hunk().before.push(text);
|
|
23700
|
+
hunk().after.push(text);
|
|
23701
|
+
}
|
|
23702
|
+
continue;
|
|
23522
23703
|
}
|
|
23523
|
-
|
|
23524
|
-
|
|
23525
|
-
|
|
23704
|
+
const trimmed = line2.trim();
|
|
23705
|
+
const move = headerPath(trimmed, PATCH_MOVE_HEADER);
|
|
23706
|
+
if (move !== void 0) {
|
|
23707
|
+
const last = ops[ops.length - 1];
|
|
23708
|
+
if (!lastWasUpdate || move === "" || last === void 0 || last.kind !== "update")
|
|
23709
|
+
return null;
|
|
23710
|
+
ops[ops.length - 1] = { ...last, movedTo: move };
|
|
23711
|
+
lastWasUpdate = false;
|
|
23712
|
+
continue;
|
|
23526
23713
|
}
|
|
23527
|
-
|
|
23528
|
-
|
|
23714
|
+
lastWasUpdate = false;
|
|
23715
|
+
const header = [PATCH_ADD_HEADER, PATCH_UPDATE_HEADER, PATCH_DELETE_HEADER].find((h) => headerPath(trimmed, h) !== void 0);
|
|
23716
|
+
if (header === void 0)
|
|
23717
|
+
continue;
|
|
23718
|
+
const path = headerPath(trimmed, header);
|
|
23719
|
+
if (path === "")
|
|
23720
|
+
return null;
|
|
23721
|
+
addLines = null;
|
|
23722
|
+
hunks = null;
|
|
23723
|
+
if (header === PATCH_ADD_HEADER) {
|
|
23724
|
+
addLines = [];
|
|
23725
|
+
ops.push({ kind: "add", path, lines: addLines });
|
|
23726
|
+
} else if (header === PATCH_UPDATE_HEADER) {
|
|
23727
|
+
hunks = [];
|
|
23728
|
+
ops.push({ kind: "update", path, movedTo: null, hunks });
|
|
23729
|
+
} else {
|
|
23730
|
+
ops.push({ kind: "delete", path });
|
|
23731
|
+
}
|
|
23732
|
+
lastWasUpdate = header === PATCH_UPDATE_HEADER;
|
|
23529
23733
|
}
|
|
23530
|
-
|
|
23531
|
-
const run = buildGuardEvalRun({
|
|
23532
|
-
engagementId: guardEngagementId,
|
|
23533
|
-
runType: "hook",
|
|
23534
|
-
// The phase this run belongs to, as the client named it — it names the
|
|
23535
|
-
// guard-eval log file the run is appended to. Absent → the run is unphased.
|
|
23536
|
-
phase: telemetryConfig.phase,
|
|
23537
|
-
outcome: "evaluated",
|
|
23538
|
-
changeSet,
|
|
23539
|
-
result: clientResult.envelope
|
|
23540
|
-
});
|
|
23541
|
-
await emitTelemetry(run, {
|
|
23542
|
-
controlTelemetryUrl: telemetryConfig.controlTelemetryUrl,
|
|
23543
|
-
// THE CREDENTIAL IN FORCE, NOT THE ONE IN THE ENVIRONMENT (T-08,
|
|
23544
|
-
// credential-lifecycle). `readTelemetryConfig` reads the bearer off
|
|
23545
|
-
// `process.env` after the evaluation, and a renewal does not write there — so
|
|
23546
|
-
// taking its value on a run that just recovered would present the credential
|
|
23547
|
-
// the plane had already refused and drop the record with a 401, silently,
|
|
23548
|
-
// because telemetry is best-effort.
|
|
23549
|
-
controlTelemetryToken: evaluation.token,
|
|
23550
|
-
// Per-engagement log dir from HALFCYCLE_GUARD_EVAL_LOG_DIR (E1-T-02, #61).
|
|
23551
|
-
guardEvalLogDir: telemetryConfig.guardEvalLogDir
|
|
23552
|
-
}, {
|
|
23553
|
-
warn: (msg) => process.stderr.write(msg + "\n")
|
|
23554
|
-
});
|
|
23555
|
-
actOnEnvelope2(clientResult.envelope, reply);
|
|
23734
|
+
return ops;
|
|
23556
23735
|
}
|
|
23557
|
-
function
|
|
23558
|
-
|
|
23559
|
-
const notRunBlock = notRun ? `
|
|
23560
|
-
${notRun}` : "";
|
|
23561
|
-
if (envelope.guardsFired.length === 0) {
|
|
23562
|
-
if (notRun)
|
|
23563
|
-
reply.notice(notRun);
|
|
23564
|
-
return;
|
|
23565
|
-
}
|
|
23566
|
-
if (envelope.blocking) {
|
|
23567
|
-
const personNotRun = notRunMessage((envelope.notRun ?? []).map((n) => n.reason));
|
|
23568
|
-
const reason = `${blockReason(envelope.guardsFired.map((g) => g.explanation))}${personNotRun ? `
|
|
23569
|
-
${personNotRun}` : ""}`;
|
|
23570
|
-
reply.block(reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
23571
|
-
} else {
|
|
23572
|
-
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
23573
|
-
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
23574
|
-
${guardLines}${notRunBlock}`);
|
|
23575
|
-
}
|
|
23736
|
+
function isUpdateLine(line2) {
|
|
23737
|
+
return line2 === "" || line2.startsWith(" ") || line2.startsWith("+") || line2.startsWith("-") || line2.startsWith("@@");
|
|
23576
23738
|
}
|
|
23577
|
-
function
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
23582
|
-
process.stdin.on("error", reject);
|
|
23583
|
-
});
|
|
23739
|
+
function headerPath(trimmed, header) {
|
|
23740
|
+
if (trimmed === header.trimEnd())
|
|
23741
|
+
return "";
|
|
23742
|
+
return trimmed.startsWith(header) ? trimmed.slice(header.length).trim() : void 0;
|
|
23584
23743
|
}
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
import { join as join11 } from "node:path";
|
|
23589
|
-
var LIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
23590
|
-
function field(input, key) {
|
|
23591
|
-
if (typeof input !== "object" || input === null)
|
|
23744
|
+
function patchHeaderPaths(patch) {
|
|
23745
|
+
const ops = patchOperations(patch);
|
|
23746
|
+
if (ops === null)
|
|
23592
23747
|
return null;
|
|
23593
|
-
|
|
23594
|
-
return typeof v === "string" && v !== "" ? v : null;
|
|
23748
|
+
return ops.map((op) => op.kind === "update" && op.movedTo !== null ? op.movedTo : op.path);
|
|
23595
23749
|
}
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
}
|
|
23613
|
-
closeSync2(openSync2(join11(tmp, name), "a", 384));
|
|
23614
|
-
renameSync5(tmp, folder);
|
|
23615
|
-
return true;
|
|
23616
|
-
} catch {
|
|
23617
|
-
if (tmp !== null)
|
|
23618
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
23619
|
-
}
|
|
23620
|
-
}
|
|
23621
|
-
return false;
|
|
23750
|
+
|
|
23751
|
+
// dist/pacing.js
|
|
23752
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
23753
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync4, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync14, readSync, realpathSync as realpathSync5, renameSync as renameSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
23754
|
+
import { homedir as homedir5 } from "node:os";
|
|
23755
|
+
import nodePath from "node:path";
|
|
23756
|
+
var DEFAULT_CONFIG_FOLDER_NAME = ".claude";
|
|
23757
|
+
var CODEX_DEFAULT_FOLDER_NAME = ".codex";
|
|
23758
|
+
var RECORDS_FOLDER_NAME = "projects";
|
|
23759
|
+
function hookInputForRule(input, rule) {
|
|
23760
|
+
if (rule === "claude-config" || typeof input !== "object" || input === null || Array.isArray(input))
|
|
23761
|
+
return input;
|
|
23762
|
+
const copy = { ...input };
|
|
23763
|
+
delete copy["transcript_path"];
|
|
23764
|
+
delete copy["agent_transcript_path"];
|
|
23765
|
+
return copy;
|
|
23622
23766
|
}
|
|
23623
|
-
function recordLiveness(input, deps = {}) {
|
|
23624
|
-
try {
|
|
23625
|
-
const sessionId = field(input, "session_id");
|
|
23626
|
-
const agentId = field(input, "agent_id");
|
|
23627
|
-
const cwd = field(input, "cwd");
|
|
23628
|
-
if (sessionId === null || cwd === null || !livenessIdOk(sessionId))
|
|
23629
|
-
return null;
|
|
23630
|
-
if (agentId !== null && !livenessIdOk(agentId))
|
|
23631
|
-
return null;
|
|
23632
|
-
if (/[\r\n]/.test(cwd))
|
|
23633
|
-
return null;
|
|
23634
|
-
const copy = whichCopy(cwd, deps.walk);
|
|
23635
|
-
const dir = deps.dir ?? markerDir();
|
|
23636
|
-
const name = livenessFileName(livenessKey(copy?.common ?? null), sessionId, agentId);
|
|
23637
|
-
if (!indexEntry(dir, sessionId, name))
|
|
23638
|
-
return null;
|
|
23639
|
-
const path = join11(dir, name);
|
|
23640
|
-
const text = `${cwd}
|
|
23641
|
-
${copy?.top ?? ""}
|
|
23642
|
-
`;
|
|
23643
|
-
try {
|
|
23644
|
-
writeFileSync7(path, text, { encoding: "utf-8", flag: "wx", mode: 384 });
|
|
23645
|
-
return { firstSeen: true };
|
|
23646
|
-
} catch (err) {
|
|
23647
|
-
if (err.code !== "EEXIST")
|
|
23648
|
-
return null;
|
|
23649
|
-
}
|
|
23650
|
-
writeFileSync7(path, text, { encoding: "utf-8", mode: 384 });
|
|
23651
|
-
return { firstSeen: false };
|
|
23652
|
-
} catch {
|
|
23653
|
-
return null;
|
|
23654
|
-
}
|
|
23655
|
-
}
|
|
23656
|
-
function clearLiveness(input, deps = {}) {
|
|
23657
|
-
try {
|
|
23658
|
-
const sessionId = field(input, "session_id");
|
|
23659
|
-
const agentId = field(input, "agent_id");
|
|
23660
|
-
if (sessionId === null || agentId === null)
|
|
23661
|
-
return 0;
|
|
23662
|
-
const dir = deps.dir ?? markerDir();
|
|
23663
|
-
let removed = 0;
|
|
23664
|
-
for (const name of readdirSync5(dir)) {
|
|
23665
|
-
const p = parseLivenessFileName(name);
|
|
23666
|
-
if (p === null || p.sessionId !== sessionId || p.agentId !== agentId)
|
|
23667
|
-
continue;
|
|
23668
|
-
try {
|
|
23669
|
-
unlinkSync6(join11(dir, name));
|
|
23670
|
-
removed += 1;
|
|
23671
|
-
} catch {
|
|
23672
|
-
}
|
|
23673
|
-
removeLivenessIndexEntry(dir, sessionId, name);
|
|
23674
|
-
}
|
|
23675
|
-
return removed;
|
|
23676
|
-
} catch {
|
|
23677
|
-
return 0;
|
|
23678
|
-
}
|
|
23679
|
-
}
|
|
23680
|
-
function topOf(path) {
|
|
23681
|
-
try {
|
|
23682
|
-
const top = readFileSync14(path, "utf-8").split("\n")[1] ?? "";
|
|
23683
|
-
return top === "" ? null : top;
|
|
23684
|
-
} catch {
|
|
23685
|
-
return null;
|
|
23686
|
-
}
|
|
23687
|
-
}
|
|
23688
|
-
function readLiveness(sessionId, cwd, deps = {}) {
|
|
23689
|
-
try {
|
|
23690
|
-
if (!sessionId || !cwd || !livenessIdOk(sessionId))
|
|
23691
|
-
return null;
|
|
23692
|
-
const copy = whichCopy(cwd, deps.walk);
|
|
23693
|
-
if (copy === null)
|
|
23694
|
-
return null;
|
|
23695
|
-
const key = livenessKey(copy.common);
|
|
23696
|
-
const dir = deps.dir ?? markerDir();
|
|
23697
|
-
const now = (deps.now ?? Date.now)();
|
|
23698
|
-
let main2 = null;
|
|
23699
|
-
const agents = [];
|
|
23700
|
-
for (const name of readdirSync5(dir)) {
|
|
23701
|
-
const p = parseLivenessFileName(name);
|
|
23702
|
-
if (p === null || p.key !== key || p.sessionId !== sessionId)
|
|
23703
|
-
continue;
|
|
23704
|
-
const full = join11(dir, name);
|
|
23705
|
-
let seenAt;
|
|
23706
|
-
try {
|
|
23707
|
-
seenAt = statSync4(full).mtimeMs;
|
|
23708
|
-
} catch {
|
|
23709
|
-
continue;
|
|
23710
|
-
}
|
|
23711
|
-
const top = topOf(full);
|
|
23712
|
-
if (top === null)
|
|
23713
|
-
continue;
|
|
23714
|
-
if (p.agentId === null)
|
|
23715
|
-
main2 = { top, seenAt };
|
|
23716
|
-
else if (now - seenAt <= LIVE_WINDOW_MS)
|
|
23717
|
-
agents.push({ agentId: p.agentId, top, seenAt });
|
|
23718
|
-
}
|
|
23719
|
-
agents.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
23720
|
-
return { main: main2, agents };
|
|
23721
|
-
} catch {
|
|
23722
|
-
return null;
|
|
23723
|
-
}
|
|
23724
|
-
}
|
|
23725
|
-
function liveCount(record2) {
|
|
23726
|
-
return record2 === null ? 0 : record2.agents.length;
|
|
23727
|
-
}
|
|
23728
|
-
function withoutAgent(record2, agentId) {
|
|
23729
|
-
if (record2 === null || agentId === null || agentId === void 0 || agentId === "")
|
|
23730
|
-
return record2;
|
|
23731
|
-
return { main: record2.main, agents: record2.agents.filter((a) => a.agentId !== agentId) };
|
|
23732
|
-
}
|
|
23733
|
-
function runsBesideOtherWork(toolInput, record2) {
|
|
23734
|
-
const t = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
|
|
23735
|
-
const bg = t["run_in_background"];
|
|
23736
|
-
if (bg !== false && bg !== "false")
|
|
23737
|
-
return true;
|
|
23738
|
-
const name = t["name"];
|
|
23739
|
-
if (name !== void 0 && name !== null && name !== "")
|
|
23740
|
-
return true;
|
|
23741
|
-
return liveCount(record2) > 0;
|
|
23742
|
-
}
|
|
23743
|
-
|
|
23744
|
-
// dist/pace-hook.js
|
|
23745
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
23746
|
-
import nodePath4 from "node:path";
|
|
23747
|
-
|
|
23748
|
-
// dist/pace-verb.js
|
|
23749
|
-
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
23750
|
-
import { homedir as homedir6 } from "node:os";
|
|
23751
|
-
import nodePath3 from "node:path";
|
|
23752
|
-
|
|
23753
|
-
// dist/pacing.js
|
|
23754
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
23755
|
-
import { closeSync as closeSync3, mkdirSync as mkdirSync4, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync15, readSync, realpathSync as realpathSync5, renameSync as renameSync6, statSync as statSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
23756
|
-
import { homedir as homedir5 } from "node:os";
|
|
23757
|
-
import nodePath from "node:path";
|
|
23758
|
-
var DEFAULT_CONFIG_FOLDER_NAME = ".claude";
|
|
23759
|
-
var RECORDS_FOLDER_NAME = "projects";
|
|
23760
23767
|
function normaliseFolder(folder, input = {}) {
|
|
23761
23768
|
const p = input.path ?? nodePath;
|
|
23762
23769
|
const home = input.home ?? homedir5();
|
|
@@ -23770,6 +23777,12 @@ function normaliseFolder(folder, input = {}) {
|
|
|
23770
23777
|
function configFolderKey(input = {}) {
|
|
23771
23778
|
const p = input.path ?? nodePath;
|
|
23772
23779
|
const env = input.env ?? process.env;
|
|
23780
|
+
if (input.rule === "codex-home") {
|
|
23781
|
+
const codexHome = env["CODEX_HOME"];
|
|
23782
|
+
if (typeof codexHome === "string" && codexHome !== "")
|
|
23783
|
+
return normaliseFolder(codexHome, input);
|
|
23784
|
+
return normaliseFolder(p.join(input.home ?? homedir5(), CODEX_DEFAULT_FOLDER_NAME), input);
|
|
23785
|
+
}
|
|
23773
23786
|
const fromEnv = env["CLAUDE_CONFIG_DIR"];
|
|
23774
23787
|
if (typeof fromEnv === "string" && fromEnv !== "")
|
|
23775
23788
|
return normaliseFolder(fromEnv, input);
|
|
@@ -23801,7 +23814,7 @@ function pacingDir(home) {
|
|
|
23801
23814
|
}
|
|
23802
23815
|
function readJsonObject(path) {
|
|
23803
23816
|
try {
|
|
23804
|
-
const v = JSON.parse(
|
|
23817
|
+
const v = JSON.parse(readFileSync14(path, "utf-8"));
|
|
23805
23818
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
23806
23819
|
} catch {
|
|
23807
23820
|
return null;
|
|
@@ -23810,8 +23823,8 @@ function readJsonObject(path) {
|
|
|
23810
23823
|
function writeJsonWhole(path, value) {
|
|
23811
23824
|
mkdirSync4(nodePath.dirname(path), { recursive: true, mode: 448 });
|
|
23812
23825
|
const tmp = `${path}.${String(process.pid)}.${createHash4("sha1").update(String(Math.random())).digest("hex").slice(0, 8)}.tmp`;
|
|
23813
|
-
|
|
23814
|
-
|
|
23826
|
+
writeFileSync7(tmp, JSON.stringify(value), { mode: 384 });
|
|
23827
|
+
renameSync5(tmp, path);
|
|
23815
23828
|
}
|
|
23816
23829
|
function writeAccountEntry(path, key, entry) {
|
|
23817
23830
|
const current = readJsonObject(path);
|
|
@@ -23968,7 +23981,7 @@ function readLearnedAccount(key, deps = {}) {
|
|
|
23968
23981
|
}
|
|
23969
23982
|
function dirents(path) {
|
|
23970
23983
|
try {
|
|
23971
|
-
return
|
|
23984
|
+
return readdirSync5(path, { withFileTypes: true });
|
|
23972
23985
|
} catch {
|
|
23973
23986
|
return [];
|
|
23974
23987
|
}
|
|
@@ -24002,7 +24015,7 @@ var NEWLINE = 10;
|
|
|
24002
24015
|
var ASSISTANT = Buffer.from('"assistant"');
|
|
24003
24016
|
var READ_CHUNK = 8 * 1024 * 1024;
|
|
24004
24017
|
function readWholeLines(path, from, size, onLine) {
|
|
24005
|
-
const fd =
|
|
24018
|
+
const fd = openSync2(path, "r");
|
|
24006
24019
|
try {
|
|
24007
24020
|
let pos = from;
|
|
24008
24021
|
let carry = Buffer.alloc(0);
|
|
@@ -24030,7 +24043,7 @@ function readWholeLines(path, from, size, onLine) {
|
|
|
24030
24043
|
}
|
|
24031
24044
|
return consumed;
|
|
24032
24045
|
} finally {
|
|
24033
|
-
|
|
24046
|
+
closeSync2(fd);
|
|
24034
24047
|
}
|
|
24035
24048
|
}
|
|
24036
24049
|
function hashId(id) {
|
|
@@ -24059,7 +24072,7 @@ function scanRecords(key, deps = {}) {
|
|
|
24059
24072
|
let size;
|
|
24060
24073
|
let mtimeSec;
|
|
24061
24074
|
try {
|
|
24062
|
-
const st =
|
|
24075
|
+
const st = statSync4(file2.path);
|
|
24063
24076
|
size = st.size;
|
|
24064
24077
|
mtimeSec = Math.floor(st.mtimeMs / 1e3);
|
|
24065
24078
|
} catch {
|
|
@@ -24195,7 +24208,7 @@ function timeLogTranscripts(home) {
|
|
|
24195
24208
|
continue;
|
|
24196
24209
|
let text;
|
|
24197
24210
|
try {
|
|
24198
|
-
text =
|
|
24211
|
+
text = readFileSync14(nodePath.join(engagements, e.name, TIME_LOG_DIR_NAME, TIME_LOG_FILENAME), "utf-8");
|
|
24199
24212
|
} catch {
|
|
24200
24213
|
continue;
|
|
24201
24214
|
}
|
|
@@ -24350,9 +24363,395 @@ function knownWindowOf(learned, reported, now) {
|
|
|
24350
24363
|
if (learned?.ceilings[kind] !== void 0 || reportedFresh(reported[kind], now))
|
|
24351
24364
|
return kind;
|
|
24352
24365
|
}
|
|
24353
|
-
return null;
|
|
24366
|
+
return null;
|
|
24367
|
+
}
|
|
24368
|
+
|
|
24369
|
+
// dist/hooks/post-tool-use.js
|
|
24370
|
+
import { isAbsolute as isAbsolute2, join as join11, relative as relative5, resolve as resolve4, sep as sep3 } from "node:path";
|
|
24371
|
+
var HANDLED_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
24372
|
+
async function runPostToolUse(readSeat = readSeatConfiguration, profile = AGENT_PROFILES["claude-code"]) {
|
|
24373
|
+
const reply = hookReply("PostToolUse", profile);
|
|
24374
|
+
const at = { session: void 0, folder: null };
|
|
24375
|
+
await evaluateEdit(reply, readSeat, at, profile);
|
|
24376
|
+
handOverAtEdit(reply, at);
|
|
24377
|
+
reply.flush();
|
|
24378
|
+
}
|
|
24379
|
+
function handOverAtEdit(reply, at) {
|
|
24380
|
+
if (at.folder === null)
|
|
24381
|
+
return;
|
|
24382
|
+
const code = process.exitCode;
|
|
24383
|
+
if (code !== void 0 && code !== null && Number(code) !== 0)
|
|
24384
|
+
return;
|
|
24385
|
+
const given = deliverFindings(at.session, at.folder);
|
|
24386
|
+
if (given.length === 0)
|
|
24387
|
+
return;
|
|
24388
|
+
reply.agentContext(detailsBlock(given, realPathOf(projectFolder())));
|
|
24389
|
+
}
|
|
24390
|
+
async function evaluateEdit(reply, readSeat, at, profile) {
|
|
24391
|
+
let input;
|
|
24392
|
+
try {
|
|
24393
|
+
const raw = await readStdin();
|
|
24394
|
+
input = hookInputForRule(JSON.parse(raw), profile.configFolder);
|
|
24395
|
+
} catch {
|
|
24396
|
+
recordTimeEvent("edit", {});
|
|
24397
|
+
return;
|
|
24398
|
+
}
|
|
24399
|
+
recordTimeEvent("edit", input);
|
|
24400
|
+
const toolName = input.tool_name ?? "";
|
|
24401
|
+
const edited = editedPaths(input, profile);
|
|
24402
|
+
if (edited === UNREADABLE_EDIT) {
|
|
24403
|
+
reply.personNotice(unreadableEditText());
|
|
24404
|
+
return;
|
|
24405
|
+
}
|
|
24406
|
+
if (edited.length === 0)
|
|
24407
|
+
return;
|
|
24408
|
+
for (const absFilePath of edited)
|
|
24409
|
+
recordOwnEdit(input, absFilePath);
|
|
24410
|
+
const repoRootResult = getRepoRoot(process.cwd());
|
|
24411
|
+
if (!repoRootResult.ok) {
|
|
24412
|
+
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
24413
|
+
reply.notice(noRepositoryText(repoRootResult));
|
|
24414
|
+
}
|
|
24415
|
+
return;
|
|
24416
|
+
}
|
|
24417
|
+
const repoRoot = repoRootResult.value;
|
|
24418
|
+
at.session = input.session_id;
|
|
24419
|
+
at.folder = realPathOf(repoRoot);
|
|
24420
|
+
const files = [];
|
|
24421
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24422
|
+
for (const named of edited) {
|
|
24423
|
+
const absFilePath = profile.id === "claude-code" ? named : insideRepository(named, repoRoot);
|
|
24424
|
+
if (absFilePath === null)
|
|
24425
|
+
continue;
|
|
24426
|
+
const relPath = relative5(repoRoot, absFilePath);
|
|
24427
|
+
if (isInstallerOutput(relPath) || seen.has(relPath))
|
|
24428
|
+
continue;
|
|
24429
|
+
seen.add(relPath);
|
|
24430
|
+
const diffContent = toolName === "NotebookEdit" ? "" : getSingleFileDiff(absFilePath, repoRoot);
|
|
24431
|
+
files.push({ path: relPath, diffContent });
|
|
24432
|
+
}
|
|
24433
|
+
if (files.length === 0)
|
|
24434
|
+
return;
|
|
24435
|
+
const context = loadChangeSetContext(repoRoot);
|
|
24436
|
+
const changeSet = buildChangeSet(files, {
|
|
24437
|
+
...context,
|
|
24438
|
+
excerpts: readExcerptConfig()
|
|
24439
|
+
});
|
|
24440
|
+
const configResult = readConfig();
|
|
24441
|
+
if (!configResult.config) {
|
|
24442
|
+
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
24443
|
+
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
24444
|
+
return;
|
|
24445
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()), reply);
|
|
24446
|
+
return;
|
|
24447
|
+
}
|
|
24448
|
+
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
24449
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat, profile);
|
|
24450
|
+
const evaluation = await evaluateWithRefresh({
|
|
24451
|
+
guardServiceUrl,
|
|
24452
|
+
token: guardServiceToken,
|
|
24453
|
+
engagementId: guardEngagementId,
|
|
24454
|
+
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration),
|
|
24455
|
+
warn: warnOnceThisSession("credential-renewed-for-this-run", repoRoot, input.session_id)
|
|
24456
|
+
});
|
|
24457
|
+
const clientResult = evaluation.result;
|
|
24458
|
+
if (!clientResult.ok) {
|
|
24459
|
+
const failure = classifyFailure(clientResult);
|
|
24460
|
+
await emitRunRecord({
|
|
24461
|
+
engagementId: guardEngagementId,
|
|
24462
|
+
runType: "hook",
|
|
24463
|
+
outcome: outcomeForClientFailure(clientResult.kind),
|
|
24464
|
+
failureReason: failure.reason,
|
|
24465
|
+
changeSet,
|
|
24466
|
+
credential: evaluation.token,
|
|
24467
|
+
credentialRefused: failure.kind === "credential"
|
|
24468
|
+
});
|
|
24469
|
+
if (failure.kind === "credential") {
|
|
24470
|
+
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
24471
|
+
return;
|
|
24472
|
+
emitCredentialRejected(
|
|
24473
|
+
failure.statusCode,
|
|
24474
|
+
failure.message,
|
|
24475
|
+
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
24476
|
+
// The remedy the renewal attempt earned: the sign-in when the account
|
|
24477
|
+
// credential is the thing that was refused, and otherwise the plane's own
|
|
24478
|
+
// sentence about why it would not mint.
|
|
24479
|
+
evaluation.remedy,
|
|
24480
|
+
reply
|
|
24481
|
+
);
|
|
24482
|
+
return;
|
|
24483
|
+
}
|
|
24484
|
+
if (failure.kind === "contract") {
|
|
24485
|
+
reply.notice(contractFaultText(failure.message));
|
|
24486
|
+
return;
|
|
24487
|
+
}
|
|
24488
|
+
reply.notice(infraWarningText());
|
|
24489
|
+
return;
|
|
24490
|
+
}
|
|
24491
|
+
const telemetryConfig = readTelemetryConfig();
|
|
24492
|
+
const run = buildGuardEvalRun({
|
|
24493
|
+
engagementId: guardEngagementId,
|
|
24494
|
+
runType: "hook",
|
|
24495
|
+
// The phase this run belongs to, as the client named it — it names the
|
|
24496
|
+
// guard-eval log file the run is appended to. Absent → the run is unphased.
|
|
24497
|
+
phase: telemetryConfig.phase,
|
|
24498
|
+
outcome: "evaluated",
|
|
24499
|
+
changeSet,
|
|
24500
|
+
result: clientResult.envelope
|
|
24501
|
+
});
|
|
24502
|
+
await emitTelemetry(run, {
|
|
24503
|
+
controlTelemetryUrl: telemetryConfig.controlTelemetryUrl,
|
|
24504
|
+
// THE CREDENTIAL IN FORCE, NOT THE ONE IN THE ENVIRONMENT (T-08,
|
|
24505
|
+
// credential-lifecycle). `readTelemetryConfig` reads the bearer off
|
|
24506
|
+
// `process.env` after the evaluation, and a renewal does not write there — so
|
|
24507
|
+
// taking its value on a run that just recovered would present the credential
|
|
24508
|
+
// the plane had already refused and drop the record with a 401, silently,
|
|
24509
|
+
// because telemetry is best-effort.
|
|
24510
|
+
controlTelemetryToken: evaluation.token,
|
|
24511
|
+
// Per-engagement log dir from HALFCYCLE_GUARD_EVAL_LOG_DIR (E1-T-02, #61).
|
|
24512
|
+
guardEvalLogDir: telemetryConfig.guardEvalLogDir
|
|
24513
|
+
}, {
|
|
24514
|
+
warn: (msg) => process.stderr.write(msg + "\n")
|
|
24515
|
+
});
|
|
24516
|
+
actOnEnvelope2(clientResult.envelope, reply);
|
|
24517
|
+
}
|
|
24518
|
+
var UNREADABLE_EDIT = /* @__PURE__ */ Symbol("unreadable-edit");
|
|
24519
|
+
function editedPaths(input, profile) {
|
|
24520
|
+
const toolName = input.tool_name ?? "";
|
|
24521
|
+
if (profile.id === "claude-code") {
|
|
24522
|
+
if (!HANDLED_EDIT_TOOLS.has(toolName))
|
|
24523
|
+
return [];
|
|
24524
|
+
const absFilePath = toolName === "NotebookEdit" ? input.tool_input?.notebook_path : input.tool_input?.file_path;
|
|
24525
|
+
if (!absFilePath || typeof absFilePath !== "string")
|
|
24526
|
+
return [];
|
|
24527
|
+
return [absFilePath];
|
|
24528
|
+
}
|
|
24529
|
+
if (!profile.writeCapableTools.includes(toolName))
|
|
24530
|
+
return [];
|
|
24531
|
+
const filePath = input.tool_input?.file_path;
|
|
24532
|
+
const command = input.tool_input?.command;
|
|
24533
|
+
let named;
|
|
24534
|
+
if (typeof filePath === "string" && filePath !== "")
|
|
24535
|
+
named = [filePath];
|
|
24536
|
+
else if (typeof command === "string")
|
|
24537
|
+
named = patchHeaderPaths(command);
|
|
24538
|
+
else
|
|
24539
|
+
named = null;
|
|
24540
|
+
if (named === null || named.length === 0)
|
|
24541
|
+
return UNREADABLE_EDIT;
|
|
24542
|
+
const cwd = typeof input.cwd === "string" && isAbsolute2(input.cwd) ? input.cwd : null;
|
|
24543
|
+
const out = [];
|
|
24544
|
+
for (const path of named) {
|
|
24545
|
+
if (!isAbsolute2(path) && cwd === null)
|
|
24546
|
+
return UNREADABLE_EDIT;
|
|
24547
|
+
const abs = isAbsolute2(path) ? resolve4(path) : resolve4(cwd, path);
|
|
24548
|
+
if (!out.includes(abs))
|
|
24549
|
+
out.push(abs);
|
|
24550
|
+
}
|
|
24551
|
+
return out;
|
|
24552
|
+
}
|
|
24553
|
+
function insideRepository(path, repoRoot) {
|
|
24554
|
+
const rel = relative5(realPathOf(repoRoot), realPathOf(path));
|
|
24555
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep3}`) || isAbsolute2(rel))
|
|
24556
|
+
return null;
|
|
24557
|
+
return join11(repoRoot, rel);
|
|
24558
|
+
}
|
|
24559
|
+
function actOnEnvelope2(envelope, reply) {
|
|
24560
|
+
const notRun = notRunReport(envelope.notRun);
|
|
24561
|
+
const notRunBlock = notRun ? `
|
|
24562
|
+
${notRun}` : "";
|
|
24563
|
+
if (envelope.guardsFired.length === 0) {
|
|
24564
|
+
if (notRun)
|
|
24565
|
+
reply.notice(notRun);
|
|
24566
|
+
return;
|
|
24567
|
+
}
|
|
24568
|
+
if (envelope.blocking) {
|
|
24569
|
+
const personNotRun = notRunMessage((envelope.notRun ?? []).map((n) => n.reason));
|
|
24570
|
+
const reason = `${blockReason(envelope.guardsFired.map((g) => g.explanation))}${personNotRun ? `
|
|
24571
|
+
${personNotRun}` : ""}`;
|
|
24572
|
+
reply.block(reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
24573
|
+
} else {
|
|
24574
|
+
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
24575
|
+
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
24576
|
+
${guardLines}${notRunBlock}`);
|
|
24577
|
+
}
|
|
24578
|
+
}
|
|
24579
|
+
function readStdin() {
|
|
24580
|
+
return new Promise((resolve10, reject) => {
|
|
24581
|
+
const chunks = [];
|
|
24582
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
24583
|
+
process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString("utf-8")));
|
|
24584
|
+
process.stdin.on("error", reject);
|
|
24585
|
+
});
|
|
24586
|
+
}
|
|
24587
|
+
|
|
24588
|
+
// dist/liveness.js
|
|
24589
|
+
import { closeSync as closeSync3, mkdtempSync, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync15, renameSync as renameSync6, rmSync, statSync as statSync5, unlinkSync as unlinkSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
24590
|
+
import { join as join12 } from "node:path";
|
|
24591
|
+
var LIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
24592
|
+
function field(input, key) {
|
|
24593
|
+
if (typeof input !== "object" || input === null)
|
|
24594
|
+
return null;
|
|
24595
|
+
const v = input[key];
|
|
24596
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
24597
|
+
}
|
|
24598
|
+
function indexEntry(dir, sessionId, name) {
|
|
24599
|
+
const folder = join12(dir, livenessIndexDirName(sessionId));
|
|
24600
|
+
for (let attempt2 = 0; attempt2 < 3; attempt2++) {
|
|
24601
|
+
try {
|
|
24602
|
+
closeSync3(openSync3(join12(folder, name), "a", 384));
|
|
24603
|
+
return true;
|
|
24604
|
+
} catch (err) {
|
|
24605
|
+
if (err.code !== "ENOENT")
|
|
24606
|
+
return false;
|
|
24607
|
+
}
|
|
24608
|
+
let tmp = null;
|
|
24609
|
+
try {
|
|
24610
|
+
tmp = mkdtempSync(`${folder}.`);
|
|
24611
|
+
for (const existing of readdirSync6(dir)) {
|
|
24612
|
+
if (parseLivenessFileName(existing)?.sessionId === sessionId)
|
|
24613
|
+
closeSync3(openSync3(join12(tmp, existing), "a", 384));
|
|
24614
|
+
}
|
|
24615
|
+
closeSync3(openSync3(join12(tmp, name), "a", 384));
|
|
24616
|
+
renameSync6(tmp, folder);
|
|
24617
|
+
return true;
|
|
24618
|
+
} catch {
|
|
24619
|
+
if (tmp !== null)
|
|
24620
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
24621
|
+
}
|
|
24622
|
+
}
|
|
24623
|
+
return false;
|
|
24624
|
+
}
|
|
24625
|
+
function recordLiveness(input, deps = {}) {
|
|
24626
|
+
try {
|
|
24627
|
+
const sessionId = field(input, "session_id");
|
|
24628
|
+
const agentId = field(input, "agent_id");
|
|
24629
|
+
const cwd = field(input, "cwd");
|
|
24630
|
+
if (sessionId === null || cwd === null || !livenessIdOk(sessionId))
|
|
24631
|
+
return null;
|
|
24632
|
+
if (agentId !== null && !livenessIdOk(agentId))
|
|
24633
|
+
return null;
|
|
24634
|
+
if (/[\r\n]/.test(cwd))
|
|
24635
|
+
return null;
|
|
24636
|
+
const copy = whichCopy(cwd, deps.walk);
|
|
24637
|
+
const dir = deps.dir ?? markerDir();
|
|
24638
|
+
const name = livenessFileName(livenessKey(copy?.common ?? null), sessionId, agentId);
|
|
24639
|
+
if (!indexEntry(dir, sessionId, name))
|
|
24640
|
+
return null;
|
|
24641
|
+
const path = join12(dir, name);
|
|
24642
|
+
const text = `${cwd}
|
|
24643
|
+
${copy?.top ?? ""}
|
|
24644
|
+
`;
|
|
24645
|
+
try {
|
|
24646
|
+
writeFileSync8(path, text, { encoding: "utf-8", flag: "wx", mode: 384 });
|
|
24647
|
+
return { firstSeen: true };
|
|
24648
|
+
} catch (err) {
|
|
24649
|
+
if (err.code !== "EEXIST")
|
|
24650
|
+
return null;
|
|
24651
|
+
}
|
|
24652
|
+
writeFileSync8(path, text, { encoding: "utf-8", mode: 384 });
|
|
24653
|
+
return { firstSeen: false };
|
|
24654
|
+
} catch {
|
|
24655
|
+
return null;
|
|
24656
|
+
}
|
|
24657
|
+
}
|
|
24658
|
+
function clearLiveness(input, deps = {}) {
|
|
24659
|
+
try {
|
|
24660
|
+
const sessionId = field(input, "session_id");
|
|
24661
|
+
const agentId = field(input, "agent_id");
|
|
24662
|
+
if (sessionId === null || agentId === null)
|
|
24663
|
+
return 0;
|
|
24664
|
+
const dir = deps.dir ?? markerDir();
|
|
24665
|
+
let removed = 0;
|
|
24666
|
+
for (const name of readdirSync6(dir)) {
|
|
24667
|
+
const p = parseLivenessFileName(name);
|
|
24668
|
+
if (p === null || p.sessionId !== sessionId || p.agentId !== agentId)
|
|
24669
|
+
continue;
|
|
24670
|
+
try {
|
|
24671
|
+
unlinkSync6(join12(dir, name));
|
|
24672
|
+
removed += 1;
|
|
24673
|
+
} catch {
|
|
24674
|
+
}
|
|
24675
|
+
removeLivenessIndexEntry(dir, sessionId, name);
|
|
24676
|
+
}
|
|
24677
|
+
return removed;
|
|
24678
|
+
} catch {
|
|
24679
|
+
return 0;
|
|
24680
|
+
}
|
|
24681
|
+
}
|
|
24682
|
+
function topOf(path) {
|
|
24683
|
+
try {
|
|
24684
|
+
const top = readFileSync15(path, "utf-8").split("\n")[1] ?? "";
|
|
24685
|
+
return top === "" ? null : top;
|
|
24686
|
+
} catch {
|
|
24687
|
+
return null;
|
|
24688
|
+
}
|
|
24689
|
+
}
|
|
24690
|
+
function readLiveness(sessionId, cwd, deps = {}) {
|
|
24691
|
+
try {
|
|
24692
|
+
if (!sessionId || !cwd || !livenessIdOk(sessionId))
|
|
24693
|
+
return null;
|
|
24694
|
+
const copy = whichCopy(cwd, deps.walk);
|
|
24695
|
+
if (copy === null)
|
|
24696
|
+
return null;
|
|
24697
|
+
const key = livenessKey(copy.common);
|
|
24698
|
+
const dir = deps.dir ?? markerDir();
|
|
24699
|
+
const now = (deps.now ?? Date.now)();
|
|
24700
|
+
let main2 = null;
|
|
24701
|
+
const agents = [];
|
|
24702
|
+
for (const name of readdirSync6(dir)) {
|
|
24703
|
+
const p = parseLivenessFileName(name);
|
|
24704
|
+
if (p === null || p.key !== key || p.sessionId !== sessionId)
|
|
24705
|
+
continue;
|
|
24706
|
+
const full = join12(dir, name);
|
|
24707
|
+
let seenAt;
|
|
24708
|
+
try {
|
|
24709
|
+
seenAt = statSync5(full).mtimeMs;
|
|
24710
|
+
} catch {
|
|
24711
|
+
continue;
|
|
24712
|
+
}
|
|
24713
|
+
const top = topOf(full);
|
|
24714
|
+
if (top === null)
|
|
24715
|
+
continue;
|
|
24716
|
+
if (p.agentId === null)
|
|
24717
|
+
main2 = { top, seenAt };
|
|
24718
|
+
else if (now - seenAt <= LIVE_WINDOW_MS)
|
|
24719
|
+
agents.push({ agentId: p.agentId, top, seenAt });
|
|
24720
|
+
}
|
|
24721
|
+
agents.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
24722
|
+
return { main: main2, agents };
|
|
24723
|
+
} catch {
|
|
24724
|
+
return null;
|
|
24725
|
+
}
|
|
24726
|
+
}
|
|
24727
|
+
function liveCount(record2) {
|
|
24728
|
+
return record2 === null ? 0 : record2.agents.length;
|
|
24729
|
+
}
|
|
24730
|
+
function withoutAgent(record2, agentId) {
|
|
24731
|
+
if (record2 === null || agentId === null || agentId === void 0 || agentId === "")
|
|
24732
|
+
return record2;
|
|
24733
|
+
return { main: record2.main, agents: record2.agents.filter((a) => a.agentId !== agentId) };
|
|
24734
|
+
}
|
|
24735
|
+
function runsBesideOtherWork(toolInput, record2) {
|
|
24736
|
+
const t = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
|
|
24737
|
+
const bg = t["run_in_background"];
|
|
24738
|
+
if (bg !== false && bg !== "false")
|
|
24739
|
+
return true;
|
|
24740
|
+
const name = t["name"];
|
|
24741
|
+
if (name !== void 0 && name !== null && name !== "")
|
|
24742
|
+
return true;
|
|
24743
|
+
return liveCount(record2) > 0;
|
|
24354
24744
|
}
|
|
24355
24745
|
|
|
24746
|
+
// dist/pace-hook.js
|
|
24747
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
24748
|
+
import nodePath4 from "node:path";
|
|
24749
|
+
|
|
24750
|
+
// dist/pace-verb.js
|
|
24751
|
+
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
24752
|
+
import { homedir as homedir6 } from "node:os";
|
|
24753
|
+
import nodePath3 from "node:path";
|
|
24754
|
+
|
|
24356
24755
|
// dist/scan-lock.js
|
|
24357
24756
|
import { spawn } from "node:child_process";
|
|
24358
24757
|
import { linkSync, mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -24951,7 +25350,7 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
|
|
|
24951
25350
|
return;
|
|
24952
25351
|
const nowMs = (deps.now ?? Date.now)();
|
|
24953
25352
|
const now = new Date(nowMs);
|
|
24954
|
-
const key = configFolderKey({ env, transcriptPath: field2(input, "transcript_path"), home: deps.home });
|
|
25353
|
+
const key = configFolderKey({ env, transcriptPath: field2(input, "transcript_path"), home: deps.home, rule: deps.configFolder });
|
|
24955
25354
|
const stored = readPaceStateFile(dir);
|
|
24956
25355
|
const before = stored.accounts[key] ?? null;
|
|
24957
25356
|
if (before !== null) {
|
|
@@ -25055,7 +25454,7 @@ function whereOf(input, deps) {
|
|
|
25055
25454
|
const dir = enginePacingDir(configured.config.guardEngagementId, deps.home);
|
|
25056
25455
|
if (dir === null)
|
|
25057
25456
|
return null;
|
|
25058
|
-
return { dir, key: configFolderKey({ env, transcriptPath: field2(input, "transcript_path"), home: deps.home }) };
|
|
25457
|
+
return { dir, key: configFolderKey({ env, transcriptPath: field2(input, "transcript_path"), home: deps.home, rule: deps.configFolder }) };
|
|
25059
25458
|
}
|
|
25060
25459
|
function retirePendingStart(input, deps = {}) {
|
|
25061
25460
|
try {
|
|
@@ -25129,7 +25528,7 @@ function paceHoldText(input, toolInput, deps = {}) {
|
|
|
25129
25528
|
// dist/risky/facts.js
|
|
25130
25529
|
import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, statSync as statSync7 } from "node:fs";
|
|
25131
25530
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
|
|
25132
|
-
import { basename as pathBasename, dirname as dirname6, isAbsolute as
|
|
25531
|
+
import { basename as pathBasename, dirname as dirname6, isAbsolute as isAbsolute3, join as join13, relative as relative6, resolve as resolve5, sep as sep4 } from "node:path";
|
|
25133
25532
|
|
|
25134
25533
|
// dist/risky/shell.js
|
|
25135
25534
|
var MAX_DEPTH = 8;
|
|
@@ -27339,13 +27738,13 @@ var Env = class {
|
|
|
27339
27738
|
description;
|
|
27340
27739
|
projectCommonDir;
|
|
27341
27740
|
constructor(ctx) {
|
|
27342
|
-
this.projectDir = realOrSelf(
|
|
27343
|
-
this.cwd = realOrSelf(
|
|
27344
|
-
this.home = realOrSelf(
|
|
27741
|
+
this.projectDir = realOrSelf(resolve5(ctx.projectDir));
|
|
27742
|
+
this.cwd = realOrSelf(resolve5(this.projectDir, ctx.cwd));
|
|
27743
|
+
this.home = realOrSelf(resolve5(ctx.home ?? homedir7()));
|
|
27345
27744
|
this.timeoutMs = ctx.gitTimeoutMs ?? FACTS_GIT_TIMEOUT_MS;
|
|
27346
|
-
this.tempDir = realOrSelf(
|
|
27745
|
+
this.tempDir = realOrSelf(resolve5(ctx.tempDir ?? tmpdir2()));
|
|
27347
27746
|
const roots = ctx.tempRoots ?? [this.tempDir, tmpdir2(), ...TEMP_ROOTS];
|
|
27348
|
-
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(
|
|
27747
|
+
this.tempRoots = [...new Set(roots.map((r) => realOrSelf(resolve5(r))))].filter((r) => !isInside(this.projectDir, r));
|
|
27349
27748
|
}
|
|
27350
27749
|
isTemp(abs) {
|
|
27351
27750
|
return this.tempRoots.some((r) => isInside(abs, r));
|
|
@@ -27355,7 +27754,7 @@ var Env = class {
|
|
|
27355
27754
|
}
|
|
27356
27755
|
display(abs) {
|
|
27357
27756
|
if (isInside(abs, this.projectDir)) {
|
|
27358
|
-
const rel =
|
|
27757
|
+
const rel = relative6(this.projectDir, abs);
|
|
27359
27758
|
return rel === "" ? "." : toSlash(rel);
|
|
27360
27759
|
}
|
|
27361
27760
|
return toSlash(abs);
|
|
@@ -27365,7 +27764,7 @@ var Env = class {
|
|
|
27365
27764
|
return this.description;
|
|
27366
27765
|
this.description = null;
|
|
27367
27766
|
try {
|
|
27368
|
-
const raw = JSON.parse(readFileSync19(
|
|
27767
|
+
const raw = JSON.parse(readFileSync19(join13(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
|
|
27369
27768
|
const checked = checkSystemDescription(raw);
|
|
27370
27769
|
if (checked.ok)
|
|
27371
27770
|
this.description = checked.description;
|
|
@@ -27408,7 +27807,7 @@ var Env = class {
|
|
|
27408
27807
|
if (!r.ok)
|
|
27409
27808
|
return null;
|
|
27410
27809
|
const d = r.value.trim();
|
|
27411
|
-
return d === "" ? null : realOrSelf(
|
|
27810
|
+
return d === "" ? null : realOrSelf(resolve5(dir, d));
|
|
27412
27811
|
}
|
|
27413
27812
|
};
|
|
27414
27813
|
function realOrSelf(p) {
|
|
@@ -27419,29 +27818,29 @@ function realOrSelf(p) {
|
|
|
27419
27818
|
}
|
|
27420
27819
|
}
|
|
27421
27820
|
function toSlash(p) {
|
|
27422
|
-
return
|
|
27821
|
+
return sep4 === "/" ? p : p.split(sep4).join("/");
|
|
27423
27822
|
}
|
|
27424
27823
|
function isInside(child, parent) {
|
|
27425
|
-
const rel =
|
|
27426
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
27824
|
+
const rel = relative6(parent, child);
|
|
27825
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
27427
27826
|
}
|
|
27428
27827
|
function resolveHome(text, home, base) {
|
|
27429
27828
|
if (text === "~")
|
|
27430
27829
|
return home;
|
|
27431
27830
|
if (text.startsWith("~/"))
|
|
27432
|
-
return
|
|
27433
|
-
return
|
|
27831
|
+
return join13(home, text.slice(2));
|
|
27832
|
+
return resolve5(base, text);
|
|
27434
27833
|
}
|
|
27435
27834
|
function realTarget(abs) {
|
|
27436
27835
|
const parent = dirname6(abs);
|
|
27437
27836
|
if (parent === abs)
|
|
27438
27837
|
return abs;
|
|
27439
|
-
return
|
|
27838
|
+
return join13(realOrSelf(parent), pathBasename(abs));
|
|
27440
27839
|
}
|
|
27441
27840
|
function wordPath(word, base, env) {
|
|
27442
27841
|
if (word.temp !== void 0) {
|
|
27443
|
-
const root = word.temp.base === "mktemp" ?
|
|
27444
|
-
return
|
|
27842
|
+
const root = word.temp.base === "mktemp" ? join13(env.tempDir, ".halfcycle-mktemp-new") : env.tempDir;
|
|
27843
|
+
return resolve5(`${root}${word.temp.suffix}`);
|
|
27445
27844
|
}
|
|
27446
27845
|
return word.literal ? resolveHome(word.text, env.home, base) : null;
|
|
27447
27846
|
}
|
|
@@ -27450,7 +27849,7 @@ function follow(start, prefix, dirs, m, env) {
|
|
|
27450
27849
|
let fresh = start.fresh;
|
|
27451
27850
|
let freshRoot = start.freshRoot;
|
|
27452
27851
|
const seen = [...prefix];
|
|
27453
|
-
const insideFresh = (p) => freshRoot !== void 0 && isInside(
|
|
27852
|
+
const insideFresh = (p) => freshRoot !== void 0 && isInside(resolve5(p), resolve5(freshRoot));
|
|
27454
27853
|
const mark = (by) => {
|
|
27455
27854
|
if (fresh !== void 0 && insideFresh(cwd))
|
|
27456
27855
|
return;
|
|
@@ -27579,14 +27978,14 @@ function expandBraces(text) {
|
|
|
27579
27978
|
return body.split(",").map((b) => `${head}${b}${tail}`);
|
|
27580
27979
|
}
|
|
27581
27980
|
function globPath(abs) {
|
|
27582
|
-
const root = abs.startsWith(
|
|
27583
|
-
const segments = abs.split(
|
|
27981
|
+
const root = abs.startsWith(sep4) ? sep4 : "";
|
|
27982
|
+
const segments = abs.split(sep4).filter((s) => s !== "");
|
|
27584
27983
|
let current = [root === "" ? "." : root];
|
|
27585
27984
|
for (const segment of segments) {
|
|
27586
27985
|
const next = [];
|
|
27587
27986
|
if (!/[*?[]/.test(segment)) {
|
|
27588
27987
|
for (const c of current)
|
|
27589
|
-
next.push(
|
|
27988
|
+
next.push(join13(c, segment));
|
|
27590
27989
|
} else {
|
|
27591
27990
|
const re = segmentPattern(segment);
|
|
27592
27991
|
for (const c of current) {
|
|
@@ -27600,7 +27999,7 @@ function globPath(abs) {
|
|
|
27600
27999
|
if (n.startsWith(".") && !segment.startsWith("."))
|
|
27601
28000
|
continue;
|
|
27602
28001
|
if (re.test(n))
|
|
27603
|
-
next.push(
|
|
28002
|
+
next.push(join13(c, n));
|
|
27604
28003
|
}
|
|
27605
28004
|
}
|
|
27606
28005
|
}
|
|
@@ -27665,7 +28064,7 @@ function isDirectory(p) {
|
|
|
27665
28064
|
}
|
|
27666
28065
|
function filesUnder(targets, repoRoot, env, keep, seen) {
|
|
27667
28066
|
const specs = targets.map((t) => {
|
|
27668
|
-
const rel = toSlash(
|
|
28067
|
+
const rel = toSlash(relative6(repoRoot, t));
|
|
27669
28068
|
return rel === "" ? "." : rel;
|
|
27670
28069
|
});
|
|
27671
28070
|
const tracked = env.git(["--literal-pathspecs", "ls-files", "--full-name", "-z", "--", ...specs], repoRoot);
|
|
@@ -27728,12 +28127,12 @@ function findKeeps(filter, roots, repoRoot) {
|
|
|
27728
28127
|
const names = filter.names.map((n) => findPattern(n.pattern, n.fold, false));
|
|
27729
28128
|
const excluded = filter.excluded.map((p) => findPattern(p, false, true));
|
|
27730
28129
|
const steps = (rel) => {
|
|
27731
|
-
const abs =
|
|
28130
|
+
const abs = join13(repoRoot, rel);
|
|
27732
28131
|
const out = [];
|
|
27733
28132
|
for (const r of roots) {
|
|
27734
28133
|
if (!isInside(abs, r.abs))
|
|
27735
28134
|
continue;
|
|
27736
|
-
const below = toSlash(
|
|
28135
|
+
const below = toSlash(relative6(r.abs, abs)).split("/").filter((x) => x !== "");
|
|
27737
28136
|
const rootName = r.text === null ? pathBasename(r.abs) : r.text.replace(/\/+$/, "").split("/").pop() || r.text;
|
|
27738
28137
|
const prefixes = r.text === null ? [] : [.../* @__PURE__ */ new Set([r.text, r.text.replace(/\/+$/, "") || r.text])];
|
|
27739
28138
|
const chain = [{ name: rootName, printed: r.text === null ? [] : [r.text] }];
|
|
@@ -27800,9 +28199,9 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
27800
28199
|
const out = [];
|
|
27801
28200
|
const stack = [];
|
|
27802
28201
|
for (const f of folders) {
|
|
27803
|
-
if (isDirectory(
|
|
28202
|
+
if (isDirectory(join13(repoRoot, f)))
|
|
27804
28203
|
stack.push(f);
|
|
27805
|
-
else if (pathExists(
|
|
28204
|
+
else if (pathExists(join13(repoRoot, f)) && isCredentialPath(f))
|
|
27806
28205
|
out.push(f);
|
|
27807
28206
|
}
|
|
27808
28207
|
let seen = 0;
|
|
@@ -27810,7 +28209,7 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
27810
28209
|
const rel = stack.pop();
|
|
27811
28210
|
let entries;
|
|
27812
28211
|
try {
|
|
27813
|
-
entries = readdirSync8(
|
|
28212
|
+
entries = readdirSync8(join13(repoRoot, rel), { withFileTypes: true });
|
|
27814
28213
|
} catch {
|
|
27815
28214
|
continue;
|
|
27816
28215
|
}
|
|
@@ -27907,7 +28306,7 @@ function deleteFilesFacts(m, env) {
|
|
|
27907
28306
|
for (const raw of expanded) {
|
|
27908
28307
|
const abs = realTarget(raw);
|
|
27909
28308
|
written.set(abs, word.literal ? word.text : null);
|
|
27910
|
-
if (abs ===
|
|
28309
|
+
if (abs === sep4 || abs === env.home || abs !== env.projectDir && isInside(env.projectDir, abs)) {
|
|
27911
28310
|
guarded.push(abs);
|
|
27912
28311
|
} else if (isInside(abs, env.projectDir) || !env.isTemp(abs) && env.sameRepository(abs)) {
|
|
27913
28312
|
if (pathExists(abs))
|
|
@@ -27951,7 +28350,7 @@ function deleteFilesFacts(m, env) {
|
|
|
27951
28350
|
...base.pre.length === 0 ? {
|
|
27952
28351
|
save: {
|
|
27953
28352
|
repoRoot,
|
|
27954
|
-
paths: targets.map((t) => toSlash(
|
|
28353
|
+
paths: targets.map((t) => toSlash(relative6(repoRoot, t)) || "."),
|
|
27955
28354
|
...keep === void 0 ? {} : { keep: (rel) => keep.deletes(rel) },
|
|
27956
28355
|
lines: []
|
|
27957
28356
|
}
|
|
@@ -27963,12 +28362,12 @@ function closeFoldsAll(targets, env) {
|
|
|
27963
28362
|
const root = recordRoot(t, env);
|
|
27964
28363
|
if (root === null || !isInside(t, root))
|
|
27965
28364
|
return false;
|
|
27966
|
-
const rel = toSlash(
|
|
28365
|
+
const rel = toSlash(relative6(root, t));
|
|
27967
28366
|
if (rel === "" || recordPatternFor(rel)?.deleteAsked !== false)
|
|
27968
28367
|
return false;
|
|
27969
28368
|
let at = root;
|
|
27970
28369
|
for (const seg of rel.split("/")) {
|
|
27971
|
-
at =
|
|
28370
|
+
at = join13(at, seg);
|
|
27972
28371
|
try {
|
|
27973
28372
|
if (lstatSync2(at).isSymbolicLink())
|
|
27974
28373
|
return false;
|
|
@@ -28157,11 +28556,11 @@ function cleanFiles(m, where, env) {
|
|
|
28157
28556
|
const repoRoot = env.repoRoot(where.cwd);
|
|
28158
28557
|
if (repoRoot === null)
|
|
28159
28558
|
return null;
|
|
28160
|
-
const abs = named.map((n) =>
|
|
28559
|
+
const abs = named.map((n) => resolve5(where.cwd, n));
|
|
28161
28560
|
const files = filesUnder(abs, repoRoot, env);
|
|
28162
28561
|
if (files === null)
|
|
28163
28562
|
return null;
|
|
28164
|
-
return { lost: files.filter((f) => f.neverSaved).map((f) => f.path), removed: abs.map((a) => toSlash(
|
|
28563
|
+
return { lost: files.filter((f) => f.neverSaved).map((f) => f.path), removed: abs.map((a) => toSlash(relative6(repoRoot, a)) || ".") };
|
|
28165
28564
|
}
|
|
28166
28565
|
function stashFiles(m, where, env) {
|
|
28167
28566
|
let refs;
|
|
@@ -28551,7 +28950,7 @@ function publishFacts(m, env) {
|
|
|
28551
28950
|
const start = startFolder(m, env);
|
|
28552
28951
|
if (start !== null && PACKAGE_JSON_PROGRAMS.has(m.program)) {
|
|
28553
28952
|
try {
|
|
28554
|
-
const pkg = JSON.parse(readFileSync19(
|
|
28953
|
+
const pkg = JSON.parse(readFileSync19(join13(start, "package.json"), "utf-8"));
|
|
28555
28954
|
if (typeof pkg["name"] === "string" && typeof pkg["version"] === "string" && pkg["name"] !== "" && pkg["version"] !== "") {
|
|
28556
28955
|
name = pkg["name"];
|
|
28557
28956
|
version2 = pkg["version"];
|
|
@@ -28664,7 +29063,7 @@ function recordFileAt(abs, env) {
|
|
|
28664
29063
|
const root = recordRoot(abs, env);
|
|
28665
29064
|
if (root === null || !isInside(abs, root))
|
|
28666
29065
|
return null;
|
|
28667
|
-
const rel = toSlash(
|
|
29066
|
+
const rel = toSlash(relative6(root, abs));
|
|
28668
29067
|
if (rel === "")
|
|
28669
29068
|
return null;
|
|
28670
29069
|
const row = recordPatternFor(rel);
|
|
@@ -28674,11 +29073,11 @@ function recordFilesUnder(folder, env) {
|
|
|
28674
29073
|
const root = recordRoot(folder, env);
|
|
28675
29074
|
if (root === null)
|
|
28676
29075
|
return [];
|
|
28677
|
-
const relFolder = toSlash(
|
|
29076
|
+
const relFolder = toSlash(relative6(root, folder));
|
|
28678
29077
|
const starts = /* @__PURE__ */ new Set();
|
|
28679
29078
|
for (const { pattern } of PROJECT_RECORD_PATTERNS) {
|
|
28680
29079
|
if (relFolder === "" || patternOwns(relFolder, pattern))
|
|
28681
|
-
starts.add(
|
|
29080
|
+
starts.add(join13(root, pattern));
|
|
28682
29081
|
else if (patternOwns(pattern, relFolder))
|
|
28683
29082
|
starts.add(folder);
|
|
28684
29083
|
}
|
|
@@ -28704,7 +29103,7 @@ function recordFilesUnder(folder, env) {
|
|
|
28704
29103
|
continue;
|
|
28705
29104
|
}
|
|
28706
29105
|
for (const n of names)
|
|
28707
|
-
stack.push(
|
|
29106
|
+
stack.push(join13(p, n));
|
|
28708
29107
|
} else if (st.isFile()) {
|
|
28709
29108
|
const r = recordFileAt(p, env);
|
|
28710
29109
|
if (r !== null)
|
|
@@ -28725,7 +29124,7 @@ function recordHistory(files, env) {
|
|
|
28725
29124
|
byTree.set(top, list);
|
|
28726
29125
|
}
|
|
28727
29126
|
for (const [top, list] of byTree) {
|
|
28728
|
-
const specs = list.map((abs) => toSlash(
|
|
29127
|
+
const specs = list.map((abs) => toSlash(relative6(top, abs)));
|
|
28729
29128
|
const tracked = trackedOf(specs, top, env);
|
|
28730
29129
|
if (tracked === null)
|
|
28731
29130
|
continue;
|
|
@@ -28782,8 +29181,14 @@ function parseNumstatZ(text) {
|
|
|
28782
29181
|
// dist/risky/sentence.js
|
|
28783
29182
|
var REASON_MAX_CHARS = 200;
|
|
28784
29183
|
var MAX_LISTED = 3;
|
|
28785
|
-
var
|
|
29184
|
+
var PAUSED_OPENER = "Halfcycle paused a risky step before it runs: ";
|
|
29185
|
+
var PERSON_HOLD_LINE = "Halfcycle paused a risky step before it ran. Nothing changed \u2014 your agent will check with you.";
|
|
29186
|
+
var HELD_STEP_OPENER = "The step was: ";
|
|
29187
|
+
var PRECAUTION_OPENER = "It was stopped as a precaution: ";
|
|
29188
|
+
var ALLOW_REPLY = "allow";
|
|
29189
|
+
var HOLD_AGENT_INSTRUCTION = `If the step is not needed, carry on without it; there is no need to mention it. If it is needed, ask the person in chat, in one plain sentence, whether to run it, and tell them to reply "${ALLOW_REPLY}" to run it once, or anything else to skip it. Do not try it again until they answer.`;
|
|
28786
29190
|
var NOTHING_LOST_ON_NO = "Nothing is lost if you say no.";
|
|
29191
|
+
var IF_YES = "If you say yes, ";
|
|
28787
29192
|
var DESCRIBED_TOOLS = /* @__PURE__ */ new Set(["Bash", "Agent"]);
|
|
28788
29193
|
function reasonOf(call) {
|
|
28789
29194
|
if (!DESCRIBED_TOOLS.has(call.toolName))
|
|
@@ -28812,10 +29217,43 @@ function questionFor(facts, description) {
|
|
|
28812
29217
|
return couldNotTellQuestion(description);
|
|
28813
29218
|
if (staked.length === 1) {
|
|
28814
29219
|
const only = staked[0];
|
|
28815
|
-
return
|
|
29220
|
+
return `${PAUSED_OPENER}${stepOf(whatOf(only))}. ${reason} ${NOTHING_LOST_ON_NO} ${IF_YES}${lostOf(only)}`;
|
|
28816
29221
|
}
|
|
28817
|
-
const shown2 = staked.slice(0, MAX_LISTED).map((f) => `${whatOf(f)}
|
|
28818
|
-
return
|
|
29222
|
+
const shown2 = staked.slice(0, MAX_LISTED).map((f) => `${whatOf(f)} ${IF_YES}${lostOf(f)}`);
|
|
29223
|
+
return `${PAUSED_OPENER.trimEnd().slice(0, -1)}. ${shown2.join(" ")}${moreLine(staked.length - shown2.length)} ${reason} ${NOTHING_LOST_ON_NO}`;
|
|
29224
|
+
}
|
|
29225
|
+
function refusalFor(facts, description) {
|
|
29226
|
+
const staked = orderedStaked(facts);
|
|
29227
|
+
if (staked.length === 0)
|
|
29228
|
+
return couldNotTellRefusal(description);
|
|
29229
|
+
const shown2 = staked.slice(0, MAX_LISTED);
|
|
29230
|
+
const more = staked.length - shown2.length;
|
|
29231
|
+
const steps = `${shown2.map((f) => stepOf(whatOf(f))).join("; ")}${more > 0 ? `; and ${more} more risky ${more === 1 ? "step" : "steps"}` : ""}`;
|
|
29232
|
+
return heldStep(steps, description, shown2.map(whyOf).join("; "));
|
|
29233
|
+
}
|
|
29234
|
+
function heldStep(steps, description, why) {
|
|
29235
|
+
return {
|
|
29236
|
+
person: PERSON_HOLD_LINE,
|
|
29237
|
+
agent: `${HELD_STEP_OPENER}${steps}. ${reasonSentence(description)} ${PRECAUTION_OPENER}${why}. ${HOLD_AGENT_INSTRUCTION}`
|
|
29238
|
+
};
|
|
29239
|
+
}
|
|
29240
|
+
var STEP_VERBS = {
|
|
29241
|
+
Delete: "deleting",
|
|
29242
|
+
Throw: "throwing",
|
|
29243
|
+
Replace: "replacing",
|
|
29244
|
+
Move: "moving",
|
|
29245
|
+
Show: "showing",
|
|
29246
|
+
Publish: "publishing",
|
|
29247
|
+
Put: "putting"
|
|
29248
|
+
};
|
|
29249
|
+
function stepOf(what) {
|
|
29250
|
+
const bare = what.endsWith(".") ? what.slice(0, -1) : what;
|
|
29251
|
+
const space = bare.indexOf(" ");
|
|
29252
|
+
const verb = space < 0 ? bare : bare.slice(0, space);
|
|
29253
|
+
const ing = STEP_VERBS[verb];
|
|
29254
|
+
if (ing !== void 0)
|
|
29255
|
+
return `${ing}${bare.slice(verb.length)}`;
|
|
29256
|
+
return `one that would ${bare.charAt(0).toLowerCase()}${bare.slice(1)}`;
|
|
28819
29257
|
}
|
|
28820
29258
|
function orderedStaked(facts) {
|
|
28821
29259
|
const said = /* @__PURE__ */ new Set();
|
|
@@ -28830,7 +29268,7 @@ function orderedStaked(facts) {
|
|
|
28830
29268
|
function moreLine(more) {
|
|
28831
29269
|
return more > 0 ? ` And ${more} more risky ${more === 1 ? "step" : "steps"}.` : "";
|
|
28832
29270
|
}
|
|
28833
|
-
var UNNAMED_PUSH_WHAT = "Replace a
|
|
29271
|
+
var UNNAMED_PUSH_WHAT = "Replace a remote branch.";
|
|
28834
29272
|
function whatsText(facts, extraPush = false) {
|
|
28835
29273
|
const staked = orderedStaked(facts);
|
|
28836
29274
|
const items = staked.map((f) => ({ order: riskyTypeOrder(f.type), what: whatOf(f) }));
|
|
@@ -28849,11 +29287,12 @@ function typeSentenceFor(facts, extraPush = false) {
|
|
|
28849
29287
|
function savedWhatFor(facts) {
|
|
28850
29288
|
return whatsText(facts);
|
|
28851
29289
|
}
|
|
28852
|
-
|
|
28853
|
-
return `${question} ${TURN_QUESTIONS_ON_LINE}`;
|
|
28854
|
-
}
|
|
29290
|
+
var COULD_NOT_TELL_STEP = "one that can delete, overwrite, publish or show something that cannot be taken back";
|
|
28855
29291
|
function couldNotTellQuestion(description) {
|
|
28856
|
-
return `${
|
|
29292
|
+
return `${PAUSED_OPENER}${COULD_NOT_TELL_STEP}, and Halfcycle could not work out what. ${reasonSentence(description)} ${NOTHING_LOST_ON_NO} ${IF_YES}whatever it changes may not be possible to bring back.`;
|
|
29293
|
+
}
|
|
29294
|
+
function couldNotTellRefusal(description) {
|
|
29295
|
+
return heldStep(COULD_NOT_TELL_STEP, description, "Halfcycle could not work out what this step would change");
|
|
28857
29296
|
}
|
|
28858
29297
|
function whatOf(f) {
|
|
28859
29298
|
switch (f.type) {
|
|
@@ -28882,23 +29321,45 @@ function lostOf(f) {
|
|
|
28882
29321
|
case "delete-files":
|
|
28883
29322
|
return deleteLost(f);
|
|
28884
29323
|
case "discard-unsaved":
|
|
28885
|
-
return f.files === null ? "
|
|
29324
|
+
return f.files === null ? "any uncommitted changes it throws away could not be brought back; Halfcycle cannot tell which ones those are." : `${f.files === 1 ? "that change could not be brought back, because it was" : "those changes could not be brought back, because they were"} never committed.`;
|
|
28886
29325
|
case "rewrite-shared-history":
|
|
28887
29326
|
return pushLost(f);
|
|
28888
29327
|
case "delete-unmerged-work":
|
|
28889
29328
|
return unmergedLost(f);
|
|
28890
29329
|
case "wipe-data":
|
|
28891
|
-
return "Halfcycle cannot see the database, so it cannot tell how much that is or whether a
|
|
29330
|
+
return "that data is deleted; Halfcycle cannot see the database, so it cannot tell how much that is or whether a backup exists.";
|
|
28892
29331
|
case "read-credentials":
|
|
28893
|
-
return "nothing is deleted, but the secrets
|
|
29332
|
+
return "nothing is deleted, but the secrets become part of the agent's conversation, which is kept on this computer and sent to the coding agent's provider; that cannot be taken back.";
|
|
28894
29333
|
case "publish":
|
|
28895
|
-
return "
|
|
29334
|
+
return "anyone can install it straight away, and a published version cannot be taken back.";
|
|
28896
29335
|
case "deploy":
|
|
28897
29336
|
return "what is live now is replaced at once; Halfcycle cannot tell whether going back is possible.";
|
|
28898
29337
|
case "shrink-record":
|
|
28899
29338
|
return shrinkLost(f);
|
|
28900
29339
|
}
|
|
28901
29340
|
}
|
|
29341
|
+
function whyOf(f) {
|
|
29342
|
+
switch (f.type) {
|
|
29343
|
+
case "delete-files":
|
|
29344
|
+
return deleteWhy(f);
|
|
29345
|
+
case "discard-unsaved":
|
|
29346
|
+
return f.files === null ? "any uncommitted changes it throws away could not be brought back, and Halfcycle cannot tell which ones those are" : `${f.files === 1 ? "that change was never committed, so it" : "those changes were never committed, so they"} could not be brought back`;
|
|
29347
|
+
case "rewrite-shared-history":
|
|
29348
|
+
return pushWhy(f);
|
|
29349
|
+
case "delete-unmerged-work":
|
|
29350
|
+
return unmergedWhy(f);
|
|
29351
|
+
case "wipe-data":
|
|
29352
|
+
return "Halfcycle cannot see the database, so it cannot tell how much would be deleted or whether a backup exists";
|
|
29353
|
+
case "read-credentials":
|
|
29354
|
+
return "once shown, the secrets would be part of the agent's conversation, which is sent to the coding agent's provider";
|
|
29355
|
+
case "publish":
|
|
29356
|
+
return "a published version cannot be taken back";
|
|
29357
|
+
case "deploy":
|
|
29358
|
+
return "what is live now would be replaced at once, and Halfcycle cannot tell whether going back is possible";
|
|
29359
|
+
case "shrink-record":
|
|
29360
|
+
return shrinkWhy(f);
|
|
29361
|
+
}
|
|
29362
|
+
}
|
|
28902
29363
|
function deleteWhat(f) {
|
|
28903
29364
|
const thing = thingText(f.thing) ?? listNames(f.match.targets.map((t) => t.text));
|
|
28904
29365
|
if (f.files === null)
|
|
@@ -28907,24 +29368,39 @@ function deleteWhat(f) {
|
|
|
28907
29368
|
}
|
|
28908
29369
|
function deleteLost(f) {
|
|
28909
29370
|
if (f.files === null || f.neverSaved === null) {
|
|
28910
|
-
return "
|
|
29371
|
+
return "any file it deletes that was never committed could not be brought back; Halfcycle cannot tell which files those are.";
|
|
28911
29372
|
}
|
|
28912
29373
|
const n = f.files;
|
|
28913
29374
|
const k = f.neverSaved;
|
|
28914
29375
|
if (k === 0)
|
|
28915
|
-
return n === 1 ? "it can be brought back from the project's history." : "all of them can be brought back from the project's history.";
|
|
29376
|
+
return n === 1 ? "it can still be brought back from the project's history." : "all of them can still be brought back from the project's history.";
|
|
28916
29377
|
if (k === n) {
|
|
28917
29378
|
if (n === 1)
|
|
28918
|
-
return "that file
|
|
28919
|
-
return `${n === 2 ? "both" : `all ${n}`} of those files
|
|
29379
|
+
return "that file could not be brought back, because it was never committed.";
|
|
29380
|
+
return `${n === 2 ? "both" : `all ${n}`} of those files could not be brought back, because they were never committed.`;
|
|
28920
29381
|
}
|
|
28921
|
-
return `${k} of those files ${k === 1 ? "was" : "were"} never
|
|
29382
|
+
return `${k} of those files could not be brought back, because ${k === 1 ? "it was" : "they were"} never committed; the other ${n - k} can be brought back from the project's history.`;
|
|
29383
|
+
}
|
|
29384
|
+
function deleteWhy(f) {
|
|
29385
|
+
if (f.files === null || f.neverSaved === null) {
|
|
29386
|
+
return "Halfcycle cannot tell which files this would delete, or whether any of them were never committed";
|
|
29387
|
+
}
|
|
29388
|
+
const n = f.files;
|
|
29389
|
+
const k = f.neverSaved;
|
|
29390
|
+
if (k === 0)
|
|
29391
|
+
return `a delete like this is checked first, even though ${n === 1 ? "the file" : "every file"} can be brought back from the project's history`;
|
|
29392
|
+
if (k === n) {
|
|
29393
|
+
if (n === 1)
|
|
29394
|
+
return "that file was never committed, so it could not be brought back";
|
|
29395
|
+
return `${n === 2 ? "both" : `all ${n}`} of those files were never committed, so they could not be brought back`;
|
|
29396
|
+
}
|
|
29397
|
+
return `${k} of those files ${k === 1 ? "was" : "were"} never committed, so ${k === 1 ? "it" : "they"} could not be brought back`;
|
|
28922
29398
|
}
|
|
28923
29399
|
function discardWhat(f) {
|
|
28924
29400
|
if (f.files === null)
|
|
28925
|
-
return "Throw away
|
|
29401
|
+
return "Throw away uncommitted changes in this project.";
|
|
28926
29402
|
const thing = thingText(f.thing) ?? "this project";
|
|
28927
|
-
return `Throw away the
|
|
29403
|
+
return `Throw away the uncommitted changes to ${thing} \u2014 ${f.files} ${f.files === 1 ? "file" : "files"}.`;
|
|
28928
29404
|
}
|
|
28929
29405
|
function pushLines(f) {
|
|
28930
29406
|
const lines2 = f.lines.filter((l) => l.dropped !== 0);
|
|
@@ -28935,19 +29411,30 @@ function pushWhat(f) {
|
|
|
28935
29411
|
const verb = lines2.length > 0 && lines2.every((l) => l.deletes) ? "Delete" : "Replace";
|
|
28936
29412
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
28937
29413
|
if (named.length === 0)
|
|
28938
|
-
return verb === "Replace" ? UNNAMED_PUSH_WHAT : `${verb} a
|
|
28939
|
-
return `${verb} the
|
|
29414
|
+
return verb === "Replace" ? UNNAMED_PUSH_WHAT : `${verb} a remote branch.`;
|
|
29415
|
+
return `${verb} the remote ${named.length === 1 ? "branch" : "branches"} ${listNames(named)}.`;
|
|
29416
|
+
}
|
|
29417
|
+
function unknownPushWhere(f) {
|
|
29418
|
+
const unknown2 = pushLines(f).find((l) => l.dropped === null);
|
|
29419
|
+
if (unknown2 === void 0)
|
|
29420
|
+
return null;
|
|
29421
|
+
return unknown2.branch === null ? "that branch" : `the remote branch ${quote(unknown2.branch)}`;
|
|
28940
29422
|
}
|
|
28941
29423
|
function pushLost(f) {
|
|
28942
29424
|
const lines2 = pushLines(f);
|
|
28943
|
-
const
|
|
28944
|
-
if (
|
|
28945
|
-
|
|
28946
|
-
return `whatever is on it now might no longer be there, because Halfcycle cannot tell what is on ${where}.`;
|
|
28947
|
-
}
|
|
29425
|
+
const where = unknownPushWhere(f);
|
|
29426
|
+
if (where !== null)
|
|
29427
|
+
return `commits now on it might no longer be there; Halfcycle cannot tell what is on ${where}.`;
|
|
28948
29428
|
const n = lines2.reduce((s, l) => s + (l.dropped ?? 0), 0);
|
|
28949
29429
|
const them = n === 1 ? "it" : "them";
|
|
28950
|
-
return `${n}
|
|
29430
|
+
return `${n} ${n === 1 ? "commit" : "commits"} now on ${lines2.length === 1 ? "it" : "them"} would no longer be there, as of the last fetch; anyone who already has ${them} keeps ${them}, and Halfcycle cannot tell who does.`;
|
|
29431
|
+
}
|
|
29432
|
+
function pushWhy(f) {
|
|
29433
|
+
const where = unknownPushWhere(f);
|
|
29434
|
+
if (where !== null)
|
|
29435
|
+
return `Halfcycle cannot tell what is on ${where}, so it cannot tell whether any commits would no longer be there`;
|
|
29436
|
+
const n = pushLines(f).reduce((s, l) => s + (l.dropped ?? 0), 0);
|
|
29437
|
+
return `${n} ${n === 1 ? "commit" : "commits"} now on the remote would no longer be there, and Halfcycle cannot tell whether anyone else has ${n === 1 ? "it" : "them"}`;
|
|
28951
29438
|
}
|
|
28952
29439
|
function unmergedLines(f) {
|
|
28953
29440
|
const lines2 = f.lines.filter((l) => l.commits !== 0);
|
|
@@ -28957,24 +29444,34 @@ function unmergedWhat(f) {
|
|
|
28957
29444
|
const lines2 = unmergedLines(f);
|
|
28958
29445
|
if (f.match.form === "reset-to") {
|
|
28959
29446
|
const l = lines2[0];
|
|
28960
|
-
const which = l?.branch == null ? "
|
|
29447
|
+
const which = l?.branch == null ? "the current branch" : `the branch ${quote(l.branch)}`;
|
|
28961
29448
|
if (l?.commits == null)
|
|
28962
29449
|
return `Move ${which} back.`;
|
|
28963
|
-
return `Move ${which} back ${l.commits}
|
|
29450
|
+
return `Move ${which} back ${l.commits} ${l.commits === 1 ? "commit" : "commits"}.`;
|
|
28964
29451
|
}
|
|
28965
29452
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
28966
29453
|
if (named.length === 0)
|
|
28967
|
-
return "Delete a
|
|
28968
|
-
return `Delete the ${named.length === 1 ? "
|
|
29454
|
+
return "Delete a branch.";
|
|
29455
|
+
return `Delete the ${named.length === 1 ? "branch" : "branches"} ${listNames(named)}.`;
|
|
28969
29456
|
}
|
|
28970
|
-
function
|
|
29457
|
+
function unmergedCount(f) {
|
|
28971
29458
|
const lines2 = unmergedLines(f);
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
29459
|
+
if (lines2.some((l) => l.commits === null))
|
|
29460
|
+
return null;
|
|
29461
|
+
return lines2.reduce((s, l) => s + (l.commits ?? 0), 0);
|
|
29462
|
+
}
|
|
29463
|
+
function unmergedLost(f) {
|
|
29464
|
+
const n = unmergedCount(f);
|
|
29465
|
+
if (n === null) {
|
|
29466
|
+
return "some commits might be left on no branch, here or on the remote; Halfcycle cannot tell how many, and they can still be recovered on this computer for a while.";
|
|
28975
29467
|
}
|
|
28976
|
-
|
|
28977
|
-
|
|
29468
|
+
return n === 1 ? "1 commit would be left on no branch, here or on the remote; it can still be recovered on this computer for a while." : `${n} commits would be left on no branch, here or on the remote; they can still be recovered on this computer for a while.`;
|
|
29469
|
+
}
|
|
29470
|
+
function unmergedWhy(f) {
|
|
29471
|
+
const n = unmergedCount(f);
|
|
29472
|
+
if (n === null)
|
|
29473
|
+
return "some of its commits may not be on any other branch or on the remote";
|
|
29474
|
+
return `${n} of its commits ${n === 1 ? "is" : "are"} not on any other branch or on the remote`;
|
|
28978
29475
|
}
|
|
28979
29476
|
function wipeWhat(f) {
|
|
28980
29477
|
const m = f.match;
|
|
@@ -29018,7 +29515,7 @@ function sqlTarget(statement) {
|
|
|
29018
29515
|
}
|
|
29019
29516
|
function credentialsWhat(f) {
|
|
29020
29517
|
if (f.match.form === "gh-token")
|
|
29021
|
-
return "Show the agent the sign-in token this computer
|
|
29518
|
+
return "Show the agent the GitHub sign-in token on this computer.";
|
|
29022
29519
|
if (f.match.form === "keychain")
|
|
29023
29520
|
return "Show the agent a password kept in this computer's keychain.";
|
|
29024
29521
|
const file2 = f.file ?? f.match.file;
|
|
@@ -29057,6 +29554,20 @@ function shrinkLost(f) {
|
|
|
29057
29554
|
return "Halfcycle cannot tell how much of it could be brought back from the project's history.";
|
|
29058
29555
|
}
|
|
29059
29556
|
}
|
|
29557
|
+
function shrinkWhy(f) {
|
|
29558
|
+
switch (f.history) {
|
|
29559
|
+
case "saved":
|
|
29560
|
+
return "a record cut this much is checked first, even though all of it can be brought back from the project's history";
|
|
29561
|
+
case "never":
|
|
29562
|
+
return "this record was never saved to the project's history, so what is removed could not be brought back";
|
|
29563
|
+
case "changed": {
|
|
29564
|
+
const u = Math.min(f.unsaved ?? f.removed, f.removed);
|
|
29565
|
+
return `${u === f.removed && u > 1 ? `all ${u}` : u} of those lines ${u === 1 ? "was" : "were"} changed since the record was last saved, so ${u === 1 ? "it" : "they"} could not be brought back`;
|
|
29566
|
+
}
|
|
29567
|
+
case null:
|
|
29568
|
+
return "Halfcycle cannot tell how much of it could be brought back from the project's history";
|
|
29569
|
+
}
|
|
29570
|
+
}
|
|
29060
29571
|
var NOTHING_WAS_CHANGED = "Nothing was changed.";
|
|
29061
29572
|
var OWN_COPY_START_FIX = 'To start it, add isolation: "worktree" so it works in a copy of its own. If you already made a copy for it, remove that copy (git worktree remove <path>) and keep its branch: never delete it. In the brief, tell the agent to work in the copy it starts in, its first action git switch <branch>, and drop any "your working folder is <path>" line, because that folder is gone.';
|
|
29062
29573
|
function ownCopyRefusal(f, description) {
|
|
@@ -29117,24 +29628,188 @@ function listPlain(items) {
|
|
|
29117
29628
|
|
|
29118
29629
|
// dist/risky/check.js
|
|
29119
29630
|
function riskyAnswer(call, contextOf) {
|
|
29120
|
-
const
|
|
29631
|
+
const calls = isOneCall(call) ? [call] : call;
|
|
29632
|
+
const matches = calls.flatMap((c) => recognise(c)).sort((a, b) => riskyTypeOrder(a.type) - riskyTypeOrder(b.type));
|
|
29121
29633
|
if (matches.length === 0)
|
|
29122
29634
|
return { silent: true };
|
|
29123
|
-
const reason = reasonOf(
|
|
29635
|
+
const reason = calls.length === 0 ? null : reasonOf(calls[0]);
|
|
29124
29636
|
try {
|
|
29125
29637
|
const staked = factsFor(matches, contextOf()).filter((f) => f.atStake);
|
|
29126
29638
|
if (staked.length === 0)
|
|
29127
29639
|
return { silent: true };
|
|
29128
|
-
return { silent: false, question: questionFor(staked, reason), staked };
|
|
29640
|
+
return { silent: false, question: questionFor(staked, reason), refusal: refusalFor(staked, reason), staked };
|
|
29641
|
+
} catch {
|
|
29642
|
+
return { silent: false, question: couldNotTellQuestion(reason), refusal: couldNotTellRefusal(reason), staked: [] };
|
|
29643
|
+
}
|
|
29644
|
+
}
|
|
29645
|
+
function isOneCall(call) {
|
|
29646
|
+
return !Array.isArray(call);
|
|
29647
|
+
}
|
|
29648
|
+
|
|
29649
|
+
// dist/risky/allow-once.js
|
|
29650
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
29651
|
+
import { appendFileSync as appendFileSync4, readFileSync as readFileSync20, renameSync as renameSync10, unlinkSync as unlinkSync9, writeFileSync as writeFileSync12 } from "node:fs";
|
|
29652
|
+
var ALLOW_ONCE_MS = 5 * 60 * 1e3;
|
|
29653
|
+
var HELD_STEP_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
29654
|
+
function isAllowReply(prompt) {
|
|
29655
|
+
return typeof prompt === "string" && prompt.trim().toLowerCase() === ALLOW_REPLY;
|
|
29656
|
+
}
|
|
29657
|
+
function stepKey(input, staked) {
|
|
29658
|
+
const toolName = input["tool_name"];
|
|
29659
|
+
const toolInput = input["tool_input"];
|
|
29660
|
+
if (typeof toolName !== "string" || toolName === "")
|
|
29661
|
+
return null;
|
|
29662
|
+
if (typeof toolInput !== "object" || toolInput === null || Array.isArray(toolInput))
|
|
29663
|
+
return null;
|
|
29664
|
+
const rest = { ...toolInput };
|
|
29665
|
+
if (DESCRIBED_TOOLS.has(toolName))
|
|
29666
|
+
delete rest["description"];
|
|
29667
|
+
const cwd = typeof input["cwd"] === "string" ? input["cwd"] : "";
|
|
29668
|
+
return createHash5("sha256").update(canonical([toolName, cwd, rest, staked])).digest("hex");
|
|
29669
|
+
}
|
|
29670
|
+
function canonical(value) {
|
|
29671
|
+
if (Array.isArray(value))
|
|
29672
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
29673
|
+
if (typeof value === "object" && value !== null) {
|
|
29674
|
+
const o = value;
|
|
29675
|
+
return `{${Object.keys(o).sort().filter((k) => o[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(",")}}`;
|
|
29676
|
+
}
|
|
29677
|
+
return JSON.stringify(value) ?? "null";
|
|
29678
|
+
}
|
|
29679
|
+
function recordHeld(sessionId, key, deps = {}) {
|
|
29680
|
+
const paths = allowOncePaths(sessionId, deps.dir);
|
|
29681
|
+
if (paths === null)
|
|
29682
|
+
return;
|
|
29683
|
+
const record2 = { version: 1, session: sessionId, key, at: nowOf(deps) };
|
|
29684
|
+
try {
|
|
29685
|
+
appendFileSync4(paths.held, `${JSON.stringify(record2)}
|
|
29686
|
+
`, { mode: 384 });
|
|
29687
|
+
} catch {
|
|
29688
|
+
return;
|
|
29689
|
+
}
|
|
29690
|
+
}
|
|
29691
|
+
function armAllow(sessionId, deps = {}) {
|
|
29692
|
+
try {
|
|
29693
|
+
const paths = allowOncePaths(sessionId, deps.dir);
|
|
29694
|
+
if (paths === null)
|
|
29695
|
+
return "nothing-held";
|
|
29696
|
+
const now = nowOf(deps);
|
|
29697
|
+
const keys = claimHeldKeys(paths.held, sessionId, now);
|
|
29698
|
+
if (keys.length === 0)
|
|
29699
|
+
return "nothing-held";
|
|
29700
|
+
if (keys.length > 1)
|
|
29701
|
+
return "several-held";
|
|
29702
|
+
return writeRecord(paths.allow, { version: 1, session: sessionId, key: keys[0], at: now }) ? "armed" : "not-saved";
|
|
29703
|
+
} catch {
|
|
29704
|
+
return "not-saved";
|
|
29705
|
+
}
|
|
29706
|
+
}
|
|
29707
|
+
function claimHeldKeys(path, session, now) {
|
|
29708
|
+
const claimed = `${path}.${process.pid}.${now}.claim`;
|
|
29709
|
+
try {
|
|
29710
|
+
renameSync10(path, claimed);
|
|
29711
|
+
} catch {
|
|
29712
|
+
return [];
|
|
29713
|
+
}
|
|
29714
|
+
let text = "";
|
|
29715
|
+
try {
|
|
29716
|
+
text = readFileSync20(claimed, "utf8");
|
|
29717
|
+
} catch {
|
|
29718
|
+
text = "";
|
|
29719
|
+
}
|
|
29720
|
+
removeQuietly(claimed);
|
|
29721
|
+
const keys = /* @__PURE__ */ new Set();
|
|
29722
|
+
for (const line2 of text.split("\n")) {
|
|
29723
|
+
const r = parseRecord(line2, session);
|
|
29724
|
+
if (r !== null && within(r.at, now, HELD_STEP_MAX_AGE_MS))
|
|
29725
|
+
keys.add(r.key);
|
|
29726
|
+
}
|
|
29727
|
+
return [...keys];
|
|
29728
|
+
}
|
|
29729
|
+
function spendAllow(sessionId, key, deps = {}) {
|
|
29730
|
+
try {
|
|
29731
|
+
const paths = allowOncePaths(sessionId, deps.dir);
|
|
29732
|
+
if (paths === null)
|
|
29733
|
+
return false;
|
|
29734
|
+
const armed = readRecord2(paths.allow, sessionId);
|
|
29735
|
+
if (armed === null)
|
|
29736
|
+
return false;
|
|
29737
|
+
if (!within(armed.at, nowOf(deps), ALLOW_ONCE_MS)) {
|
|
29738
|
+
removeQuietly(paths.allow);
|
|
29739
|
+
return false;
|
|
29740
|
+
}
|
|
29741
|
+
if (armed.key !== key)
|
|
29742
|
+
return false;
|
|
29743
|
+
unlinkSync9(paths.allow);
|
|
29744
|
+
return true;
|
|
29745
|
+
} catch {
|
|
29746
|
+
return false;
|
|
29747
|
+
}
|
|
29748
|
+
}
|
|
29749
|
+
function within(at, now, limit) {
|
|
29750
|
+
const age = now - at;
|
|
29751
|
+
return Number.isFinite(age) && age >= 0 && age < limit;
|
|
29752
|
+
}
|
|
29753
|
+
function nowOf(deps) {
|
|
29754
|
+
return (deps.now ?? Date.now)();
|
|
29755
|
+
}
|
|
29756
|
+
function readRecord2(path, session) {
|
|
29757
|
+
try {
|
|
29758
|
+
return parseRecord(readFileSync20(path, "utf8"), session);
|
|
29759
|
+
} catch {
|
|
29760
|
+
return null;
|
|
29761
|
+
}
|
|
29762
|
+
}
|
|
29763
|
+
function parseRecord(text, session) {
|
|
29764
|
+
let parsed;
|
|
29765
|
+
try {
|
|
29766
|
+
parsed = JSON.parse(text);
|
|
29767
|
+
} catch {
|
|
29768
|
+
return null;
|
|
29769
|
+
}
|
|
29770
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
29771
|
+
return null;
|
|
29772
|
+
const r = parsed;
|
|
29773
|
+
if (r["version"] !== 1 || r["session"] !== session)
|
|
29774
|
+
return null;
|
|
29775
|
+
if (typeof r["key"] !== "string" || !/^[0-9a-f]{64}$/.test(r["key"]))
|
|
29776
|
+
return null;
|
|
29777
|
+
if (typeof r["at"] !== "number")
|
|
29778
|
+
return null;
|
|
29779
|
+
return { version: 1, session, key: r["key"], at: r["at"] };
|
|
29780
|
+
}
|
|
29781
|
+
function writeRecord(path, record2) {
|
|
29782
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
29783
|
+
try {
|
|
29784
|
+
writeFileSync12(tmp, JSON.stringify(record2), { mode: 384 });
|
|
29785
|
+
renameSync10(tmp, path);
|
|
29786
|
+
return true;
|
|
29787
|
+
} catch {
|
|
29788
|
+
removeQuietly(tmp);
|
|
29789
|
+
return false;
|
|
29790
|
+
}
|
|
29791
|
+
}
|
|
29792
|
+
function removeQuietly(path) {
|
|
29793
|
+
try {
|
|
29794
|
+
unlinkSync9(path);
|
|
29129
29795
|
} catch {
|
|
29130
|
-
return { silent: false, question: couldNotTellQuestion(reason), staked: [] };
|
|
29131
29796
|
}
|
|
29132
29797
|
}
|
|
29133
29798
|
|
|
29134
29799
|
// dist/risky/decide.js
|
|
29135
29800
|
var QUESTIONS_ON_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "auto"]);
|
|
29136
|
-
function answerModeFor(permissionMode) {
|
|
29137
|
-
|
|
29801
|
+
function answerModeFor(permissionMode, agent = "claude-code") {
|
|
29802
|
+
switch (agent) {
|
|
29803
|
+
case "codex":
|
|
29804
|
+
return "questions-off";
|
|
29805
|
+
case "claude-code":
|
|
29806
|
+
return typeof permissionMode === "string" && QUESTIONS_ON_MODES.has(permissionMode) ? "questions-on" : "questions-off";
|
|
29807
|
+
default: {
|
|
29808
|
+
const unhandled = agent;
|
|
29809
|
+
void unhandled;
|
|
29810
|
+
return "questions-off";
|
|
29811
|
+
}
|
|
29812
|
+
}
|
|
29138
29813
|
}
|
|
29139
29814
|
var REFUSED_IN_EVERY_MODE = /* @__PURE__ */ new Set(["outside-own-copy"]);
|
|
29140
29815
|
function autonomousRowOf(type) {
|
|
@@ -29184,6 +29859,9 @@ function decide(action, facts, mode, mayPublishNow = () => false) {
|
|
|
29184
29859
|
}
|
|
29185
29860
|
}
|
|
29186
29861
|
}
|
|
29862
|
+
function allowableByTyping(action, facts) {
|
|
29863
|
+
return decide(action, facts, "questions-on") === "ask";
|
|
29864
|
+
}
|
|
29187
29865
|
function decideAutonomous(facts, mayPublishNow) {
|
|
29188
29866
|
if (facts.length === 0)
|
|
29189
29867
|
return { autonomous: "could-not-tell" };
|
|
@@ -29200,8 +29878,8 @@ function decideAutonomous(facts, mayPublishNow) {
|
|
|
29200
29878
|
}
|
|
29201
29879
|
|
|
29202
29880
|
// dist/risky/own-copy.js
|
|
29203
|
-
import { readFileSync as
|
|
29204
|
-
import { join as
|
|
29881
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
29882
|
+
import { join as join14, posix as posix2, win32 as win322 } from "node:path";
|
|
29205
29883
|
var OWN_COPY_START_TOOL = "Agent";
|
|
29206
29884
|
var OWN_COPY_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
29207
29885
|
var OWN_COPY_ISOLATION = "worktree";
|
|
@@ -29222,7 +29900,7 @@ function stringField2(o, key) {
|
|
|
29222
29900
|
}
|
|
29223
29901
|
function readOr(deps, path) {
|
|
29224
29902
|
try {
|
|
29225
|
-
return (deps.readFile ?? ((p) =>
|
|
29903
|
+
return (deps.readFile ?? ((p) => readFileSync21(p, "utf-8")))(path);
|
|
29226
29904
|
} catch {
|
|
29227
29905
|
return null;
|
|
29228
29906
|
}
|
|
@@ -29262,7 +29940,7 @@ function typeCanChangeFiles(subagentType, deps) {
|
|
|
29262
29940
|
return true;
|
|
29263
29941
|
if (AGENT_FILE_NAME.test(type)) {
|
|
29264
29942
|
for (const base of [deps.projectDir, deps.home]) {
|
|
29265
|
-
const text = readOr(deps,
|
|
29943
|
+
const text = readOr(deps, join14(base, ".claude", "agents", `${type}.md`));
|
|
29266
29944
|
if (text === null)
|
|
29267
29945
|
continue;
|
|
29268
29946
|
const tools = frontmatterTools(text);
|
|
@@ -29368,9 +30046,9 @@ function judgeChange(input, call, deps) {
|
|
|
29368
30046
|
|
|
29369
30047
|
// dist/risky/save.js
|
|
29370
30048
|
import { randomBytes } from "node:crypto";
|
|
29371
|
-
import { chmodSync as chmodSync2, constants as fsConstants, copyFileSync, lstatSync as lstatSync3, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as
|
|
30049
|
+
import { chmodSync as chmodSync2, constants as fsConstants, copyFileSync, lstatSync as lstatSync3, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync22, readlinkSync, realpathSync as realpathSync7, renameSync as renameSync11, rmSync as rmSync2, statSync as statSync8, symlinkSync, writeFileSync as writeFileSync13 } from "node:fs";
|
|
29372
30050
|
import { homedir as homedir8 } from "node:os";
|
|
29373
|
-
import { dirname as dirname7, isAbsolute as
|
|
30051
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join15, resolve as resolve6 } from "node:path";
|
|
29374
30052
|
var SAVE_LIMIT_BYTES = 2e8;
|
|
29375
30053
|
var SAVE_BUDGET_MS = 5e3;
|
|
29376
30054
|
function notSaved(reason) {
|
|
@@ -29379,7 +30057,7 @@ function notSaved(reason) {
|
|
|
29379
30057
|
function savedFolderOf(engagementId, home = homedir8()) {
|
|
29380
30058
|
if (!isEngagementId(engagementId))
|
|
29381
30059
|
return null;
|
|
29382
|
-
return
|
|
30060
|
+
return join15(home, HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
29383
30061
|
}
|
|
29384
30062
|
function saveFirst(staked, ctx) {
|
|
29385
30063
|
const now = ctx.now ?? Date.now;
|
|
@@ -29410,20 +30088,20 @@ function saveFirst(staked, ctx) {
|
|
|
29410
30088
|
const id = freshId(folder, ctx.newId);
|
|
29411
30089
|
if (id === null)
|
|
29412
30090
|
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29413
|
-
saveDir =
|
|
30091
|
+
saveDir = join15(folder, id);
|
|
29414
30092
|
const repository = repositoryOf(g);
|
|
29415
30093
|
if (repository === null)
|
|
29416
30094
|
return failed(saveDir);
|
|
29417
|
-
|
|
30095
|
+
writeFileSync13(join15(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
|
|
29418
30096
|
`, { mode: 384 });
|
|
29419
|
-
const privateIndex =
|
|
30097
|
+
const privateIndex = join15(saveDir, PRIVATE_INDEX_NAME);
|
|
29420
30098
|
const commit = saveCommit(id, headCommit2, found2.tracked, plan.lines, privateIndex, g, now());
|
|
29421
30099
|
rmSync2(privateIndex, { force: true });
|
|
29422
30100
|
if (commit === null)
|
|
29423
30101
|
return failed(saveDir);
|
|
29424
|
-
const copiesDir =
|
|
30102
|
+
const copiesDir = join15(saveDir, SAVED_COPIES_DIR_NAME);
|
|
29425
30103
|
for (const rel of found2.copies) {
|
|
29426
|
-
if (now() > deadline || !copyOne(
|
|
30104
|
+
if (now() > deadline || !copyOne(join15(plan.root, rel), join15(copiesDir, rel)))
|
|
29427
30105
|
return failed(saveDir);
|
|
29428
30106
|
}
|
|
29429
30107
|
const description = savedSchema.safeParse({
|
|
@@ -29439,10 +30117,10 @@ function saveFirst(staked, ctx) {
|
|
|
29439
30117
|
});
|
|
29440
30118
|
if (!description.success)
|
|
29441
30119
|
return failed(saveDir);
|
|
29442
|
-
const file2 =
|
|
29443
|
-
|
|
30120
|
+
const file2 = join15(saveDir, SAVED_FILE_NAME);
|
|
30121
|
+
writeFileSync13(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
|
|
29444
30122
|
`, { mode: 384 });
|
|
29445
|
-
|
|
30123
|
+
renameSync11(`${file2}.part`, file2);
|
|
29446
30124
|
ctx.beforeRef?.();
|
|
29447
30125
|
const ref = g.run(["update-ref", "--no-deref", savedRefOf(id), commit, ""]);
|
|
29448
30126
|
if (!ref.ok)
|
|
@@ -29470,7 +30148,7 @@ function planOf(staked) {
|
|
|
29470
30148
|
const t = f.save;
|
|
29471
30149
|
if (t === void 0)
|
|
29472
30150
|
return { ok: false, reason: f.cannotTell === void 0 ? AUTONOMY_SAVE_NOT_COVERED_REASON : AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29473
|
-
const r = realOrSelf2(
|
|
30151
|
+
const r = realOrSelf2(resolve6(t.repoRoot));
|
|
29474
30152
|
if (root === null)
|
|
29475
30153
|
root = r;
|
|
29476
30154
|
else if (root !== r)
|
|
@@ -29489,7 +30167,7 @@ function planOf(staked) {
|
|
|
29489
30167
|
function isInsideRel(p) {
|
|
29490
30168
|
if (p === ".")
|
|
29491
30169
|
return true;
|
|
29492
|
-
if (p === "" ||
|
|
30170
|
+
if (p === "" || isAbsolute4(p) || p.includes("\\"))
|
|
29493
30171
|
return false;
|
|
29494
30172
|
return p.split("/").every((s) => s !== "" && s !== "." && s !== "..");
|
|
29495
30173
|
}
|
|
@@ -29519,7 +30197,7 @@ function filesOf(plan, g, deadline) {
|
|
|
29519
30197
|
trackedAll.add(path);
|
|
29520
30198
|
if (!reached(path, plan.specs))
|
|
29521
30199
|
continue;
|
|
29522
|
-
const st = lstatOrNull(
|
|
30200
|
+
const st = lstatOrNull(join15(plan.root, path));
|
|
29523
30201
|
if (st !== null && (st.isFile() || st.isSymbolicLink()))
|
|
29524
30202
|
tracked.add(path);
|
|
29525
30203
|
}
|
|
@@ -29531,7 +30209,7 @@ function filesOf(plan, g, deadline) {
|
|
|
29531
30209
|
const rel = stack.pop();
|
|
29532
30210
|
if (rel === ".git")
|
|
29533
30211
|
continue;
|
|
29534
|
-
const abs = rel === "." ? plan.root :
|
|
30212
|
+
const abs = rel === "." ? plan.root : join15(plan.root, rel);
|
|
29535
30213
|
const st = lstatOrNull(abs);
|
|
29536
30214
|
if (st === null)
|
|
29537
30215
|
continue;
|
|
@@ -29575,7 +30253,7 @@ function saveCommit(id, head, tracked, lines2, privateIndex, g, at) {
|
|
|
29575
30253
|
const indexPath = g.run(["rev-parse", "--git-path", "index"]);
|
|
29576
30254
|
if (!indexPath.ok)
|
|
29577
30255
|
return null;
|
|
29578
|
-
const projectIndex =
|
|
30256
|
+
const projectIndex = resolve6(g.root, indexPath.value.trim());
|
|
29579
30257
|
const env = commitEnv(privateIndex, at);
|
|
29580
30258
|
if (lstatOrNull(projectIndex) !== null)
|
|
29581
30259
|
copyFileSync(projectIndex, privateIndex);
|
|
@@ -29644,7 +30322,7 @@ function freshId(folder, newId) {
|
|
|
29644
30322
|
if (!savedIdSchema.safeParse(id).success)
|
|
29645
30323
|
return null;
|
|
29646
30324
|
try {
|
|
29647
|
-
mkdirSync8(
|
|
30325
|
+
mkdirSync8(join15(folder, id), { mode: 448 });
|
|
29648
30326
|
return id;
|
|
29649
30327
|
} catch (e) {
|
|
29650
30328
|
if (e.code !== "EEXIST")
|
|
@@ -29665,11 +30343,11 @@ function repositoryOf(g) {
|
|
|
29665
30343
|
if (!r.ok)
|
|
29666
30344
|
return null;
|
|
29667
30345
|
const dir = r.value.trim();
|
|
29668
|
-
return dir === "" ? null : realOrSelf2(
|
|
30346
|
+
return dir === "" ? null : realOrSelf2(resolve6(g.root, dir));
|
|
29669
30347
|
}
|
|
29670
30348
|
function markedRepository(dir) {
|
|
29671
30349
|
try {
|
|
29672
|
-
const text =
|
|
30350
|
+
const text = readFileSync22(join15(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
29673
30351
|
return text === "" ? null : text;
|
|
29674
30352
|
} catch {
|
|
29675
30353
|
return null;
|
|
@@ -29703,7 +30381,7 @@ function pruneSaves(root, engagementId, ctx = {}) {
|
|
|
29703
30381
|
if (!savedIdSchema.safeParse(id).success)
|
|
29704
30382
|
continue;
|
|
29705
30383
|
here.add(id);
|
|
29706
|
-
const dir =
|
|
30384
|
+
const dir = join15(folder, id);
|
|
29707
30385
|
const st = lstatOrNull(dir);
|
|
29708
30386
|
const answer = savedPruneAnswer({
|
|
29709
30387
|
refMadeAt: refs.get(id) ?? null,
|
|
@@ -29742,19 +30420,19 @@ function realOrSelf2(p) {
|
|
|
29742
30420
|
|
|
29743
30421
|
// dist/autonomy-state.js
|
|
29744
30422
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29745
|
-
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as
|
|
30423
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync9, readFileSync as readFileSync23, renameSync as renameSync12, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "node:fs";
|
|
29746
30424
|
import { homedir as homedir9, platform as platform2 } from "node:os";
|
|
29747
|
-
import { dirname as dirname8, join as
|
|
30425
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
29748
30426
|
var AUTONOMY_STATE_FILENAME = "autonomy.json";
|
|
29749
30427
|
var AUTONOMY_STATE_DEADLINE_MS = 1500;
|
|
29750
30428
|
function autonomyStateFilePath(engagementId, home) {
|
|
29751
|
-
return
|
|
30429
|
+
return join16(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, AUTONOMY_STATE_FILENAME);
|
|
29752
30430
|
}
|
|
29753
30431
|
function readCachedAutonomy(engagementId, nowMs, home) {
|
|
29754
30432
|
if (!isEngagementId(engagementId))
|
|
29755
30433
|
return null;
|
|
29756
30434
|
try {
|
|
29757
|
-
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(
|
|
30435
|
+
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync23(autonomyStateFilePath(engagementId, home), "utf-8")));
|
|
29758
30436
|
if (!parsed.success || !parsed.data.on)
|
|
29759
30437
|
return null;
|
|
29760
30438
|
const age = nowMs - Date.parse(parsed.data.readAt);
|
|
@@ -29800,14 +30478,14 @@ function deleteAutonomyStateFile(engagementId, home) {
|
|
|
29800
30478
|
}
|
|
29801
30479
|
function writeAutonomyStateFile(engagementId, file2, home) {
|
|
29802
30480
|
const target = autonomyStateFilePath(engagementId, home);
|
|
29803
|
-
const temp =
|
|
30481
|
+
const temp = join16(dirname8(target), `.${AUTONOMY_STATE_FILENAME}.${process.pid}.tmp`);
|
|
29804
30482
|
try {
|
|
29805
30483
|
mkdirSync9(dirname8(target), { recursive: true, mode: 448 });
|
|
29806
|
-
|
|
30484
|
+
writeFileSync14(temp, `${JSON.stringify(file2)}
|
|
29807
30485
|
`, { mode: 384 });
|
|
29808
30486
|
if (platform2() !== "win32")
|
|
29809
30487
|
chmodSync3(temp, 384);
|
|
29810
|
-
|
|
30488
|
+
renameSync12(temp, target);
|
|
29811
30489
|
return true;
|
|
29812
30490
|
} catch {
|
|
29813
30491
|
try {
|
|
@@ -29861,6 +30539,76 @@ async function cacheAutonomySetting(opts) {
|
|
|
29861
30539
|
}
|
|
29862
30540
|
}
|
|
29863
30541
|
|
|
30542
|
+
// dist/risky/patch-calls.js
|
|
30543
|
+
import { isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
|
|
30544
|
+
var SHELL_TOOL = "Bash";
|
|
30545
|
+
function toolCallsOf(input, profile) {
|
|
30546
|
+
const toolName = input["tool_name"];
|
|
30547
|
+
const toolInput = input["tool_input"];
|
|
30548
|
+
if (typeof toolName !== "string" || toolName === "")
|
|
30549
|
+
return null;
|
|
30550
|
+
if (typeof toolInput !== "object" || toolInput === null || Array.isArray(toolInput))
|
|
30551
|
+
return null;
|
|
30552
|
+
const call = { toolName, toolInput };
|
|
30553
|
+
if (profile.id === "claude-code")
|
|
30554
|
+
return [call];
|
|
30555
|
+
if (!profile.writeCapableTools.includes(toolName))
|
|
30556
|
+
return [call];
|
|
30557
|
+
const filePath = call.toolInput["file_path"];
|
|
30558
|
+
if (typeof filePath === "string" && filePath !== "")
|
|
30559
|
+
return [call];
|
|
30560
|
+
const command = call.toolInput["command"];
|
|
30561
|
+
if (typeof command !== "string")
|
|
30562
|
+
return null;
|
|
30563
|
+
const ops = patchOperations(command);
|
|
30564
|
+
if (ops === null || ops.length === 0)
|
|
30565
|
+
return null;
|
|
30566
|
+
const cwd = typeof input["cwd"] === "string" && isAbsolute5(input["cwd"]) ? input["cwd"] : null;
|
|
30567
|
+
const calls = [];
|
|
30568
|
+
for (const op of ops) {
|
|
30569
|
+
const made = callsFor(op, cwd);
|
|
30570
|
+
if (made === null)
|
|
30571
|
+
return null;
|
|
30572
|
+
calls.push(...made);
|
|
30573
|
+
}
|
|
30574
|
+
return calls;
|
|
30575
|
+
}
|
|
30576
|
+
function callsFor(op, cwd) {
|
|
30577
|
+
const path = placed(op.path, cwd);
|
|
30578
|
+
if (path === null)
|
|
30579
|
+
return null;
|
|
30580
|
+
switch (op.kind) {
|
|
30581
|
+
case "add":
|
|
30582
|
+
return [{ toolName: "Write", toolInput: { file_path: path, content: op.lines.length === 0 ? "" : `${op.lines.join("\n")}
|
|
30583
|
+
` } }];
|
|
30584
|
+
case "delete":
|
|
30585
|
+
return [{ toolName: SHELL_TOOL, toolInput: { command: `rm -- ${shellWord(path)}` } }];
|
|
30586
|
+
case "update": {
|
|
30587
|
+
const edits = op.hunks.filter((h) => h.before.length > 0).map((h) => ({ old_string: h.before.join("\n"), new_string: h.after.join("\n") }));
|
|
30588
|
+
const out = [{ toolName: "MultiEdit", toolInput: { file_path: path, edits } }];
|
|
30589
|
+
if (op.movedTo !== null) {
|
|
30590
|
+
const dest = placed(op.movedTo, cwd);
|
|
30591
|
+
if (dest === null)
|
|
30592
|
+
return null;
|
|
30593
|
+
out.push({ toolName: SHELL_TOOL, toolInput: { command: `mv -- ${shellWord(path)} ${shellWord(dest)}` } });
|
|
30594
|
+
}
|
|
30595
|
+
return out;
|
|
30596
|
+
}
|
|
30597
|
+
default: {
|
|
30598
|
+
const unhandled = op;
|
|
30599
|
+
return unhandled;
|
|
30600
|
+
}
|
|
30601
|
+
}
|
|
30602
|
+
}
|
|
30603
|
+
function placed(path, cwd) {
|
|
30604
|
+
if (isAbsolute5(path))
|
|
30605
|
+
return resolve7(path);
|
|
30606
|
+
return cwd === null ? null : resolve7(cwd, path);
|
|
30607
|
+
}
|
|
30608
|
+
function shellWord(text) {
|
|
30609
|
+
return `'${text.split("'").join("'\\''")}'`;
|
|
30610
|
+
}
|
|
30611
|
+
|
|
29864
30612
|
// dist/hooks/pre-tool-use.js
|
|
29865
30613
|
import { performance } from "node:perf_hooks";
|
|
29866
30614
|
import { homedir as homedir10 } from "node:os";
|
|
@@ -29872,13 +30620,15 @@ var OWN_COPY_UNKNOWN_NOTICE = "Halfcycle could not tell which copy of the projec
|
|
|
29872
30620
|
var CHECKED = new Set(CHECKED_TOOLS);
|
|
29873
30621
|
var RECORDED_ONLY = new Set(RECORDED_ONLY_TOOLS);
|
|
29874
30622
|
function runPreToolUse(deps) {
|
|
29875
|
-
const
|
|
30623
|
+
const parsed = parseObject(deps.input);
|
|
30624
|
+
const input = parsed === null ? null : hookInputForRule(parsed, profileOf(deps).configFolder);
|
|
29876
30625
|
const at = clockOf(deps)();
|
|
29877
30626
|
const stamped = { ...deps, now: () => at };
|
|
29878
30627
|
recordTimeEvent("action", input ?? {}, stamped);
|
|
29879
30628
|
recordOwnShell(input, at, { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir } });
|
|
29880
30629
|
const seen = recordLiveness(input ?? {}, deps.liveness);
|
|
29881
30630
|
const paceDeps = {
|
|
30631
|
+
configFolder: profileOf(deps).configFolder,
|
|
29882
30632
|
...deps.pace ?? {},
|
|
29883
30633
|
...deps.env === void 0 ? {} : { env: deps.env },
|
|
29884
30634
|
...deps.home === void 0 ? {} : { home: deps.home },
|
|
@@ -29890,14 +30640,15 @@ function runPreToolUse(deps) {
|
|
|
29890
30640
|
const toolName = input === null ? void 0 : stringOf(input, "tool_name");
|
|
29891
30641
|
if (toolName !== void 0 && RECORDED_ONLY.has(toolName))
|
|
29892
30642
|
return recordedOnlyReply(mode);
|
|
29893
|
-
const
|
|
29894
|
-
if (
|
|
30643
|
+
const calls = input === null ? null : toolCallsOf(input, profileOf(deps));
|
|
30644
|
+
if (calls === null || !calls.every((c) => CHECKED.has(c.toolName)))
|
|
29895
30645
|
return unreadable(deps, input);
|
|
30646
|
+
const call = calls[0] ?? { toolName: stringOf(input, "tool_name") ?? "", toolInput: {} };
|
|
29896
30647
|
let answer;
|
|
29897
30648
|
try {
|
|
29898
|
-
answer = riskyAnswer(
|
|
30649
|
+
answer = riskyAnswer(calls, () => {
|
|
29899
30650
|
const cwd = stringOf(input, "cwd") ?? (deps.cwd ?? (() => process.cwd()))();
|
|
29900
|
-
const projectDir =
|
|
30651
|
+
const projectDir = projectDirOf(deps);
|
|
29901
30652
|
const tempDir = (deps.env ?? process.env)["TMPDIR"];
|
|
29902
30653
|
return {
|
|
29903
30654
|
cwd,
|
|
@@ -29910,7 +30661,7 @@ function runPreToolUse(deps) {
|
|
|
29910
30661
|
} catch {
|
|
29911
30662
|
answer = { silent: true };
|
|
29912
30663
|
}
|
|
29913
|
-
const own =
|
|
30664
|
+
const own = ownCopyOfAll(input, calls, deps);
|
|
29914
30665
|
try {
|
|
29915
30666
|
if (own.kind === "refused") {
|
|
29916
30667
|
const staked = [...answer.silent ? [] : answer.staked, own.facts];
|
|
@@ -29921,12 +30672,12 @@ function runPreToolUse(deps) {
|
|
|
29921
30672
|
}
|
|
29922
30673
|
}
|
|
29923
30674
|
if (mode === "autonomous") {
|
|
29924
|
-
const sendsSaves =
|
|
30675
|
+
const sendsSaves = calls.some((c) => c.toolName === "Bash" && typeof c.toolInput["command"] === "string" && pushesSavedRefs(c.toolInput["command"]));
|
|
29925
30676
|
if (!answer.silent || sendsSaves)
|
|
29926
30677
|
return autonomousReply(call, answer.silent ? [] : answer.staked, sendsSaves, mode, autonomy, deps);
|
|
29927
30678
|
}
|
|
29928
30679
|
if (answer.silent) {
|
|
29929
|
-
if (call.toolName === PACE_HELD_TOOL) {
|
|
30680
|
+
if (calls.length === 1 && call.toolName === PACE_HELD_TOOL) {
|
|
29930
30681
|
const held = paceHoldText(input, call.toolInput, paceDeps);
|
|
29931
30682
|
if (held !== null)
|
|
29932
30683
|
return `${preToolUseReplyText("refuse", held)}
|
|
@@ -29939,7 +30690,16 @@ function runPreToolUse(deps) {
|
|
|
29939
30690
|
return `${preToolUseReplyText("ask", answer.question)}
|
|
29940
30691
|
`;
|
|
29941
30692
|
}
|
|
29942
|
-
|
|
30693
|
+
if (allowableByTyping(call, answer.staked)) {
|
|
30694
|
+
const key = stepKey(input, answer.staked);
|
|
30695
|
+
if (key !== null) {
|
|
30696
|
+
const allowOnce = { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir }, now: () => at.getTime() };
|
|
30697
|
+
if (spendAllow(input["session_id"], key, allowOnce))
|
|
30698
|
+
return "";
|
|
30699
|
+
recordHeld(input["session_id"], key, allowOnce);
|
|
30700
|
+
}
|
|
30701
|
+
}
|
|
30702
|
+
return `${preToolUseReplyText("refuse", answer.refusal.person, answer.refusal.agent)}
|
|
29943
30703
|
`;
|
|
29944
30704
|
} catch {
|
|
29945
30705
|
return "";
|
|
@@ -29953,7 +30713,7 @@ function answerModeOf(input, deps, at) {
|
|
|
29953
30713
|
const cached2 = engagementId === void 0 ? null : readCachedAutonomy(engagementId, at.getTime(), deps.home);
|
|
29954
30714
|
if (engagementId !== void 0 && cached2 !== null)
|
|
29955
30715
|
return { mode: "autonomous", autonomy: { engagementId, mayPublish: cached2.mayPublish } };
|
|
29956
|
-
return { mode: answerModeFor(input === null ? void 0 : input["permission_mode"]), autonomy: null };
|
|
30716
|
+
return { mode: answerModeFor(input === null ? void 0 : input["permission_mode"], profileOf(deps).id), autonomy: null };
|
|
29957
30717
|
}
|
|
29958
30718
|
function autonomousReply(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29959
30719
|
try {
|
|
@@ -30033,11 +30793,40 @@ function recordedOnlyReply(mode) {
|
|
|
30033
30793
|
}
|
|
30034
30794
|
}
|
|
30035
30795
|
}
|
|
30796
|
+
function profileOf(deps) {
|
|
30797
|
+
return deps.profile ?? AGENT_PROFILES["claude-code"];
|
|
30798
|
+
}
|
|
30799
|
+
function projectDirOf(deps) {
|
|
30800
|
+
if (deps.projectDir !== void 0)
|
|
30801
|
+
return deps.projectDir;
|
|
30802
|
+
const rule = profileOf(deps).projectRoot;
|
|
30803
|
+
switch (rule) {
|
|
30804
|
+
case "git-toplevel":
|
|
30805
|
+
return (deps.cwd ?? (() => process.cwd()))();
|
|
30806
|
+
case "claude-project-dir":
|
|
30807
|
+
return projectFolder(deps.env ?? process.env);
|
|
30808
|
+
default: {
|
|
30809
|
+
const unhandled = rule;
|
|
30810
|
+
return unhandled;
|
|
30811
|
+
}
|
|
30812
|
+
}
|
|
30813
|
+
}
|
|
30814
|
+
function ownCopyOfAll(input, calls, deps) {
|
|
30815
|
+
let unknown2 = false;
|
|
30816
|
+
for (const c of calls) {
|
|
30817
|
+
const v = ownCopyOf(input, c, deps);
|
|
30818
|
+
if (v.kind === "refused")
|
|
30819
|
+
return v;
|
|
30820
|
+
if (v.kind === "unknown")
|
|
30821
|
+
unknown2 = true;
|
|
30822
|
+
}
|
|
30823
|
+
return unknown2 ? { kind: "unknown" } : { kind: "pass" };
|
|
30824
|
+
}
|
|
30036
30825
|
function ownCopyOf(input, call, deps) {
|
|
30037
30826
|
try {
|
|
30038
30827
|
const walk2 = deps.walk ?? deps.liveness?.walk;
|
|
30039
30828
|
return judgeOwnCopy(input, call, {
|
|
30040
|
-
projectDir:
|
|
30829
|
+
projectDir: projectDirOf(deps),
|
|
30041
30830
|
home: deps.home ?? homedir10(),
|
|
30042
30831
|
...walk2 === void 0 ? {} : { walk: walk2 },
|
|
30043
30832
|
...deps.liveness === void 0 ? {} : { liveness: deps.liveness }
|
|
@@ -30047,7 +30836,7 @@ function ownCopyOf(input, call, deps) {
|
|
|
30047
30836
|
}
|
|
30048
30837
|
}
|
|
30049
30838
|
function ownCopyNotice(input, deps) {
|
|
30050
|
-
const folder = stringOf(input, "cwd") ??
|
|
30839
|
+
const folder = stringOf(input, "cwd") ?? projectDirOf(deps);
|
|
30051
30840
|
return firstNoticeThisSession("outside-own-copy-unknown", folder, stringOf(input, "session_id"), deps.liveness?.dir) ? `${preToolUseNoticeText(OWN_COPY_UNKNOWN_NOTICE)}
|
|
30052
30841
|
` : "";
|
|
30053
30842
|
}
|
|
@@ -30063,23 +30852,16 @@ function stringOf(input, key) {
|
|
|
30063
30852
|
const v = input[key];
|
|
30064
30853
|
return typeof v === "string" && v !== "" ? v : void 0;
|
|
30065
30854
|
}
|
|
30066
|
-
function toolCallOf(input) {
|
|
30067
|
-
const toolName = stringOf(input, "tool_name");
|
|
30068
|
-
const toolInput = input["tool_input"];
|
|
30069
|
-
if (toolName === void 0 || typeof toolInput !== "object" || toolInput === null || Array.isArray(toolInput))
|
|
30070
|
-
return null;
|
|
30071
|
-
return { toolName, toolInput };
|
|
30072
|
-
}
|
|
30073
30855
|
function unreadable(deps, input) {
|
|
30074
30856
|
const session = (input === null ? void 0 : stringOf(input, "session_id")) ?? /"session_id"\s*:\s*"([^"\\]+)"/.exec(deps.input)?.[1];
|
|
30075
|
-
const folder = (input === null ? void 0 : stringOf(input, "cwd")) ??
|
|
30857
|
+
const folder = (input === null ? void 0 : stringOf(input, "cwd")) ?? projectDirOf(deps);
|
|
30076
30858
|
return firstNoticeThisSession("pre-tool-use-unreadable", folder, session) ? `${preToolUseNoticeText(UNREADABLE_INPUT_NOTICE)}
|
|
30077
30859
|
` : "";
|
|
30078
30860
|
}
|
|
30079
30861
|
|
|
30080
30862
|
// dist/hooks/session-diff.js
|
|
30081
|
-
import { unlinkSync as
|
|
30082
|
-
import { join as
|
|
30863
|
+
import { unlinkSync as unlinkSync10 } from "node:fs";
|
|
30864
|
+
import { join as join17 } from "node:path";
|
|
30083
30865
|
var STOP_HOOK_EVENT = {
|
|
30084
30866
|
stop: "Stop",
|
|
30085
30867
|
"subagent-stop": "SubagentStop"
|
|
@@ -30114,14 +30896,14 @@ function collectDiff(repoRoot, base) {
|
|
|
30114
30896
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
30115
30897
|
};
|
|
30116
30898
|
}
|
|
30117
|
-
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration, afterPass) {
|
|
30118
|
-
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
30119
|
-
await evaluateSession(input, kind, reply, readSeat);
|
|
30899
|
+
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration, afterPass, profile = AGENT_PROFILES["claude-code"]) {
|
|
30900
|
+
const reply = hookReply(STOP_HOOK_EVENT[kind], profile);
|
|
30901
|
+
await evaluateSession(input, kind, reply, readSeat, profile);
|
|
30120
30902
|
if (afterPass)
|
|
30121
30903
|
await afterPass(reply);
|
|
30122
30904
|
reply.flush();
|
|
30123
30905
|
}
|
|
30124
|
-
async function evaluateSession(input, kind, reply, readSeat) {
|
|
30906
|
+
async function evaluateSession(input, kind, reply, readSeat, profile) {
|
|
30125
30907
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
30126
30908
|
if (!repoRootResult.ok) {
|
|
30127
30909
|
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
@@ -30144,7 +30926,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30144
30926
|
const candidates = ownEditedFiles(repoRoot, sessionId).filter((relPath) => !tracked.has(relPath));
|
|
30145
30927
|
const untracked = getUntrackedFiles(repoRoot, candidates);
|
|
30146
30928
|
const created = untracked.ok ? untracked.value.filter((relPath) => !tracked.has(relPath)) : [];
|
|
30147
|
-
const getDiff = (relPath) => tracked.has(relPath) ? collected.getDiff(relPath) : getSingleFileDiff(
|
|
30929
|
+
const getDiff = (relPath) => tracked.has(relPath) ? collected.getDiff(relPath) : getSingleFileDiff(join17(repoRoot, relPath), repoRoot);
|
|
30148
30930
|
const { evaluated } = partitionInstallerOutput(collected.files);
|
|
30149
30931
|
const { evaluated: createdEvaluated } = partitionInstallerOutput(created);
|
|
30150
30932
|
const changedFiles2 = [...ownChangedFiles(repoRoot, evaluated, sessionId), ...createdEvaluated];
|
|
@@ -30170,11 +30952,11 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30170
30952
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
|
|
30171
30953
|
if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
|
|
30172
30954
|
return;
|
|
30173
|
-
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
30955
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()), reply);
|
|
30174
30956
|
return;
|
|
30175
30957
|
}
|
|
30176
30958
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
30177
|
-
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
30959
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat, profile);
|
|
30178
30960
|
const evaluation = await evaluateWithRefresh({
|
|
30179
30961
|
guardServiceUrl,
|
|
30180
30962
|
token: guardServiceToken,
|
|
@@ -30205,7 +30987,8 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30205
30987
|
// The remedy the renewal attempt earned: the sign-in when the account
|
|
30206
30988
|
// credential is what was refused, and otherwise the plane's own sentence
|
|
30207
30989
|
// about why it would not mint.
|
|
30208
|
-
evaluation.remedy
|
|
30990
|
+
evaluation.remedy,
|
|
30991
|
+
reply
|
|
30209
30992
|
);
|
|
30210
30993
|
return;
|
|
30211
30994
|
}
|
|
@@ -30261,7 +31044,7 @@ function removeFindingsFile(sessionId) {
|
|
|
30261
31044
|
if (path === null)
|
|
30262
31045
|
return;
|
|
30263
31046
|
try {
|
|
30264
|
-
|
|
31047
|
+
unlinkSync10(path);
|
|
30265
31048
|
} catch {
|
|
30266
31049
|
}
|
|
30267
31050
|
}
|
|
@@ -30307,9 +31090,9 @@ async function readHookStdin() {
|
|
|
30307
31090
|
}
|
|
30308
31091
|
}
|
|
30309
31092
|
function readStdin2() {
|
|
30310
|
-
return new Promise((
|
|
31093
|
+
return new Promise((resolve10) => {
|
|
30311
31094
|
if (process.stdin.isTTY) {
|
|
30312
|
-
|
|
31095
|
+
resolve10("");
|
|
30313
31096
|
return;
|
|
30314
31097
|
}
|
|
30315
31098
|
const chunks = [];
|
|
@@ -30319,7 +31102,7 @@ function readStdin2() {
|
|
|
30319
31102
|
return;
|
|
30320
31103
|
settled = true;
|
|
30321
31104
|
clearTimeout(timer);
|
|
30322
|
-
|
|
31105
|
+
resolve10(Buffer.concat(chunks).toString("utf-8"));
|
|
30323
31106
|
};
|
|
30324
31107
|
const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
|
|
30325
31108
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -30329,11 +31112,11 @@ function readStdin2() {
|
|
|
30329
31112
|
}
|
|
30330
31113
|
|
|
30331
31114
|
// dist/hooks/subagent-stop.js
|
|
30332
|
-
async function runSubagentStop() {
|
|
30333
|
-
const input = await readHookStdin();
|
|
31115
|
+
async function runSubagentStop(profile = AGENT_PROFILES["claude-code"]) {
|
|
31116
|
+
const input = hookInputForRule(await readHookStdin(), profile.configFolder);
|
|
30334
31117
|
recordTimeEvent("turn-end", input);
|
|
30335
|
-
subagentStopped(input);
|
|
30336
|
-
await runSessionDiff(input, "subagent-stop");
|
|
31118
|
+
subagentStopped(input, { pace: { configFolder: profile.configFolder } });
|
|
31119
|
+
await runSessionDiff(input, "subagent-stop", void 0, void 0, profile);
|
|
30337
31120
|
}
|
|
30338
31121
|
function subagentStopped(input, deps = {}) {
|
|
30339
31122
|
if (clearLiveness(input, deps.liveness) === 0)
|
|
@@ -30341,14 +31124,14 @@ function subagentStopped(input, deps = {}) {
|
|
|
30341
31124
|
}
|
|
30342
31125
|
|
|
30343
31126
|
// dist/hooks/stop.js
|
|
30344
|
-
async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}) {
|
|
30345
|
-
const input = await readHookStdin();
|
|
31127
|
+
async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}, profile = AGENT_PROFILES["claude-code"]) {
|
|
31128
|
+
const input = hookInputForRule(await readHookStdin(), profile.configFolder);
|
|
30346
31129
|
recordTimeEvent("turn-end", input);
|
|
30347
31130
|
await runSessionDiff(input, "stop", readSeatConfiguration, async (reply) => {
|
|
30348
31131
|
await observeChanges(input.session_id, reply, observeDeps);
|
|
30349
31132
|
await sendTimeRecord(timeDeps);
|
|
30350
|
-
await paceAtTurnEnd(input, reply, paceDeps);
|
|
30351
|
-
});
|
|
31133
|
+
await paceAtTurnEnd(input, reply, { configFolder: profile.configFolder, ...paceDeps });
|
|
31134
|
+
}, profile);
|
|
30352
31135
|
}
|
|
30353
31136
|
|
|
30354
31137
|
// dist/hooks/scope-phrase.js
|
|
@@ -30429,6 +31212,9 @@ function reviewPhraseShape(prompt) {
|
|
|
30429
31212
|
const line2 = prompt.trim();
|
|
30430
31213
|
if (LINE_BREAK.test(line2))
|
|
30431
31214
|
return null;
|
|
31215
|
+
const reject = REJECT_VERB.exec(line2);
|
|
31216
|
+
if (reject !== null)
|
|
31217
|
+
return rejectShape(line2.slice(reject[0].length));
|
|
30432
31218
|
for (const [prefix, act] of VERBS) {
|
|
30433
31219
|
const verb = prefix.exec(line2);
|
|
30434
31220
|
if (verb === null)
|
|
@@ -30446,6 +31232,21 @@ function reviewPhraseShape(prompt) {
|
|
|
30446
31232
|
}
|
|
30447
31233
|
return null;
|
|
30448
31234
|
}
|
|
31235
|
+
var REJECT_VERB = /^reject /i;
|
|
31236
|
+
function rejectShape(rest) {
|
|
31237
|
+
const colon = rest.indexOf(":");
|
|
31238
|
+
if (colon === -1)
|
|
31239
|
+
return null;
|
|
31240
|
+
const head = rest.slice(0, colon);
|
|
31241
|
+
const slash = head.indexOf("/");
|
|
31242
|
+
if (slash === -1)
|
|
31243
|
+
return null;
|
|
31244
|
+
const reference = head.slice(0, slash);
|
|
31245
|
+
const task = head.slice(slash + 1);
|
|
31246
|
+
if (!isValidPhaseIdentity(reference) || /\s/.test(task) || !buildTaskIdSchema.safeParse(task).success)
|
|
31247
|
+
return null;
|
|
31248
|
+
return { kind: "reject", reference, task, words: rest.slice(colon + 1).trim() };
|
|
31249
|
+
}
|
|
30449
31250
|
function partShape(act, head, words) {
|
|
30450
31251
|
const slash = head.indexOf("/");
|
|
30451
31252
|
if (slash === -1)
|
|
@@ -30458,10 +31259,15 @@ function partShape(act, head, words) {
|
|
|
30458
31259
|
}
|
|
30459
31260
|
function matchReviewPhraseShape(shape, reviews) {
|
|
30460
31261
|
const resolved = resolvePhaseReference(shape.reference, reviews);
|
|
30461
|
-
const act = shape.kind === "phase" ? "accept" : shape.act;
|
|
31262
|
+
const act = shape.kind === "phase" ? "accept" : shape.kind === "reject" ? "reject" : shape.act;
|
|
30462
31263
|
if (resolved.kind === "ambiguous")
|
|
30463
31264
|
return { kind: "ambiguous", act, entries: resolved.entries };
|
|
30464
31265
|
const entry = resolved.kind === "found" ? resolved.entry : null;
|
|
31266
|
+
if (shape.kind === "reject") {
|
|
31267
|
+
if (entry === null)
|
|
31268
|
+
return { kind: "show-first", act: "reject" };
|
|
31269
|
+
return { kind: "reject", entry, reference: shape.reference, task: shape.task, words: shape.words };
|
|
31270
|
+
}
|
|
30465
31271
|
if (shape.kind === "phase") {
|
|
30466
31272
|
if (entry === null)
|
|
30467
31273
|
return { kind: "nothing" };
|
|
@@ -30481,20 +31287,20 @@ function matchReviewPhraseShape(shape, reviews) {
|
|
|
30481
31287
|
}
|
|
30482
31288
|
|
|
30483
31289
|
// dist/hooks/shown-record.js
|
|
30484
|
-
import { chmodSync as chmodSync4, readFileSync as
|
|
31290
|
+
import { chmodSync as chmodSync4, readFileSync as readFileSync24, renameSync as renameSync13, unlinkSync as unlinkSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
30485
31291
|
import { homedir as homedir11, platform as platform3 } from "node:os";
|
|
30486
|
-
import { join as
|
|
31292
|
+
import { join as join18 } from "node:path";
|
|
30487
31293
|
function recordDir(engagementId, home) {
|
|
30488
31294
|
if (!isEngagementId(engagementId))
|
|
30489
31295
|
return null;
|
|
30490
|
-
return
|
|
31296
|
+
return join18(home ?? homedir11(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
30491
31297
|
}
|
|
30492
31298
|
function readShownRecord(engagementId, home) {
|
|
30493
31299
|
const dir = recordDir(engagementId, home);
|
|
30494
31300
|
if (dir === null)
|
|
30495
31301
|
return { entries: [], reviews: [] };
|
|
30496
31302
|
try {
|
|
30497
|
-
return parseShownRecord(
|
|
31303
|
+
return parseShownRecord(readFileSync24(join18(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
30498
31304
|
} catch {
|
|
30499
31305
|
return { entries: [], reviews: [] };
|
|
30500
31306
|
}
|
|
@@ -30515,24 +31321,24 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
30515
31321
|
const dir = recordDir(engagementId, home);
|
|
30516
31322
|
if (dir === null)
|
|
30517
31323
|
return false;
|
|
30518
|
-
const path =
|
|
30519
|
-
const current = parseShownRecord(
|
|
31324
|
+
const path = join18(dir, SHOWN_PLANS_FILENAME);
|
|
31325
|
+
const current = parseShownRecord(readFileSync24(path, "utf-8"));
|
|
30520
31326
|
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
30521
31327
|
if (at === -1)
|
|
30522
31328
|
return false;
|
|
30523
31329
|
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
30524
31330
|
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
30525
31331
|
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
30526
|
-
temp =
|
|
30527
|
-
|
|
31332
|
+
temp = join18(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
31333
|
+
writeFileSync15(temp, body, { mode: 384 });
|
|
30528
31334
|
if (platform3() !== "win32")
|
|
30529
31335
|
chmodSync4(temp, 384);
|
|
30530
|
-
|
|
31336
|
+
renameSync13(temp, path);
|
|
30531
31337
|
return true;
|
|
30532
31338
|
} catch {
|
|
30533
31339
|
if (temp !== null) {
|
|
30534
31340
|
try {
|
|
30535
|
-
|
|
31341
|
+
unlinkSync11(temp);
|
|
30536
31342
|
} catch {
|
|
30537
31343
|
return false;
|
|
30538
31344
|
}
|
|
@@ -30650,6 +31456,8 @@ function reviewWrongActLine(name, built) {
|
|
|
30650
31456
|
}
|
|
30651
31457
|
function wordsActNoun(act) {
|
|
30652
31458
|
switch (act) {
|
|
31459
|
+
case "reject":
|
|
31460
|
+
return "a rejection";
|
|
30653
31461
|
case "question":
|
|
30654
31462
|
return "a question";
|
|
30655
31463
|
case "send-back":
|
|
@@ -30686,7 +31494,10 @@ function reviewPartNotListedLine(part, phase) {
|
|
|
30686
31494
|
return line(`${NOTHING_RECORDED}: "${part}" is not a part of the review of ${namedPhase(phase)} as it was listed here.`);
|
|
30687
31495
|
}
|
|
30688
31496
|
function reviewNothingToAcceptLine(phase) {
|
|
30689
|
-
return line(`${NOTHING_RECORDED}: no built part of ${namedPhase(phase)} waits for your decision.`);
|
|
31497
|
+
return line(`${NOTHING_RECORDED}: no built part and no check nobody ran of ${namedPhase(phase)} waits for your decision.`);
|
|
31498
|
+
}
|
|
31499
|
+
function reviewNoClaimWaitingLine(task, phase) {
|
|
31500
|
+
return line(`${NOTHING_RECORDED}: no check nobody ran on task "${task}" of ${namedPhase(phase)} waits for your answer. ${LIST_AGAIN} and copy the task from its reject line.`);
|
|
30690
31501
|
}
|
|
30691
31502
|
function reviewListedTooLongAgoLine(phase) {
|
|
30692
31503
|
return line(`${NOTHING_RECORDED}: ${namedPhase(phase)} was listed an hour or more ago. ${LIST_AGAIN}, then type the line again within the hour.`);
|
|
@@ -30762,9 +31573,12 @@ function reviewRecordedLine(decided, phase, waiting) {
|
|
|
30762
31573
|
}
|
|
30763
31574
|
function reviewBulkRecordedLine(accepted, phase, waiting) {
|
|
30764
31575
|
const named = namedPhase(phase);
|
|
30765
|
-
const first = accepted === 1 ? `Recorded in your session: 1 part of ${named} accepted.` : `Recorded in your session: ${accepted} parts of ${named} accepted.`;
|
|
31576
|
+
const first = accepted === 0 ? `Recorded in your session: any check nobody ran of ${named} that waited is accepted.` : accepted === 1 ? `Recorded in your session: 1 part of ${named} accepted, with any check nobody ran that waited.` : `Recorded in your session: ${accepted} parts of ${named} accepted, with any check nobody ran that waited.`;
|
|
30766
31577
|
return line(`${first} ${stillWaiting(waiting)}`);
|
|
30767
31578
|
}
|
|
31579
|
+
function reviewClaimsRejectedLine(task, phase, waiting) {
|
|
31580
|
+
return line(`Recorded in your session: the checks nobody ran on task "${task}" are rejected, with your words. ${stillWaitingOf(phase, waiting)}`);
|
|
31581
|
+
}
|
|
30768
31582
|
function reviewClosedLine(phase) {
|
|
30769
31583
|
return `Recorded in your session. That was the last decision: phase ${namedPhase(phase)} is closed. Run its close step now, then tell the person what it says.`;
|
|
30770
31584
|
}
|
|
@@ -30791,12 +31605,16 @@ function reviewLineForAnswer(context, answer) {
|
|
|
30791
31605
|
switch (answer.outcome) {
|
|
30792
31606
|
case "recorded": {
|
|
30793
31607
|
if (answer.closed) {
|
|
30794
|
-
return
|
|
31608
|
+
return context.listedParts === 0 ? reviewNoPartsClosedLine(phase) : reviewClosedLine(phase);
|
|
30795
31609
|
}
|
|
31610
|
+
if (context.task !== null)
|
|
31611
|
+
return reviewClaimsRejectedLine(context.task, phase, answer.waiting);
|
|
30796
31612
|
if (context.bulk)
|
|
30797
31613
|
return reviewBulkRecordedLine(answer.decided.length, phase, answer.waiting);
|
|
30798
31614
|
return reviewRecordedLine(answer.decided[0], phase, answer.waiting);
|
|
30799
31615
|
}
|
|
31616
|
+
case "nothing-waiting":
|
|
31617
|
+
return context.task !== null ? reviewNoClaimWaitingLine(context.task, phase) : reviewNothingToAcceptLine(phase);
|
|
30800
31618
|
case "review-changed":
|
|
30801
31619
|
return reviewChangedLine(phase);
|
|
30802
31620
|
case "no-review":
|
|
@@ -30864,16 +31682,63 @@ function autonomyLineForAnswer(answer) {
|
|
|
30864
31682
|
}
|
|
30865
31683
|
}
|
|
30866
31684
|
}
|
|
31685
|
+
var NOTHING_ALLOWED = "Nothing was allowed";
|
|
31686
|
+
var ALLOW_MINUTES = Math.round(ALLOW_ONCE_MS / 6e4);
|
|
31687
|
+
function allowArmedLine() {
|
|
31688
|
+
return line(`The person allowed the paused step once. 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
|
+
}
|
|
31690
|
+
function allowNothingHeldLine() {
|
|
31691
|
+
return line(`${NOTHING_ALLOWED}: no step is paused in this session.`);
|
|
31692
|
+
}
|
|
31693
|
+
function allowSeveralHeldLine() {
|
|
31694
|
+
return line(`${NOTHING_ALLOWED}: more than one step is paused in this session. Ask the agent to try the one you mean again, then type ${ALLOW_REPLY}.`);
|
|
31695
|
+
}
|
|
31696
|
+
function allowNotAttendedLine() {
|
|
31697
|
+
return line(`${NOTHING_ALLOWED}: a paused step can be allowed by typing only when a person types it into an open session.`);
|
|
31698
|
+
}
|
|
31699
|
+
function allowNotSavedLine() {
|
|
31700
|
+
return line(`${NOTHING_ALLOWED}: Halfcycle could not save the answer on this computer. Type ${ALLOW_REPLY} again.`);
|
|
31701
|
+
}
|
|
31702
|
+
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";
|
|
31703
|
+
function codexScopeLine(act) {
|
|
31704
|
+
return act === "approve" ? line(`${nothingDone(act)}: ${CODEX_TYPING_NEVER_COUNTS}. To approve, choose Accept in the console.`) : line(`${nothingDone(act)}: ${CODEX_TYPING_NEVER_COUNTS}. The console offers only Accept, so work cannot be sent back from Codex.`);
|
|
31705
|
+
}
|
|
31706
|
+
function codexReviewLine(act) {
|
|
31707
|
+
return act === "accept" ? line(`${NOTHING_RECORDED}: ${CODEX_TYPING_NEVER_COUNTS}. To accept, choose Accept in the console.`) : line(`${NOTHING_RECORDED}: ${CODEX_TYPING_NEVER_COUNTS}. The console offers only Accept, so this cannot be done from Codex.`);
|
|
31708
|
+
}
|
|
31709
|
+
function codexAutonomyLine() {
|
|
31710
|
+
return line(`${AUTONOMY_NOTHING_CHANGED}: ${CODEX_TYPING_NEVER_COUNTS}. Turn it on or off with "${AUTONOMY_A1_TITLE}" in the console's settings.`);
|
|
31711
|
+
}
|
|
30867
31712
|
|
|
30868
31713
|
// dist/hooks/user-prompt-submit.js
|
|
30869
31714
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
30870
31715
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
30871
31716
|
async function runUserPromptSubmit(opts) {
|
|
31717
|
+
return decideLine(opts);
|
|
31718
|
+
}
|
|
31719
|
+
function promptReplyFor(line2, agent) {
|
|
31720
|
+
if (line2 === "" || agent === "claude-code")
|
|
31721
|
+
return line2;
|
|
31722
|
+
return `${userPromptContextReply(line2.trimEnd())}
|
|
31723
|
+
`;
|
|
31724
|
+
}
|
|
31725
|
+
function whoTyped(opts) {
|
|
31726
|
+
if (agentOf(opts) !== "claude-code")
|
|
31727
|
+
return "cannot-tell";
|
|
31728
|
+
return readSessionAttended(opts.env ?? process.env) ? "person" : "not-attended";
|
|
31729
|
+
}
|
|
31730
|
+
function agentOf(opts) {
|
|
31731
|
+
return opts.agent ?? "claude-code";
|
|
31732
|
+
}
|
|
31733
|
+
async function decideLine(opts) {
|
|
30872
31734
|
let act = null;
|
|
30873
31735
|
let autonomyAct = null;
|
|
30874
31736
|
const review = { recognised: false };
|
|
30875
31737
|
try {
|
|
30876
31738
|
const prompt = promptOf(opts.input);
|
|
31739
|
+
if (isAllowReply(prompt))
|
|
31740
|
+
return `${decideAllow(opts)}
|
|
31741
|
+
`;
|
|
30877
31742
|
const autonomyShape = autonomyPhraseShape(prompt);
|
|
30878
31743
|
if (autonomyShape !== null) {
|
|
30879
31744
|
autonomyAct = autonomyShape;
|
|
@@ -30903,9 +31768,35 @@ async function runUserPromptSubmit(opts) {
|
|
|
30903
31768
|
`;
|
|
30904
31769
|
}
|
|
30905
31770
|
}
|
|
31771
|
+
function decideAllow(opts) {
|
|
31772
|
+
const env = opts.env ?? process.env;
|
|
31773
|
+
if ((opts.agent ?? "claude-code") === "claude-code" && !readSessionAttended(env))
|
|
31774
|
+
return allowNotAttendedLine();
|
|
31775
|
+
const outcome = armAllow(sessionIdOf(opts.input), {
|
|
31776
|
+
...opts.tempDir === void 0 ? {} : { dir: opts.tempDir },
|
|
31777
|
+
...opts.now === void 0 ? {} : { now: opts.now }
|
|
31778
|
+
});
|
|
31779
|
+
switch (outcome) {
|
|
31780
|
+
case "armed":
|
|
31781
|
+
return allowArmedLine();
|
|
31782
|
+
case "nothing-held":
|
|
31783
|
+
return allowNothingHeldLine();
|
|
31784
|
+
case "several-held":
|
|
31785
|
+
return allowSeveralHeldLine();
|
|
31786
|
+
case "not-saved":
|
|
31787
|
+
return allowNotSavedLine();
|
|
31788
|
+
default: {
|
|
31789
|
+
const unhandled = outcome;
|
|
31790
|
+
return unhandled;
|
|
31791
|
+
}
|
|
31792
|
+
}
|
|
31793
|
+
}
|
|
30906
31794
|
async function decideAutonomy(act, opts) {
|
|
30907
31795
|
const env = opts.env ?? process.env;
|
|
30908
|
-
|
|
31796
|
+
const typed = whoTyped(opts);
|
|
31797
|
+
if (typed === "cannot-tell")
|
|
31798
|
+
return codexAutonomyLine();
|
|
31799
|
+
if (typed === "not-attended")
|
|
30909
31800
|
return autonomyNotAttendedLine();
|
|
30910
31801
|
const read = readConfig(env);
|
|
30911
31802
|
if (read.config === null) {
|
|
@@ -30951,12 +31842,18 @@ async function decideReview(shape, opts, progress) {
|
|
|
30951
31842
|
if (match2.kind === "nothing")
|
|
30952
31843
|
return "";
|
|
30953
31844
|
progress.recognised = true;
|
|
30954
|
-
|
|
31845
|
+
const typed2 = whoTyped(opts);
|
|
31846
|
+
if (typed2 === "cannot-tell")
|
|
31847
|
+
return codexReviewLine("accept");
|
|
31848
|
+
if (typed2 === "not-attended")
|
|
30955
31849
|
return reviewNotAttendedLine();
|
|
30956
31850
|
return checkAndSendReview(match2, read2.config.guardEngagementId, read2.config.guardServiceToken, env, opts);
|
|
30957
31851
|
}
|
|
30958
31852
|
progress.recognised = true;
|
|
30959
|
-
|
|
31853
|
+
const typed = whoTyped(opts);
|
|
31854
|
+
if (typed === "cannot-tell")
|
|
31855
|
+
return codexReviewLine(shape.kind === "part" && shape.act === "accept" ? "accept" : "other");
|
|
31856
|
+
if (typed === "not-attended")
|
|
30960
31857
|
return reviewNotAttendedLine();
|
|
30961
31858
|
const read = readConfig(env);
|
|
30962
31859
|
if (read.config === null) {
|
|
@@ -30977,14 +31874,22 @@ async function checkAndSendReview(match, engagementId, credential, env, opts) {
|
|
|
30977
31874
|
return reviewListedTooLongAgoLine(entry.phase);
|
|
30978
31875
|
let candidate;
|
|
30979
31876
|
if (match.kind === "accept") {
|
|
30980
|
-
if (entry.parts.length > 0 && match.parts.length === 0)
|
|
30981
|
-
return reviewNothingToAcceptLine(entry.phase);
|
|
30982
31877
|
candidate = {
|
|
30983
31878
|
act: "accept",
|
|
30984
31879
|
reference: match.reference,
|
|
30985
31880
|
reviewVersion: entry.reviewVersion,
|
|
30986
|
-
parts: match.parts.map((p) => p.part)
|
|
31881
|
+
parts: match.parts.map((p) => p.part),
|
|
31882
|
+
waitingClaims: true
|
|
30987
31883
|
};
|
|
31884
|
+
} else if (match.kind === "reject") {
|
|
31885
|
+
const words = match.words;
|
|
31886
|
+
if (words === "")
|
|
31887
|
+
return reviewNoWordsLine("reject");
|
|
31888
|
+
if (!reviewNoteWireSchema.safeParse(words).success)
|
|
31889
|
+
return reviewWordsTooLongLine();
|
|
31890
|
+
if (looksLikeSecret(words))
|
|
31891
|
+
return reviewWordsLookSecretLine();
|
|
31892
|
+
candidate = { act: "reject", reference: match.reference, reviewVersion: entry.reviewVersion, task: match.task, note: words };
|
|
30988
31893
|
} else {
|
|
30989
31894
|
const { act, part } = match;
|
|
30990
31895
|
if (!reviewActsFor(part.built).includes(act))
|
|
@@ -31037,7 +31942,14 @@ async function postReview(bulk, entry, request, engagementId, credential, env, o
|
|
|
31037
31942
|
return reviewCannotReachLine();
|
|
31038
31943
|
moveListedReviewForward(engagementId, opts.home, entry, answer.reviewVersion, answer.decided);
|
|
31039
31944
|
}
|
|
31040
|
-
return reviewLineForAnswer({
|
|
31945
|
+
return reviewLineForAnswer({
|
|
31946
|
+
act: request.act,
|
|
31947
|
+
bulk,
|
|
31948
|
+
phase: entry.phase,
|
|
31949
|
+
number: entry.number,
|
|
31950
|
+
listedParts: entry.parts.length,
|
|
31951
|
+
task: request.act === "reject" ? request.task : null
|
|
31952
|
+
}, answer);
|
|
31041
31953
|
} catch {
|
|
31042
31954
|
return reviewCannotReachLine();
|
|
31043
31955
|
} finally {
|
|
@@ -31047,7 +31959,10 @@ async function postReview(bulk, entry, request, engagementId, credential, env, o
|
|
|
31047
31959
|
async function decide2(shape, opts) {
|
|
31048
31960
|
const { act } = shape;
|
|
31049
31961
|
const env = opts.env ?? process.env;
|
|
31050
|
-
|
|
31962
|
+
const typed = whoTyped(opts);
|
|
31963
|
+
if (typed === "cannot-tell")
|
|
31964
|
+
return codexScopeLine(act);
|
|
31965
|
+
if (typed === "not-attended")
|
|
31051
31966
|
return notAttendedLine(act);
|
|
31052
31967
|
const read = readConfig(env);
|
|
31053
31968
|
if (read.config === null) {
|
|
@@ -31080,6 +31995,16 @@ async function decide2(shape, opts) {
|
|
|
31080
31995
|
return showFirstLine(act);
|
|
31081
31996
|
return post(act, entry, request.data, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
31082
31997
|
}
|
|
31998
|
+
function sessionIdOf(input) {
|
|
31999
|
+
try {
|
|
32000
|
+
const parsed = JSON.parse(input);
|
|
32001
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
32002
|
+
return void 0;
|
|
32003
|
+
return parsed["session_id"];
|
|
32004
|
+
} catch {
|
|
32005
|
+
return void 0;
|
|
32006
|
+
}
|
|
32007
|
+
}
|
|
31083
32008
|
function promptOf(input) {
|
|
31084
32009
|
try {
|
|
31085
32010
|
const parsed = JSON.parse(input);
|
|
@@ -31141,17 +32066,17 @@ function runUserPromptFindings(opts) {
|
|
|
31141
32066
|
}
|
|
31142
32067
|
|
|
31143
32068
|
// dist/resolve-engagement.js
|
|
31144
|
-
import { existsSync as existsSync8, readFileSync as
|
|
31145
|
-
import { join as
|
|
32069
|
+
import { existsSync as existsSync8, readFileSync as readFileSync25, realpathSync as realpathSync8 } from "node:fs";
|
|
32070
|
+
import { join as join19, sep as sep5 } from "node:path";
|
|
31146
32071
|
import { homedir as homedir12 } from "node:os";
|
|
31147
32072
|
function defaultRegistryPath() {
|
|
31148
|
-
return
|
|
32073
|
+
return join19(homedir12(), ".halfcycle", "registry.json");
|
|
31149
32074
|
}
|
|
31150
32075
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
31151
32076
|
if (!existsSync8(registryPath))
|
|
31152
32077
|
return {};
|
|
31153
32078
|
try {
|
|
31154
|
-
const parsed = JSON.parse(
|
|
32079
|
+
const parsed = JSON.parse(readFileSync25(registryPath, "utf-8"));
|
|
31155
32080
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
31156
32081
|
return {};
|
|
31157
32082
|
const out = {};
|
|
@@ -31187,7 +32112,7 @@ function resolveEngagement(cwd, registry2) {
|
|
|
31187
32112
|
function isSelfOrAncestor(ancestor, target) {
|
|
31188
32113
|
if (ancestor === target)
|
|
31189
32114
|
return true;
|
|
31190
|
-
const withSep = ancestor.endsWith(
|
|
32115
|
+
const withSep = ancestor.endsWith(sep5) ? ancestor : ancestor + sep5;
|
|
31191
32116
|
return target.startsWith(withSep);
|
|
31192
32117
|
}
|
|
31193
32118
|
function runResolveEngagement(cwd, registryPath) {
|
|
@@ -31199,13 +32124,27 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
31199
32124
|
}
|
|
31200
32125
|
|
|
31201
32126
|
// dist/refresh-credential.js
|
|
31202
|
-
import { readFileSync as
|
|
31203
|
-
import { resolve as
|
|
32127
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
32128
|
+
import { resolve as resolve9 } from "node:path";
|
|
31204
32129
|
|
|
31205
32130
|
// dist/mcp-headers.js
|
|
31206
|
-
import { readFileSync as
|
|
32131
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
31207
32132
|
import { homedir as homedir13 } from "node:os";
|
|
31208
|
-
import { dirname as dirname9, resolve as
|
|
32133
|
+
import { dirname as dirname9, resolve as resolve8 } from "node:path";
|
|
32134
|
+
var URL_FLAG = "--url";
|
|
32135
|
+
function urlFromArguments(rest) {
|
|
32136
|
+
const at = [];
|
|
32137
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
32138
|
+
if ((rest[i] ?? "").startsWith(URL_FLAG))
|
|
32139
|
+
at.push(i);
|
|
32140
|
+
}
|
|
32141
|
+
if (at.length === 0)
|
|
32142
|
+
return void 0;
|
|
32143
|
+
const only = at[0];
|
|
32144
|
+
if (at.length !== 1 || rest[only] !== URL_FLAG)
|
|
32145
|
+
return "";
|
|
32146
|
+
return rest[only + 1] ?? "";
|
|
32147
|
+
}
|
|
31209
32148
|
function defaultWriteErr(text) {
|
|
31210
32149
|
process.stderr.write(text);
|
|
31211
32150
|
}
|
|
@@ -31218,12 +32157,12 @@ function headerLine(bearer, clientVersion) {
|
|
|
31218
32157
|
}
|
|
31219
32158
|
function projectRootFromBinary(binaryPath) {
|
|
31220
32159
|
const depth = VENDORED_BIN_REL.split("/").length - 1;
|
|
31221
|
-
return
|
|
32160
|
+
return resolve8(dirname9(binaryPath), ...Array(depth).fill(".."));
|
|
31222
32161
|
}
|
|
31223
32162
|
function readPinnedVersion(root) {
|
|
31224
32163
|
let raw;
|
|
31225
32164
|
try {
|
|
31226
|
-
raw =
|
|
32165
|
+
raw = readFileSync26(resolve8(root, BUNDLE_PIN_REL), "utf-8");
|
|
31227
32166
|
} catch {
|
|
31228
32167
|
return void 0;
|
|
31229
32168
|
}
|
|
@@ -31265,7 +32204,7 @@ function sentences(lines2) {
|
|
|
31265
32204
|
}
|
|
31266
32205
|
function badIdSentence(root) {
|
|
31267
32206
|
return sentences([
|
|
31268
|
-
`${
|
|
32207
|
+
`${resolve8(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
|
|
31269
32208
|
'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
|
|
31270
32209
|
"is then set up as a new Halfcycle project."
|
|
31271
32210
|
]);
|
|
@@ -31279,7 +32218,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
31279
32218
|
]);
|
|
31280
32219
|
case "no-id":
|
|
31281
32220
|
return sentences([
|
|
31282
|
-
`${
|
|
32221
|
+
`${resolve8(root, BUNDLE_PIN_REL)} names no engagement id.`,
|
|
31283
32222
|
'run "npx halfcycle" here to rewrite it.'
|
|
31284
32223
|
]);
|
|
31285
32224
|
case "bad-id":
|
|
@@ -31296,7 +32235,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
31296
32235
|
case "no-credential":
|
|
31297
32236
|
return sentences([
|
|
31298
32237
|
`this machine holds no credential for engagement ${engagementId ?? ""}.`,
|
|
31299
|
-
`run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${
|
|
32238
|
+
`run "npx halfcycle" in this repository \u2014 it will sign you in through your browser if needed, and write the credential to ${resolve8(home, HALFCYCLE_DIR_NAME)}, outside this repository.`
|
|
31300
32239
|
]);
|
|
31301
32240
|
}
|
|
31302
32241
|
}
|
|
@@ -31358,13 +32297,14 @@ async function chooseBearer(opts) {
|
|
|
31358
32297
|
}
|
|
31359
32298
|
return NO_CREDENTIAL_PLACEHOLDER;
|
|
31360
32299
|
}
|
|
32300
|
+
const registrationFile = opts.registrationFile ?? MCP_REGISTRATION_REL;
|
|
31361
32301
|
const want = urlOrigin(recorded);
|
|
31362
32302
|
const got = requested === void 0 ? void 0 : urlOrigin(requested);
|
|
31363
32303
|
if (want === void 0 || got !== want) {
|
|
31364
32304
|
writeErr(sentences([
|
|
31365
|
-
|
|
32305
|
+
`${registrationFile} asks for this repository's Halfcycle credential to be sent to ${shownUrl(requested ?? "")},`,
|
|
31366
32306
|
`which is not this machine's Halfcycle server (${want ?? "none recorded"}). The credential was NOT sent.`,
|
|
31367
|
-
|
|
32307
|
+
`if nobody meant to change ${registrationFile}, treat that change as suspect; "npx halfcycle" restores the entry.`
|
|
31368
32308
|
]));
|
|
31369
32309
|
return NO_CREDENTIAL_PLACEHOLDER;
|
|
31370
32310
|
}
|
|
@@ -31384,13 +32324,18 @@ async function chooseBearer(opts) {
|
|
|
31384
32324
|
async function mcpHeaders(opts) {
|
|
31385
32325
|
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
31386
32326
|
const root = projectRootFromBinary(opts.binaryPath);
|
|
32327
|
+
const variableUrl = readMcpServerUrl();
|
|
31387
32328
|
try {
|
|
31388
32329
|
return headerLine(await chooseBearer({
|
|
31389
|
-
|
|
32330
|
+
binaryPath: opts.binaryPath,
|
|
32331
|
+
home: opts.home,
|
|
32332
|
+
fetchFn: opts.fetchFn,
|
|
32333
|
+
deadlineMs: opts.deadlineMs,
|
|
31390
32334
|
writeErr,
|
|
31391
|
-
requestedUrl:
|
|
32335
|
+
requestedUrl: variableUrl ?? opts.url,
|
|
31392
32336
|
renewal: "renew-here",
|
|
31393
|
-
environmentTokenFirst: true
|
|
32337
|
+
environmentTokenFirst: true,
|
|
32338
|
+
registrationFile: variableUrl === void 0 && opts.url !== void 0 ? AGENT_PROFILES.codex.mcpRegistration.file : void 0
|
|
31394
32339
|
}), readPinnedVersion(root));
|
|
31395
32340
|
} catch (err) {
|
|
31396
32341
|
try {
|
|
@@ -31404,6 +32349,25 @@ async function mcpHeaders(opts) {
|
|
|
31404
32349
|
return headerLine(NO_CREDENTIAL_PLACEHOLDER, readPinnedVersion(root));
|
|
31405
32350
|
}
|
|
31406
32351
|
}
|
|
32352
|
+
async function headersVerbLine(binaryPath, rest, run = mcpHeaders, writeErr = defaultWriteErr) {
|
|
32353
|
+
const url2 = urlFromArguments(rest);
|
|
32354
|
+
try {
|
|
32355
|
+
return await run({ binaryPath, url: url2 });
|
|
32356
|
+
} catch (err) {
|
|
32357
|
+
if (url2 === void 0)
|
|
32358
|
+
throw err;
|
|
32359
|
+
const fallback = headerLine(NO_CREDENTIAL_PLACEHOLDER, readPinnedVersion(projectRootFromBinary(binaryPath)));
|
|
32360
|
+
try {
|
|
32361
|
+
writeErr(sentences([
|
|
32362
|
+
`the credential could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
32363
|
+
`run "npx halfcycle" in this repository to rewrite this machine's credential.`
|
|
32364
|
+
]));
|
|
32365
|
+
} catch {
|
|
32366
|
+
return fallback;
|
|
32367
|
+
}
|
|
32368
|
+
return fallback;
|
|
32369
|
+
}
|
|
32370
|
+
}
|
|
31407
32371
|
|
|
31408
32372
|
// dist/refresh-credential.js
|
|
31409
32373
|
var UNAUTHORIZED2 = 401;
|
|
@@ -31444,13 +32408,19 @@ async function refreshCredentialToken(opts) {
|
|
|
31444
32408
|
return { token: refreshed.token };
|
|
31445
32409
|
}
|
|
31446
32410
|
var CHECKED_SOURCES = /* @__PURE__ */ new Set(["startup", "resume"]);
|
|
32411
|
+
function isCheckedSessionStart(input, profile) {
|
|
32412
|
+
const source = input["source"];
|
|
32413
|
+
if (source === void 0 && profile.id === "codex")
|
|
32414
|
+
return true;
|
|
32415
|
+
return CHECKED_SOURCES.has(source);
|
|
32416
|
+
}
|
|
31447
32417
|
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.`;
|
|
31448
32418
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
31449
32419
|
var SESSION_START_PROBE_ATTEMPTS = 2;
|
|
31450
32420
|
function registeredMcpUrl(projectRoot) {
|
|
31451
32421
|
let parsed;
|
|
31452
32422
|
try {
|
|
31453
|
-
parsed = JSON.parse(
|
|
32423
|
+
parsed = JSON.parse(readFileSync27(resolve9(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
31454
32424
|
} catch {
|
|
31455
32425
|
return void 0;
|
|
31456
32426
|
}
|
|
@@ -31622,6 +32592,7 @@ function defaultWriteErr2(text) {
|
|
|
31622
32592
|
}
|
|
31623
32593
|
async function runRefreshCredential(opts) {
|
|
31624
32594
|
const writeErr = opts.writeErr ?? defaultWriteErr2;
|
|
32595
|
+
const profile = opts.profile ?? AGENT_PROFILES["claude-code"];
|
|
31625
32596
|
const startedAt = Date.now();
|
|
31626
32597
|
try {
|
|
31627
32598
|
const renewalSaid = [];
|
|
@@ -31635,7 +32606,7 @@ async function runRefreshCredential(opts) {
|
|
|
31635
32606
|
]);
|
|
31636
32607
|
renewalOpen = false;
|
|
31637
32608
|
const parts = [];
|
|
31638
|
-
if (
|
|
32609
|
+
if (isCheckedSessionStart(input, profile)) {
|
|
31639
32610
|
const connection = await checkConnection(renewedToken, { ...opts, writeErr });
|
|
31640
32611
|
if (connection !== void 0) {
|
|
31641
32612
|
parts.push(connection.sentence);
|
|
@@ -31657,7 +32628,7 @@ async function runRefreshCredential(opts) {
|
|
|
31657
32628
|
}
|
|
31658
32629
|
}
|
|
31659
32630
|
}
|
|
31660
|
-
if (
|
|
32631
|
+
if (isCheckedSessionStart(input, profile)) {
|
|
31661
32632
|
const config2 = readConfig().config;
|
|
31662
32633
|
if (config2 !== null) {
|
|
31663
32634
|
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
@@ -31697,7 +32668,7 @@ async function runRefreshCredential(opts) {
|
|
|
31697
32668
|
import { fileURLToPath } from "node:url";
|
|
31698
32669
|
|
|
31699
32670
|
// dist/capture.js
|
|
31700
|
-
import { readFileSync as
|
|
32671
|
+
import { readFileSync as readFileSync28, writeFileSync as writeFileSync16, mkdirSync as mkdirSync10, existsSync as existsSync9 } from "node:fs";
|
|
31701
32672
|
import { dirname as dirname10 } from "node:path";
|
|
31702
32673
|
function runCapture(provider, endpoint, opts = {}) {
|
|
31703
32674
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -31734,7 +32705,7 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
31734
32705
|
const manifestFile = manifestPath(repoRoot);
|
|
31735
32706
|
try {
|
|
31736
32707
|
mkdirSync10(dirname10(fixture), { recursive: true });
|
|
31737
|
-
|
|
32708
|
+
writeFileSync16(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
31738
32709
|
} catch (err) {
|
|
31739
32710
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31740
32711
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -31765,7 +32736,7 @@ function readInputFile(fromFile) {
|
|
|
31765
32736
|
if (!fromFile)
|
|
31766
32737
|
return void 0;
|
|
31767
32738
|
try {
|
|
31768
|
-
return
|
|
32739
|
+
return readFileSync28(fromFile, "utf-8");
|
|
31769
32740
|
} catch {
|
|
31770
32741
|
return void 0;
|
|
31771
32742
|
}
|
|
@@ -31777,10 +32748,10 @@ function relativeFixturePath(repoRoot, fixture) {
|
|
|
31777
32748
|
|
|
31778
32749
|
// dist/prove.js
|
|
31779
32750
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
31780
|
-
import { createHash as
|
|
31781
|
-
import { createWriteStream, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as
|
|
32751
|
+
import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
|
|
32752
|
+
import { createWriteStream, mkdirSync as mkdirSync11, readdirSync as readdirSync10, readFileSync as readFileSync29, renameSync as renameSync14, unlinkSync as unlinkSync12, writeFileSync as writeFileSync17 } from "node:fs";
|
|
31782
32753
|
import { homedir as homedir14 } from "node:os";
|
|
31783
|
-
import { dirname as dirname11, join as
|
|
32754
|
+
import { dirname as dirname11, join as join20 } from "node:path";
|
|
31784
32755
|
|
|
31785
32756
|
// dist/test-counts.js
|
|
31786
32757
|
function sum(values) {
|
|
@@ -31889,10 +32860,10 @@ function isProofTestPath(path) {
|
|
|
31889
32860
|
function testRunsDir(engagementId, home) {
|
|
31890
32861
|
if (!isEngagementId(engagementId))
|
|
31891
32862
|
return null;
|
|
31892
|
-
return
|
|
32863
|
+
return join20(home ?? homedir14(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, TEST_RUNS_DIR_NAME);
|
|
31893
32864
|
}
|
|
31894
32865
|
function commandIdOf(command) {
|
|
31895
|
-
return
|
|
32866
|
+
return createHash6("sha256").update(command, "utf8").digest("hex").slice(0, 16);
|
|
31896
32867
|
}
|
|
31897
32868
|
var PLAIN_WORD = /^[A-Za-z0-9_./:=@%+,-]+$/;
|
|
31898
32869
|
function commandFromWords(words, platform4 = process.platform) {
|
|
@@ -31977,19 +32948,19 @@ function parseProveArgs(args2) {
|
|
|
31977
32948
|
function writeJsonFile2(path, value) {
|
|
31978
32949
|
mkdirSync11(dirname11(path), { recursive: true, mode: 448 });
|
|
31979
32950
|
const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
31980
|
-
|
|
31981
|
-
|
|
32951
|
+
writeFileSync17(tmp, JSON.stringify(value), { mode: 384 });
|
|
32952
|
+
renameSync14(tmp, path);
|
|
31982
32953
|
}
|
|
31983
32954
|
function subjectKey(subject) {
|
|
31984
32955
|
return subject.kind === "task" ? ["task", subject.phase, subject.task] : ["change", subject.change];
|
|
31985
32956
|
}
|
|
31986
32957
|
function keptCheckPath(dir, subject, label) {
|
|
31987
|
-
const key =
|
|
31988
|
-
return
|
|
32958
|
+
const key = createHash6("sha256").update(JSON.stringify([...subjectKey(subject), label]), "utf8").digest("hex");
|
|
32959
|
+
return join20(dir, COMMANDS_DIR_NAME, `${key.slice(0, 32)}.json`);
|
|
31989
32960
|
}
|
|
31990
32961
|
function readKeptCheck(dir, subject, label) {
|
|
31991
32962
|
try {
|
|
31992
|
-
const raw = JSON.parse(
|
|
32963
|
+
const raw = JSON.parse(readFileSync29(keptCheckPath(dir, subject, label), "utf-8"));
|
|
31993
32964
|
if (typeof raw.command !== "string" || raw.command === "" || typeof raw.commandId !== "string")
|
|
31994
32965
|
return null;
|
|
31995
32966
|
const b = raw.before;
|
|
@@ -32000,21 +32971,21 @@ function readKeptCheck(dir, subject, label) {
|
|
|
32000
32971
|
}
|
|
32001
32972
|
}
|
|
32002
32973
|
function queuePath(dir, runId) {
|
|
32003
|
-
return
|
|
32974
|
+
return join20(dir, QUEUE_DIR_NAME, `${runId}.json`);
|
|
32004
32975
|
}
|
|
32005
32976
|
function readQueue(dir) {
|
|
32006
32977
|
let names;
|
|
32007
32978
|
try {
|
|
32008
|
-
names = readdirSync10(
|
|
32979
|
+
names = readdirSync10(join20(dir, QUEUE_DIR_NAME)).filter((n) => n.endsWith(".json"));
|
|
32009
32980
|
} catch {
|
|
32010
32981
|
return [];
|
|
32011
32982
|
}
|
|
32012
32983
|
const reports = [];
|
|
32013
32984
|
for (const name of names) {
|
|
32014
|
-
const path =
|
|
32985
|
+
const path = join20(dir, QUEUE_DIR_NAME, name);
|
|
32015
32986
|
let parsed = null;
|
|
32016
32987
|
try {
|
|
32017
|
-
parsed = testRunReportSchema.safeParse(JSON.parse(
|
|
32988
|
+
parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync29(path, "utf-8")));
|
|
32018
32989
|
} catch {
|
|
32019
32990
|
parsed = null;
|
|
32020
32991
|
}
|
|
@@ -32027,7 +32998,7 @@ function readQueue(dir) {
|
|
|
32027
32998
|
}
|
|
32028
32999
|
function removeFile(path) {
|
|
32029
33000
|
try {
|
|
32030
|
-
|
|
33001
|
+
unlinkSync12(path);
|
|
32031
33002
|
} catch {
|
|
32032
33003
|
}
|
|
32033
33004
|
}
|
|
@@ -32094,7 +33065,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
32094
33065
|
mkdirSync11(dirname11(logPath), { recursive: true, mode: 448 });
|
|
32095
33066
|
const log = createWriteStream(logPath, { mode: 384 });
|
|
32096
33067
|
const began = process.hrtime.bigint();
|
|
32097
|
-
return new Promise((
|
|
33068
|
+
return new Promise((resolve10) => {
|
|
32098
33069
|
let timedOut = false;
|
|
32099
33070
|
let started = true;
|
|
32100
33071
|
let settled = false;
|
|
@@ -32129,7 +33100,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
32129
33100
|
process.off("SIGINT", onSignal);
|
|
32130
33101
|
process.off("SIGTERM", onSignal);
|
|
32131
33102
|
const durationMs = Number((process.hrtime.bigint() - began) / 1000000n);
|
|
32132
|
-
log.end(() =>
|
|
33103
|
+
log.end(() => resolve10({ exitCode: timedOut || !started ? null : code, timedOut, started, durationMs }));
|
|
32133
33104
|
};
|
|
32134
33105
|
child.on("error", (e) => {
|
|
32135
33106
|
started = false;
|
|
@@ -32183,7 +33154,7 @@ function checkCommand(command, reason, repoRoot, env, home) {
|
|
|
32183
33154
|
...home === void 0 ? {} : { home }
|
|
32184
33155
|
}));
|
|
32185
33156
|
} catch {
|
|
32186
|
-
return { silent: false, question: couldNotTellQuestion(reason ?? null), staked: [] };
|
|
33157
|
+
return { silent: false, question: couldNotTellQuestion(reason ?? null), refusal: couldNotTellRefusal(reason ?? null), staked: [] };
|
|
32187
33158
|
}
|
|
32188
33159
|
}
|
|
32189
33160
|
async function runProve(args2, deps = {}) {
|
|
@@ -32251,13 +33222,13 @@ ${PROVE_USAGE}`);
|
|
|
32251
33222
|
if (request.when === "before" && changedOutsideTests > 0)
|
|
32252
33223
|
say(lateBeforeLine(changedOutsideTests));
|
|
32253
33224
|
const runId = randomUUID3();
|
|
32254
|
-
const logPath =
|
|
33225
|
+
const logPath = join20(dir, `${runId}.log`);
|
|
32255
33226
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
32256
33227
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
32257
33228
|
const end = await runCommand(command, repoRoot, logPath, env, deps);
|
|
32258
33229
|
let output = "";
|
|
32259
33230
|
try {
|
|
32260
|
-
output =
|
|
33231
|
+
output = readFileSync29(logPath, "utf-8");
|
|
32261
33232
|
} catch {
|
|
32262
33233
|
output = "";
|
|
32263
33234
|
}
|
|
@@ -32307,7 +33278,7 @@ ${PROVE_USAGE}`);
|
|
|
32307
33278
|
|
|
32308
33279
|
// dist/statusline.js
|
|
32309
33280
|
import { spawn as spawn3 } from "node:child_process";
|
|
32310
|
-
import { readFileSync as
|
|
33281
|
+
import { readFileSync as readFileSync30 } from "node:fs";
|
|
32311
33282
|
import nodePath5 from "node:path";
|
|
32312
33283
|
var OWN_STATUS_LINE_DEADLINE_MS = 5e3;
|
|
32313
33284
|
var PERSON_SETTINGS_FILENAME = "settings.json";
|
|
@@ -32345,7 +33316,7 @@ function halfcycleStatusLineOf(figures) {
|
|
|
32345
33316
|
}
|
|
32346
33317
|
function personStatusLineCommand(configFolder) {
|
|
32347
33318
|
try {
|
|
32348
|
-
const v = JSON.parse(
|
|
33319
|
+
const v = JSON.parse(readFileSync30(nodePath5.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
|
|
32349
33320
|
const line2 = v["statusLine"];
|
|
32350
33321
|
if (typeof line2 !== "object" || line2 === null)
|
|
32351
33322
|
return null;
|
|
@@ -32369,14 +33340,14 @@ function personCommandEnv(env) {
|
|
|
32369
33340
|
}
|
|
32370
33341
|
return out;
|
|
32371
33342
|
}
|
|
32372
|
-
var runOwnCommandReal = (command, input, env) => new Promise((
|
|
33343
|
+
var runOwnCommandReal = (command, input, env) => new Promise((resolve10) => {
|
|
32373
33344
|
let out = "";
|
|
32374
33345
|
let settled = false;
|
|
32375
33346
|
const done = (v) => {
|
|
32376
33347
|
if (settled)
|
|
32377
33348
|
return;
|
|
32378
33349
|
settled = true;
|
|
32379
|
-
|
|
33350
|
+
resolve10(v);
|
|
32380
33351
|
};
|
|
32381
33352
|
try {
|
|
32382
33353
|
const child = spawn3(command, {
|
|
@@ -32486,7 +33457,12 @@ async function main() {
|
|
|
32486
33457
|
return;
|
|
32487
33458
|
}
|
|
32488
33459
|
if (cmd === "refresh-credential") {
|
|
32489
|
-
const
|
|
33460
|
+
const profile = agentFromArguments(args.slice(1));
|
|
33461
|
+
if (profile === null) {
|
|
33462
|
+
unknownCommand();
|
|
33463
|
+
return;
|
|
33464
|
+
}
|
|
33465
|
+
const reply = await runRefreshCredential({ binaryPath: fileURLToPath(import.meta.url), profile });
|
|
32490
33466
|
process.stdout.write(reply, () => process.exit(0));
|
|
32491
33467
|
return;
|
|
32492
33468
|
}
|
|
@@ -32571,7 +33547,7 @@ async function main() {
|
|
|
32571
33547
|
return;
|
|
32572
33548
|
}
|
|
32573
33549
|
if (cmd === "mcp-headers") {
|
|
32574
|
-
const line2 = await
|
|
33550
|
+
const line2 = await headersVerbLine(fileURLToPath(import.meta.url), args.slice(1));
|
|
32575
33551
|
process.stdout.write(line2, () => process.exit(0));
|
|
32576
33552
|
return;
|
|
32577
33553
|
}
|
|
@@ -32613,25 +33589,30 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32613
33589
|
return;
|
|
32614
33590
|
}
|
|
32615
33591
|
if (cmd === "hook") {
|
|
33592
|
+
const profile = agentFromArguments(args.slice(2));
|
|
33593
|
+
if (profile === null) {
|
|
33594
|
+
unknownCommand();
|
|
33595
|
+
return;
|
|
33596
|
+
}
|
|
32616
33597
|
if (sub === "post-tool-use") {
|
|
32617
|
-
await runPostToolUse();
|
|
32618
|
-
process.exit(
|
|
33598
|
+
await runPostToolUse(void 0, profile);
|
|
33599
|
+
process.exit(hookExitCode(profile));
|
|
32619
33600
|
return;
|
|
32620
33601
|
}
|
|
32621
33602
|
if (sub === "stop") {
|
|
32622
|
-
await runStop();
|
|
32623
|
-
process.exit(
|
|
33603
|
+
await runStop(void 0, void 0, void 0, profile);
|
|
33604
|
+
process.exit(hookExitCode(profile));
|
|
32624
33605
|
return;
|
|
32625
33606
|
}
|
|
32626
33607
|
if (sub === "subagent-stop") {
|
|
32627
|
-
await runSubagentStop();
|
|
32628
|
-
process.exit(
|
|
33608
|
+
await runSubagentStop(profile);
|
|
33609
|
+
process.exit(hookExitCode(profile));
|
|
32629
33610
|
return;
|
|
32630
33611
|
}
|
|
32631
33612
|
if (sub === "pre-tool-use") {
|
|
32632
33613
|
let reply = "";
|
|
32633
33614
|
try {
|
|
32634
|
-
reply = runPreToolUse({ input: await readStdin2() });
|
|
33615
|
+
reply = runPreToolUse({ input: await readStdin2(), profile });
|
|
32635
33616
|
} catch {
|
|
32636
33617
|
reply = "";
|
|
32637
33618
|
}
|
|
@@ -32641,7 +33622,7 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32641
33622
|
if (sub === "user-prompt-submit") {
|
|
32642
33623
|
let reply = "";
|
|
32643
33624
|
try {
|
|
32644
|
-
reply = await runUserPromptSubmit({ input: await readStdin2() });
|
|
33625
|
+
reply = promptReplyFor(await runUserPromptSubmit({ input: await readStdin2(), agent: profile.id }), profile.id);
|
|
32645
33626
|
} catch {
|
|
32646
33627
|
reply = "";
|
|
32647
33628
|
}
|
|
@@ -32664,17 +33645,20 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32664
33645
|
process.exit(1);
|
|
32665
33646
|
return;
|
|
32666
33647
|
}
|
|
32667
|
-
|
|
33648
|
+
unknownCommand(cmd ?? "");
|
|
33649
|
+
}
|
|
33650
|
+
function unknownCommand(name = args.join(" ")) {
|
|
33651
|
+
process.stderr.write(`halfcycle-runner: unknown command "${name}"
|
|
32668
33652
|
Usage: halfcycle-runner ci
|
|
32669
|
-
halfcycle-runner hook post-tool-use
|
|
32670
|
-
halfcycle-runner hook pre-tool-use
|
|
32671
|
-
halfcycle-runner hook stop
|
|
32672
|
-
halfcycle-runner hook subagent-stop
|
|
32673
|
-
halfcycle-runner hook user-prompt-submit
|
|
32674
|
-
halfcycle-runner hook user-prompt-findings
|
|
32675
|
-
halfcycle-runner refresh-credential
|
|
33653
|
+
halfcycle-runner hook post-tool-use [--agent <id>]
|
|
33654
|
+
halfcycle-runner hook pre-tool-use [--agent <id>]
|
|
33655
|
+
halfcycle-runner hook stop [--agent <id>]
|
|
33656
|
+
halfcycle-runner hook subagent-stop [--agent <id>]
|
|
33657
|
+
halfcycle-runner hook user-prompt-submit [--agent <id>]
|
|
33658
|
+
halfcycle-runner hook user-prompt-findings [--agent <id>]
|
|
33659
|
+
halfcycle-runner refresh-credential [--agent <id>]
|
|
32676
33660
|
halfcycle-runner check-connection
|
|
32677
|
-
halfcycle-runner mcp-headers
|
|
33661
|
+
halfcycle-runner mcp-headers [--url <address>]
|
|
32678
33662
|
halfcycle-runner observe
|
|
32679
33663
|
halfcycle-runner observe --change <sortingId>
|
|
32680
33664
|
halfcycle-runner time
|
|
@@ -32688,16 +33672,20 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32688
33672
|
process.exit(1);
|
|
32689
33673
|
}
|
|
32690
33674
|
function readStdin3() {
|
|
32691
|
-
return new Promise((
|
|
33675
|
+
return new Promise((resolve10, reject) => {
|
|
32692
33676
|
const chunks = [];
|
|
32693
33677
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
32694
|
-
process.stdin.on("end", () =>
|
|
33678
|
+
process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString("utf-8")));
|
|
32695
33679
|
process.stdin.on("error", reject);
|
|
32696
33680
|
});
|
|
32697
33681
|
}
|
|
32698
33682
|
main().catch((err) => {
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
|
|
32702
|
-
|
|
33683
|
+
let reply = "";
|
|
33684
|
+
const code = answerUnexpectedError(args, err, {
|
|
33685
|
+
stdout: (text) => {
|
|
33686
|
+
reply += text;
|
|
33687
|
+
},
|
|
33688
|
+
stderr: (text) => process.stderr.write(text)
|
|
33689
|
+
});
|
|
33690
|
+
process.stdout.write(reply, () => process.exit(code));
|
|
32703
33691
|
});
|