halfcycle 0.4.18 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +6 -3
- package/bin/bin.bundle.mjs +1558 -630
- 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 +1439 -105
- package/dist/cli.js.map +3 -3
- package/dist/close-phase.d.ts.map +1 -1
- 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 +1077 -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 +1 -1
- 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",
|
|
@@ -18134,7 +18240,7 @@ function approvalSentence(phase, offerTyping) {
|
|
|
18134
18240
|
return TYPED_APPROVAL_OFF_SENTENCE;
|
|
18135
18241
|
if (offerTyping === "update")
|
|
18136
18242
|
return CLIENT_UPDATE_SENTENCE;
|
|
18137
|
-
return `In their own
|
|
18243
|
+
return `In their own coding agent's session, they ask to see the plan, check each part, and then type: approve ${phase}.`;
|
|
18138
18244
|
}
|
|
18139
18245
|
function phaseWords(phase) {
|
|
18140
18246
|
return `phase "${phase}"`;
|
|
@@ -18743,6 +18849,7 @@ var savedSchema = external_exports.object({
|
|
|
18743
18849
|
branches: external_exports.array(savedBranchSchema)
|
|
18744
18850
|
// every saved line of work
|
|
18745
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";
|
|
18746
18853
|
var AUTONOMY_A4 = 'Builds in this project now run on their own. Turn it off with "autonomy off", or in the console.';
|
|
18747
18854
|
var AUTONOMY_A5 = "Builds in this project wait for you again.";
|
|
18748
18855
|
function withoutFinalStop(text) {
|
|
@@ -18770,6 +18877,28 @@ var AUTONOMY_A18 = "Halfcycle cannot check PowerShell commands while builds run
|
|
|
18770
18877
|
|
|
18771
18878
|
// dist/hook-protocol.js
|
|
18772
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
|
|
18773
18902
|
function emitBlock(reason, systemMessage) {
|
|
18774
18903
|
const body = systemMessage === void 0 ? { decision: "block", reason } : { decision: "block", reason, systemMessage };
|
|
18775
18904
|
process.stdout.write(JSON.stringify(body) + "\n");
|
|
@@ -18783,12 +18912,8 @@ function preToolUseReplyText(answer, text = "", agentLine) {
|
|
|
18783
18912
|
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: text }
|
|
18784
18913
|
});
|
|
18785
18914
|
}
|
|
18786
|
-
const
|
|
18787
|
-
|
|
18788
|
-
return JSON.stringify({
|
|
18789
|
-
systemMessage: text,
|
|
18790
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: forAgent }
|
|
18791
|
-
});
|
|
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 });
|
|
18792
18917
|
}
|
|
18793
18918
|
function preToolUseNoticeText(text) {
|
|
18794
18919
|
return JSON.stringify({ systemMessage: text });
|
|
@@ -18808,13 +18933,26 @@ function nonBlockingReply(event, text) {
|
|
|
18808
18933
|
}
|
|
18809
18934
|
return JSON.stringify({ systemMessage: text });
|
|
18810
18935
|
}
|
|
18811
|
-
function hookReply(event) {
|
|
18936
|
+
function hookReply(event, profile = AGENT_PROFILES["claude-code"]) {
|
|
18812
18937
|
const queued = [];
|
|
18938
|
+
const forPerson = [];
|
|
18813
18939
|
const forAgent = [];
|
|
18940
|
+
const jsonOnly = repliesInJsonOnly(profile);
|
|
18814
18941
|
return {
|
|
18815
18942
|
notice(text) {
|
|
18816
18943
|
queued.push(text);
|
|
18817
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
|
+
},
|
|
18818
18956
|
agentContext(text) {
|
|
18819
18957
|
forAgent.push(text);
|
|
18820
18958
|
},
|
|
@@ -18824,11 +18962,23 @@ function hookReply(event) {
|
|
|
18824
18962
|
},
|
|
18825
18963
|
flush() {
|
|
18826
18964
|
const text = queued.splice(0).join("\n");
|
|
18965
|
+
const person = forPerson.splice(0).join("\n");
|
|
18827
18966
|
const context = event === "PostToolUse" ? forAgent.splice(0).join("\n") : "";
|
|
18828
18967
|
const code = process.exitCode;
|
|
18829
18968
|
if (code !== void 0 && code !== null && Number(code) !== 0) {
|
|
18830
|
-
if (text
|
|
18969
|
+
if (text === "")
|
|
18970
|
+
return;
|
|
18971
|
+
if (jsonOnly)
|
|
18972
|
+
process.stdout.write(`${jsonOnlyLoudReply(event, text)}
|
|
18973
|
+
`);
|
|
18974
|
+
else
|
|
18831
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}`)}
|
|
18832
18982
|
`);
|
|
18833
18983
|
return;
|
|
18834
18984
|
}
|
|
@@ -18844,6 +18994,60 @@ function hookReply(event) {
|
|
|
18844
18994
|
}
|
|
18845
18995
|
};
|
|
18846
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
|
+
};
|
|
18847
19051
|
function postToolUseReply(notice, context) {
|
|
18848
19052
|
const additionalContext = notice === "" ? context : `${notice}
|
|
18849
19053
|
${context}`;
|
|
@@ -18855,6 +19059,9 @@ function userPromptContextReply(context) {
|
|
|
18855
19059
|
function infraWarningText() {
|
|
18856
19060
|
return "[Halfcycle] WARNING: Halfcycle's check service could not be reached, so nothing was checked this time. The session continues without checks.";
|
|
18857
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
|
+
}
|
|
18858
19065
|
function localFaultText(message) {
|
|
18859
19066
|
return `[Halfcycle] WARNING: Local fault \u2014 ${message}. The guard service was not contacted. Session continues (fail-open).`;
|
|
18860
19067
|
}
|
|
@@ -18867,8 +19074,13 @@ This is not a service outage: the guard service answered. The suspect is this re
|
|
|
18867
19074
|
}
|
|
18868
19075
|
var UNCONFIGURED_EXIT_TOOL_USE = 2;
|
|
18869
19076
|
var UNCONFIGURED_EXIT_STOP_FAMILY = 1;
|
|
18870
|
-
function emitNeverConfigured(exitCode, reason, engagementId) {
|
|
18871
|
-
|
|
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);
|
|
18872
19084
|
process.exitCode = exitCode;
|
|
18873
19085
|
}
|
|
18874
19086
|
function namesTheSignInCommand(text) {
|
|
@@ -18947,8 +19159,13 @@ This is not a service outage. The guard service is up and replying; ${cause}, so
|
|
|
18947
19159
|
${remedy}
|
|
18948
19160
|
`;
|
|
18949
19161
|
}
|
|
18950
|
-
function emitCredentialRejected(statusCode, serviceMessage, exitCode, remedy = HOOK_REMEDY) {
|
|
18951
|
-
|
|
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);
|
|
18952
19169
|
process.exitCode = exitCode;
|
|
18953
19170
|
}
|
|
18954
19171
|
function credentialRenewalFailedMessage(remedy, label = "[Halfcycle]") {
|
|
@@ -21168,6 +21385,18 @@ function findingsPath(sessionId) {
|
|
|
21168
21385
|
return null;
|
|
21169
21386
|
return join6(markerDir(), `${FINDINGS_PREFIX}${sessionId}${FINDINGS_SUFFIX}`);
|
|
21170
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
|
+
}
|
|
21171
21400
|
function markerDir() {
|
|
21172
21401
|
const raw = process.env["TMPDIR"] || tmpdir();
|
|
21173
21402
|
return raw.replace(/\/+$/, "");
|
|
@@ -21324,7 +21553,9 @@ function sweepSessionFiles(keepSessionId, now) {
|
|
|
21324
21553
|
}
|
|
21325
21554
|
const kinds = [
|
|
21326
21555
|
[FINDINGS_PREFIX, FINDINGS_SUFFIX],
|
|
21327
|
-
[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]
|
|
21328
21559
|
];
|
|
21329
21560
|
for (const name of names) {
|
|
21330
21561
|
for (const [prefix, suffix] of kinds) {
|
|
@@ -21428,9 +21659,9 @@ function removeLivenessIndexEntry(dir, sessionId, name) {
|
|
|
21428
21659
|
import { existsSync as existsSync5, readFileSync as readFileSync8, realpathSync as realpathSync2 } from "node:fs";
|
|
21429
21660
|
import { homedir as homedir3 } from "node:os";
|
|
21430
21661
|
import { join as join7, relative as relative3, resolve as resolve2 } from "node:path";
|
|
21431
|
-
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
21662
|
+
function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration, profile = AGENT_PROFILES["claude-code"]) {
|
|
21432
21663
|
try {
|
|
21433
|
-
const facts = read(repoRoot);
|
|
21664
|
+
const facts = read(repoRoot, profile);
|
|
21434
21665
|
if (facts === void 0)
|
|
21435
21666
|
return void 0;
|
|
21436
21667
|
const parsed = seatConfigurationSchema.safeParse(facts);
|
|
@@ -21439,8 +21670,8 @@ function gatherSeatConfiguration(repoRoot, read = readSeatConfiguration) {
|
|
|
21439
21670
|
return void 0;
|
|
21440
21671
|
}
|
|
21441
21672
|
}
|
|
21442
|
-
function readSeatConfiguration(repoRoot) {
|
|
21443
|
-
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());
|
|
21444
21675
|
const projectDir = realpathSync2.native(given);
|
|
21445
21676
|
const home = homedir3();
|
|
21446
21677
|
let realHome = home;
|
|
@@ -21452,14 +21683,15 @@ function readSeatConfiguration(repoRoot) {
|
|
|
21452
21683
|
if (projectDir === home || given === home || projectDir === realHome)
|
|
21453
21684
|
return void 0;
|
|
21454
21685
|
return {
|
|
21455
|
-
settingsFiles:
|
|
21456
|
-
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
|
|
21457
21689
|
};
|
|
21458
21690
|
}
|
|
21459
21691
|
function isPlainObject3(value) {
|
|
21460
21692
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21461
21693
|
}
|
|
21462
|
-
function readSettingsFile(projectDir, rel) {
|
|
21694
|
+
function readSettingsFile(projectDir, rel, ours) {
|
|
21463
21695
|
let raw;
|
|
21464
21696
|
try {
|
|
21465
21697
|
raw = readFileSync8(join7(projectDir, rel), "utf-8");
|
|
@@ -21476,9 +21708,9 @@ function readSettingsFile(projectDir, rel) {
|
|
|
21476
21708
|
}
|
|
21477
21709
|
if (!isPlainObject3(parsed))
|
|
21478
21710
|
return { path: rel, state: "unreadable", hooks: [] };
|
|
21479
|
-
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"]) };
|
|
21711
|
+
return { path: rel, state: "read", hooks: ourHooks(parsed["hooks"], ours) };
|
|
21480
21712
|
}
|
|
21481
|
-
function ourHooks(hooks) {
|
|
21713
|
+
function ourHooks(hooks, ours) {
|
|
21482
21714
|
if (!isPlainObject3(hooks))
|
|
21483
21715
|
return [];
|
|
21484
21716
|
const rows = [];
|
|
@@ -21502,7 +21734,7 @@ function ourHooks(hooks) {
|
|
|
21502
21734
|
if (!isPlainObject3(hook))
|
|
21503
21735
|
continue;
|
|
21504
21736
|
const command = hook["command"];
|
|
21505
|
-
if (typeof command !== "string" || !
|
|
21737
|
+
if (typeof command !== "string" || !ours.has(command))
|
|
21506
21738
|
continue;
|
|
21507
21739
|
let timeout = null;
|
|
21508
21740
|
if (Object.hasOwn(hook, "timeout")) {
|
|
@@ -21521,8 +21753,8 @@ function ourHooks(hooks) {
|
|
|
21521
21753
|
}
|
|
21522
21754
|
return rows;
|
|
21523
21755
|
}
|
|
21524
|
-
function readPathStates(projectDir, repoRoot) {
|
|
21525
|
-
const unknown2 = () =>
|
|
21756
|
+
function readPathStates(projectDir, repoRoot, wiringPaths) {
|
|
21757
|
+
const unknown2 = () => wiringPaths.map((path) => ({ path, state: "unknown" }));
|
|
21526
21758
|
let projectTop = projectDir;
|
|
21527
21759
|
if (repoRoot !== projectDir) {
|
|
21528
21760
|
const top = getRepoRoot(projectDir);
|
|
@@ -21531,10 +21763,10 @@ function readPathStates(projectDir, repoRoot) {
|
|
|
21531
21763
|
projectTop = top.value;
|
|
21532
21764
|
}
|
|
21533
21765
|
const prefix = relative3(projectTop, projectDir);
|
|
21534
|
-
const status = getWiringStatus(
|
|
21766
|
+
const status = getWiringStatus(wiringPaths, projectDir);
|
|
21535
21767
|
if (!status.ok)
|
|
21536
21768
|
return unknown2();
|
|
21537
|
-
const mapped = mapWiringStatus(status.value, prefix,
|
|
21769
|
+
const mapped = mapWiringStatus(status.value, prefix, wiringPaths, (rel) => existsSync5(join7(projectDir, rel)));
|
|
21538
21770
|
return mapped ?? unknown2();
|
|
21539
21771
|
}
|
|
21540
21772
|
function mapWiringStatus(output, prefix, paths, onDisk) {
|
|
@@ -22253,8 +22485,8 @@ function separatorOf(path) {
|
|
|
22253
22485
|
function subagentsFolderOf(transcript) {
|
|
22254
22486
|
if (!transcript.endsWith(".jsonl") || transcript.length === ".jsonl".length)
|
|
22255
22487
|
return null;
|
|
22256
|
-
const
|
|
22257
|
-
return `${transcript.slice(0, -".jsonl".length)}${
|
|
22488
|
+
const sep6 = separatorOf(transcript);
|
|
22489
|
+
return `${transcript.slice(0, -".jsonl".length)}${sep6}subagents`;
|
|
22258
22490
|
}
|
|
22259
22491
|
function subagentRecordName(agentId) {
|
|
22260
22492
|
return /^[A-Za-z0-9_-]+$/.test(agentId) ? `agent-${agentId}.jsonl` : null;
|
|
@@ -22794,7 +23026,7 @@ async function pruneTimeLog(path, openedAt) {
|
|
|
22794
23026
|
written = true;
|
|
22795
23027
|
let seen = Buffer.byteLength(text, "utf-8");
|
|
22796
23028
|
for (; ; ) {
|
|
22797
|
-
await new Promise((
|
|
23029
|
+
await new Promise((resolve10) => setTimeout(resolve10, PRUNE_SETTLE_MS));
|
|
22798
23030
|
const after = readFileSync11(aside);
|
|
22799
23031
|
if (after.length <= seen)
|
|
22800
23032
|
break;
|
|
@@ -23429,344 +23661,109 @@ function turnEndMessage(news) {
|
|
|
23429
23661
|
return parts.length === 0 ? null : parts.join("\n");
|
|
23430
23662
|
}
|
|
23431
23663
|
|
|
23432
|
-
// dist/
|
|
23433
|
-
var
|
|
23434
|
-
|
|
23435
|
-
|
|
23436
|
-
|
|
23437
|
-
|
|
23438
|
-
|
|
23439
|
-
|
|
23440
|
-
|
|
23441
|
-
|
|
23442
|
-
|
|
23443
|
-
|
|
23444
|
-
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
const
|
|
23448
|
-
|
|
23449
|
-
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
let input;
|
|
23454
|
-
try {
|
|
23455
|
-
const raw = await readStdin();
|
|
23456
|
-
input = JSON.parse(raw);
|
|
23457
|
-
} catch {
|
|
23458
|
-
recordTimeEvent("edit", {});
|
|
23459
|
-
return;
|
|
23460
|
-
}
|
|
23461
|
-
recordTimeEvent("edit", input);
|
|
23462
|
-
const toolName = input.tool_name ?? "";
|
|
23463
|
-
if (!HANDLED_EDIT_TOOLS.has(toolName))
|
|
23464
|
-
return;
|
|
23465
|
-
const isNotebook = toolName === "NotebookEdit";
|
|
23466
|
-
const absFilePath = isNotebook ? input.tool_input?.notebook_path : input.tool_input?.file_path;
|
|
23467
|
-
if (!absFilePath || typeof absFilePath !== "string")
|
|
23468
|
-
return;
|
|
23469
|
-
recordOwnEdit(input, absFilePath);
|
|
23470
|
-
const repoRootResult = getRepoRoot(process.cwd());
|
|
23471
|
-
if (!repoRootResult.ok) {
|
|
23472
|
-
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
23473
|
-
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;
|
|
23474
23685
|
}
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23479
|
-
|
|
23480
|
-
|
|
23481
|
-
|
|
23482
|
-
|
|
23483
|
-
|
|
23484
|
-
|
|
23485
|
-
|
|
23486
|
-
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet });
|
|
23493
|
-
if (!firstNoticeThisSession("credential-missing", repoRoot, input.session_id))
|
|
23494
|
-
return;
|
|
23495
|
-
emitNeverConfigured(UNCONFIGURED_EXIT_TOOL_USE, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
23496
|
-
return;
|
|
23497
|
-
}
|
|
23498
|
-
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
23499
|
-
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
23500
|
-
const evaluation = await evaluateWithRefresh({
|
|
23501
|
-
guardServiceUrl,
|
|
23502
|
-
token: guardServiceToken,
|
|
23503
|
-
engagementId: guardEngagementId,
|
|
23504
|
-
request: buildEvaluationRequest(guardEngagementId, changeSet, context, seatConfiguration),
|
|
23505
|
-
warn: warnOnceThisSession("credential-renewed-for-this-run", repoRoot, input.session_id)
|
|
23506
|
-
});
|
|
23507
|
-
const clientResult = evaluation.result;
|
|
23508
|
-
if (!clientResult.ok) {
|
|
23509
|
-
const failure = classifyFailure(clientResult);
|
|
23510
|
-
await emitRunRecord({
|
|
23511
|
-
engagementId: guardEngagementId,
|
|
23512
|
-
runType: "hook",
|
|
23513
|
-
outcome: outcomeForClientFailure(clientResult.kind),
|
|
23514
|
-
failureReason: failure.reason,
|
|
23515
|
-
changeSet,
|
|
23516
|
-
credential: evaluation.token,
|
|
23517
|
-
credentialRefused: failure.kind === "credential"
|
|
23518
|
-
});
|
|
23519
|
-
if (failure.kind === "credential") {
|
|
23520
|
-
if (!firstNoticeThisSession("credential-rejected", repoRoot, input.session_id))
|
|
23521
|
-
return;
|
|
23522
|
-
emitCredentialRejected(
|
|
23523
|
-
failure.statusCode,
|
|
23524
|
-
failure.message,
|
|
23525
|
-
CREDENTIAL_REJECTED_EXIT_TOOL_USE,
|
|
23526
|
-
// The remedy the renewal attempt earned: the sign-in when the account
|
|
23527
|
-
// credential is the thing that was refused, and otherwise the plane's own
|
|
23528
|
-
// sentence about why it would not mint.
|
|
23529
|
-
evaluation.remedy
|
|
23530
|
-
);
|
|
23531
|
-
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;
|
|
23532
23703
|
}
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
|
|
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;
|
|
23536
23713
|
}
|
|
23537
|
-
|
|
23538
|
-
|
|
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;
|
|
23539
23733
|
}
|
|
23540
|
-
|
|
23541
|
-
const run = buildGuardEvalRun({
|
|
23542
|
-
engagementId: guardEngagementId,
|
|
23543
|
-
runType: "hook",
|
|
23544
|
-
// The phase this run belongs to, as the client named it — it names the
|
|
23545
|
-
// guard-eval log file the run is appended to. Absent → the run is unphased.
|
|
23546
|
-
phase: telemetryConfig.phase,
|
|
23547
|
-
outcome: "evaluated",
|
|
23548
|
-
changeSet,
|
|
23549
|
-
result: clientResult.envelope
|
|
23550
|
-
});
|
|
23551
|
-
await emitTelemetry(run, {
|
|
23552
|
-
controlTelemetryUrl: telemetryConfig.controlTelemetryUrl,
|
|
23553
|
-
// THE CREDENTIAL IN FORCE, NOT THE ONE IN THE ENVIRONMENT (T-08,
|
|
23554
|
-
// credential-lifecycle). `readTelemetryConfig` reads the bearer off
|
|
23555
|
-
// `process.env` after the evaluation, and a renewal does not write there — so
|
|
23556
|
-
// taking its value on a run that just recovered would present the credential
|
|
23557
|
-
// the plane had already refused and drop the record with a 401, silently,
|
|
23558
|
-
// because telemetry is best-effort.
|
|
23559
|
-
controlTelemetryToken: evaluation.token,
|
|
23560
|
-
// Per-engagement log dir from HALFCYCLE_GUARD_EVAL_LOG_DIR (E1-T-02, #61).
|
|
23561
|
-
guardEvalLogDir: telemetryConfig.guardEvalLogDir
|
|
23562
|
-
}, {
|
|
23563
|
-
warn: (msg) => process.stderr.write(msg + "\n")
|
|
23564
|
-
});
|
|
23565
|
-
actOnEnvelope2(clientResult.envelope, reply);
|
|
23734
|
+
return ops;
|
|
23566
23735
|
}
|
|
23567
|
-
function
|
|
23568
|
-
|
|
23569
|
-
const notRunBlock = notRun ? `
|
|
23570
|
-
${notRun}` : "";
|
|
23571
|
-
if (envelope.guardsFired.length === 0) {
|
|
23572
|
-
if (notRun)
|
|
23573
|
-
reply.notice(notRun);
|
|
23574
|
-
return;
|
|
23575
|
-
}
|
|
23576
|
-
if (envelope.blocking) {
|
|
23577
|
-
const personNotRun = notRunMessage((envelope.notRun ?? []).map((n) => n.reason));
|
|
23578
|
-
const reason = `${blockReason(envelope.guardsFired.map((g) => g.explanation))}${personNotRun ? `
|
|
23579
|
-
${personNotRun}` : ""}`;
|
|
23580
|
-
reply.block(reason, stoppedMessage(blockingExplanations(envelope.guardsFired)));
|
|
23581
|
-
} else {
|
|
23582
|
-
const guardLines = envelope.guardsFired.map((g) => ` [${g.severity.toUpperCase()}] ${g.patternRef}: ${g.explanation}`).join("\n");
|
|
23583
|
-
reply.notice(`[Halfcycle] Guard warning: ${envelope.explanation}
|
|
23584
|
-
${guardLines}${notRunBlock}`);
|
|
23585
|
-
}
|
|
23736
|
+
function isUpdateLine(line2) {
|
|
23737
|
+
return line2 === "" || line2.startsWith(" ") || line2.startsWith("+") || line2.startsWith("-") || line2.startsWith("@@");
|
|
23586
23738
|
}
|
|
23587
|
-
function
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
|
|
23591
|
-
process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
|
|
23592
|
-
process.stdin.on("error", reject);
|
|
23593
|
-
});
|
|
23739
|
+
function headerPath(trimmed, header) {
|
|
23740
|
+
if (trimmed === header.trimEnd())
|
|
23741
|
+
return "";
|
|
23742
|
+
return trimmed.startsWith(header) ? trimmed.slice(header.length).trim() : void 0;
|
|
23594
23743
|
}
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
import { join as join11 } from "node:path";
|
|
23599
|
-
var LIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
23600
|
-
function field(input, key) {
|
|
23601
|
-
if (typeof input !== "object" || input === null)
|
|
23744
|
+
function patchHeaderPaths(patch) {
|
|
23745
|
+
const ops = patchOperations(patch);
|
|
23746
|
+
if (ops === null)
|
|
23602
23747
|
return null;
|
|
23603
|
-
|
|
23604
|
-
return typeof v === "string" && v !== "" ? v : null;
|
|
23748
|
+
return ops.map((op) => op.kind === "update" && op.movedTo !== null ? op.movedTo : op.path);
|
|
23605
23749
|
}
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
23613
|
-
|
|
23614
|
-
|
|
23615
|
-
|
|
23616
|
-
|
|
23617
|
-
|
|
23618
|
-
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
}
|
|
23623
|
-
closeSync2(openSync2(join11(tmp, name), "a", 384));
|
|
23624
|
-
renameSync5(tmp, folder);
|
|
23625
|
-
return true;
|
|
23626
|
-
} catch {
|
|
23627
|
-
if (tmp !== null)
|
|
23628
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
23629
|
-
}
|
|
23630
|
-
}
|
|
23631
|
-
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;
|
|
23632
23766
|
}
|
|
23633
|
-
function recordLiveness(input, deps = {}) {
|
|
23634
|
-
try {
|
|
23635
|
-
const sessionId = field(input, "session_id");
|
|
23636
|
-
const agentId = field(input, "agent_id");
|
|
23637
|
-
const cwd = field(input, "cwd");
|
|
23638
|
-
if (sessionId === null || cwd === null || !livenessIdOk(sessionId))
|
|
23639
|
-
return null;
|
|
23640
|
-
if (agentId !== null && !livenessIdOk(agentId))
|
|
23641
|
-
return null;
|
|
23642
|
-
if (/[\r\n]/.test(cwd))
|
|
23643
|
-
return null;
|
|
23644
|
-
const copy = whichCopy(cwd, deps.walk);
|
|
23645
|
-
const dir = deps.dir ?? markerDir();
|
|
23646
|
-
const name = livenessFileName(livenessKey(copy?.common ?? null), sessionId, agentId);
|
|
23647
|
-
if (!indexEntry(dir, sessionId, name))
|
|
23648
|
-
return null;
|
|
23649
|
-
const path = join11(dir, name);
|
|
23650
|
-
const text = `${cwd}
|
|
23651
|
-
${copy?.top ?? ""}
|
|
23652
|
-
`;
|
|
23653
|
-
try {
|
|
23654
|
-
writeFileSync7(path, text, { encoding: "utf-8", flag: "wx", mode: 384 });
|
|
23655
|
-
return { firstSeen: true };
|
|
23656
|
-
} catch (err) {
|
|
23657
|
-
if (err.code !== "EEXIST")
|
|
23658
|
-
return null;
|
|
23659
|
-
}
|
|
23660
|
-
writeFileSync7(path, text, { encoding: "utf-8", mode: 384 });
|
|
23661
|
-
return { firstSeen: false };
|
|
23662
|
-
} catch {
|
|
23663
|
-
return null;
|
|
23664
|
-
}
|
|
23665
|
-
}
|
|
23666
|
-
function clearLiveness(input, deps = {}) {
|
|
23667
|
-
try {
|
|
23668
|
-
const sessionId = field(input, "session_id");
|
|
23669
|
-
const agentId = field(input, "agent_id");
|
|
23670
|
-
if (sessionId === null || agentId === null)
|
|
23671
|
-
return 0;
|
|
23672
|
-
const dir = deps.dir ?? markerDir();
|
|
23673
|
-
let removed = 0;
|
|
23674
|
-
for (const name of readdirSync5(dir)) {
|
|
23675
|
-
const p = parseLivenessFileName(name);
|
|
23676
|
-
if (p === null || p.sessionId !== sessionId || p.agentId !== agentId)
|
|
23677
|
-
continue;
|
|
23678
|
-
try {
|
|
23679
|
-
unlinkSync6(join11(dir, name));
|
|
23680
|
-
removed += 1;
|
|
23681
|
-
} catch {
|
|
23682
|
-
}
|
|
23683
|
-
removeLivenessIndexEntry(dir, sessionId, name);
|
|
23684
|
-
}
|
|
23685
|
-
return removed;
|
|
23686
|
-
} catch {
|
|
23687
|
-
return 0;
|
|
23688
|
-
}
|
|
23689
|
-
}
|
|
23690
|
-
function topOf(path) {
|
|
23691
|
-
try {
|
|
23692
|
-
const top = readFileSync14(path, "utf-8").split("\n")[1] ?? "";
|
|
23693
|
-
return top === "" ? null : top;
|
|
23694
|
-
} catch {
|
|
23695
|
-
return null;
|
|
23696
|
-
}
|
|
23697
|
-
}
|
|
23698
|
-
function readLiveness(sessionId, cwd, deps = {}) {
|
|
23699
|
-
try {
|
|
23700
|
-
if (!sessionId || !cwd || !livenessIdOk(sessionId))
|
|
23701
|
-
return null;
|
|
23702
|
-
const copy = whichCopy(cwd, deps.walk);
|
|
23703
|
-
if (copy === null)
|
|
23704
|
-
return null;
|
|
23705
|
-
const key = livenessKey(copy.common);
|
|
23706
|
-
const dir = deps.dir ?? markerDir();
|
|
23707
|
-
const now = (deps.now ?? Date.now)();
|
|
23708
|
-
let main2 = null;
|
|
23709
|
-
const agents = [];
|
|
23710
|
-
for (const name of readdirSync5(dir)) {
|
|
23711
|
-
const p = parseLivenessFileName(name);
|
|
23712
|
-
if (p === null || p.key !== key || p.sessionId !== sessionId)
|
|
23713
|
-
continue;
|
|
23714
|
-
const full = join11(dir, name);
|
|
23715
|
-
let seenAt;
|
|
23716
|
-
try {
|
|
23717
|
-
seenAt = statSync4(full).mtimeMs;
|
|
23718
|
-
} catch {
|
|
23719
|
-
continue;
|
|
23720
|
-
}
|
|
23721
|
-
const top = topOf(full);
|
|
23722
|
-
if (top === null)
|
|
23723
|
-
continue;
|
|
23724
|
-
if (p.agentId === null)
|
|
23725
|
-
main2 = { top, seenAt };
|
|
23726
|
-
else if (now - seenAt <= LIVE_WINDOW_MS)
|
|
23727
|
-
agents.push({ agentId: p.agentId, top, seenAt });
|
|
23728
|
-
}
|
|
23729
|
-
agents.sort((a, b) => a.agentId.localeCompare(b.agentId));
|
|
23730
|
-
return { main: main2, agents };
|
|
23731
|
-
} catch {
|
|
23732
|
-
return null;
|
|
23733
|
-
}
|
|
23734
|
-
}
|
|
23735
|
-
function liveCount(record2) {
|
|
23736
|
-
return record2 === null ? 0 : record2.agents.length;
|
|
23737
|
-
}
|
|
23738
|
-
function withoutAgent(record2, agentId) {
|
|
23739
|
-
if (record2 === null || agentId === null || agentId === void 0 || agentId === "")
|
|
23740
|
-
return record2;
|
|
23741
|
-
return { main: record2.main, agents: record2.agents.filter((a) => a.agentId !== agentId) };
|
|
23742
|
-
}
|
|
23743
|
-
function runsBesideOtherWork(toolInput, record2) {
|
|
23744
|
-
const t = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
|
|
23745
|
-
const bg = t["run_in_background"];
|
|
23746
|
-
if (bg !== false && bg !== "false")
|
|
23747
|
-
return true;
|
|
23748
|
-
const name = t["name"];
|
|
23749
|
-
if (name !== void 0 && name !== null && name !== "")
|
|
23750
|
-
return true;
|
|
23751
|
-
return liveCount(record2) > 0;
|
|
23752
|
-
}
|
|
23753
|
-
|
|
23754
|
-
// dist/pace-hook.js
|
|
23755
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, renameSync as renameSync9, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
23756
|
-
import nodePath4 from "node:path";
|
|
23757
|
-
|
|
23758
|
-
// dist/pace-verb.js
|
|
23759
|
-
import { mkdirSync as mkdirSync6, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync8, statSync as statSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
23760
|
-
import { homedir as homedir6 } from "node:os";
|
|
23761
|
-
import nodePath3 from "node:path";
|
|
23762
|
-
|
|
23763
|
-
// dist/pacing.js
|
|
23764
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
23765
|
-
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";
|
|
23766
|
-
import { homedir as homedir5 } from "node:os";
|
|
23767
|
-
import nodePath from "node:path";
|
|
23768
|
-
var DEFAULT_CONFIG_FOLDER_NAME = ".claude";
|
|
23769
|
-
var RECORDS_FOLDER_NAME = "projects";
|
|
23770
23767
|
function normaliseFolder(folder, input = {}) {
|
|
23771
23768
|
const p = input.path ?? nodePath;
|
|
23772
23769
|
const home = input.home ?? homedir5();
|
|
@@ -23780,6 +23777,12 @@ function normaliseFolder(folder, input = {}) {
|
|
|
23780
23777
|
function configFolderKey(input = {}) {
|
|
23781
23778
|
const p = input.path ?? nodePath;
|
|
23782
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
|
+
}
|
|
23783
23786
|
const fromEnv = env["CLAUDE_CONFIG_DIR"];
|
|
23784
23787
|
if (typeof fromEnv === "string" && fromEnv !== "")
|
|
23785
23788
|
return normaliseFolder(fromEnv, input);
|
|
@@ -23811,7 +23814,7 @@ function pacingDir(home) {
|
|
|
23811
23814
|
}
|
|
23812
23815
|
function readJsonObject(path) {
|
|
23813
23816
|
try {
|
|
23814
|
-
const v = JSON.parse(
|
|
23817
|
+
const v = JSON.parse(readFileSync14(path, "utf-8"));
|
|
23815
23818
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
|
|
23816
23819
|
} catch {
|
|
23817
23820
|
return null;
|
|
@@ -23820,8 +23823,8 @@ function readJsonObject(path) {
|
|
|
23820
23823
|
function writeJsonWhole(path, value) {
|
|
23821
23824
|
mkdirSync4(nodePath.dirname(path), { recursive: true, mode: 448 });
|
|
23822
23825
|
const tmp = `${path}.${String(process.pid)}.${createHash4("sha1").update(String(Math.random())).digest("hex").slice(0, 8)}.tmp`;
|
|
23823
|
-
|
|
23824
|
-
|
|
23826
|
+
writeFileSync7(tmp, JSON.stringify(value), { mode: 384 });
|
|
23827
|
+
renameSync5(tmp, path);
|
|
23825
23828
|
}
|
|
23826
23829
|
function writeAccountEntry(path, key, entry) {
|
|
23827
23830
|
const current = readJsonObject(path);
|
|
@@ -23978,7 +23981,7 @@ function readLearnedAccount(key, deps = {}) {
|
|
|
23978
23981
|
}
|
|
23979
23982
|
function dirents(path) {
|
|
23980
23983
|
try {
|
|
23981
|
-
return
|
|
23984
|
+
return readdirSync5(path, { withFileTypes: true });
|
|
23982
23985
|
} catch {
|
|
23983
23986
|
return [];
|
|
23984
23987
|
}
|
|
@@ -24012,7 +24015,7 @@ var NEWLINE = 10;
|
|
|
24012
24015
|
var ASSISTANT = Buffer.from('"assistant"');
|
|
24013
24016
|
var READ_CHUNK = 8 * 1024 * 1024;
|
|
24014
24017
|
function readWholeLines(path, from, size, onLine) {
|
|
24015
|
-
const fd =
|
|
24018
|
+
const fd = openSync2(path, "r");
|
|
24016
24019
|
try {
|
|
24017
24020
|
let pos = from;
|
|
24018
24021
|
let carry = Buffer.alloc(0);
|
|
@@ -24040,7 +24043,7 @@ function readWholeLines(path, from, size, onLine) {
|
|
|
24040
24043
|
}
|
|
24041
24044
|
return consumed;
|
|
24042
24045
|
} finally {
|
|
24043
|
-
|
|
24046
|
+
closeSync2(fd);
|
|
24044
24047
|
}
|
|
24045
24048
|
}
|
|
24046
24049
|
function hashId(id) {
|
|
@@ -24069,7 +24072,7 @@ function scanRecords(key, deps = {}) {
|
|
|
24069
24072
|
let size;
|
|
24070
24073
|
let mtimeSec;
|
|
24071
24074
|
try {
|
|
24072
|
-
const st =
|
|
24075
|
+
const st = statSync4(file2.path);
|
|
24073
24076
|
size = st.size;
|
|
24074
24077
|
mtimeSec = Math.floor(st.mtimeMs / 1e3);
|
|
24075
24078
|
} catch {
|
|
@@ -24205,7 +24208,7 @@ function timeLogTranscripts(home) {
|
|
|
24205
24208
|
continue;
|
|
24206
24209
|
let text;
|
|
24207
24210
|
try {
|
|
24208
|
-
text =
|
|
24211
|
+
text = readFileSync14(nodePath.join(engagements, e.name, TIME_LOG_DIR_NAME, TIME_LOG_FILENAME), "utf-8");
|
|
24209
24212
|
} catch {
|
|
24210
24213
|
continue;
|
|
24211
24214
|
}
|
|
@@ -24350,19 +24353,405 @@ function paceOf(input) {
|
|
|
24350
24353
|
} else {
|
|
24351
24354
|
continue;
|
|
24352
24355
|
}
|
|
24353
|
-
if (PACE_RANK[d.pace] > PACE_RANK[best.pace] || d.pace === best.pace && d.allowance < best.allowance)
|
|
24354
|
-
best = d;
|
|
24356
|
+
if (PACE_RANK[d.pace] > PACE_RANK[best.pace] || d.pace === best.pace && d.allowance < best.allowance)
|
|
24357
|
+
best = d;
|
|
24358
|
+
}
|
|
24359
|
+
return best;
|
|
24360
|
+
}
|
|
24361
|
+
function knownWindowOf(learned, reported, now) {
|
|
24362
|
+
for (const kind of PACE_WINDOW_KINDS) {
|
|
24363
|
+
if (learned?.ceilings[kind] !== void 0 || reportedFresh(reported[kind], now))
|
|
24364
|
+
return kind;
|
|
24365
|
+
}
|
|
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;
|
|
24355
24725
|
}
|
|
24356
|
-
return best;
|
|
24357
24726
|
}
|
|
24358
|
-
function
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
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;
|
|
24364
24744
|
}
|
|
24365
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
|
+
|
|
24366
24755
|
// dist/scan-lock.js
|
|
24367
24756
|
import { spawn } from "node:child_process";
|
|
24368
24757
|
import { linkSync, mkdirSync as mkdirSync5, readFileSync as readFileSync16, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
@@ -24961,7 +25350,7 @@ async function paceAtTurnEnd(input, reply, deps = {}) {
|
|
|
24961
25350
|
return;
|
|
24962
25351
|
const nowMs = (deps.now ?? Date.now)();
|
|
24963
25352
|
const now = new Date(nowMs);
|
|
24964
|
-
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 });
|
|
24965
25354
|
const stored = readPaceStateFile(dir);
|
|
24966
25355
|
const before = stored.accounts[key] ?? null;
|
|
24967
25356
|
if (before !== null) {
|
|
@@ -25065,7 +25454,7 @@ function whereOf(input, deps) {
|
|
|
25065
25454
|
const dir = enginePacingDir(configured.config.guardEngagementId, deps.home);
|
|
25066
25455
|
if (dir === null)
|
|
25067
25456
|
return null;
|
|
25068
|
-
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 }) };
|
|
25069
25458
|
}
|
|
25070
25459
|
function retirePendingStart(input, deps = {}) {
|
|
25071
25460
|
try {
|
|
@@ -25139,7 +25528,7 @@ function paceHoldText(input, toolInput, deps = {}) {
|
|
|
25139
25528
|
// dist/risky/facts.js
|
|
25140
25529
|
import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync as readdirSync8, readFileSync as readFileSync19, realpathSync as realpathSync6, statSync as statSync7 } from "node:fs";
|
|
25141
25530
|
import { homedir as homedir7, tmpdir as tmpdir2 } from "node:os";
|
|
25142
|
-
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";
|
|
25143
25532
|
|
|
25144
25533
|
// dist/risky/shell.js
|
|
25145
25534
|
var MAX_DEPTH = 8;
|
|
@@ -27349,13 +27738,13 @@ var Env = class {
|
|
|
27349
27738
|
description;
|
|
27350
27739
|
projectCommonDir;
|
|
27351
27740
|
constructor(ctx) {
|
|
27352
|
-
this.projectDir = realOrSelf(
|
|
27353
|
-
this.cwd = realOrSelf(
|
|
27354
|
-
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()));
|
|
27355
27744
|
this.timeoutMs = ctx.gitTimeoutMs ?? FACTS_GIT_TIMEOUT_MS;
|
|
27356
|
-
this.tempDir = realOrSelf(
|
|
27745
|
+
this.tempDir = realOrSelf(resolve5(ctx.tempDir ?? tmpdir2()));
|
|
27357
27746
|
const roots = ctx.tempRoots ?? [this.tempDir, tmpdir2(), ...TEMP_ROOTS];
|
|
27358
|
-
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));
|
|
27359
27748
|
}
|
|
27360
27749
|
isTemp(abs) {
|
|
27361
27750
|
return this.tempRoots.some((r) => isInside(abs, r));
|
|
@@ -27365,7 +27754,7 @@ var Env = class {
|
|
|
27365
27754
|
}
|
|
27366
27755
|
display(abs) {
|
|
27367
27756
|
if (isInside(abs, this.projectDir)) {
|
|
27368
|
-
const rel =
|
|
27757
|
+
const rel = relative6(this.projectDir, abs);
|
|
27369
27758
|
return rel === "" ? "." : toSlash(rel);
|
|
27370
27759
|
}
|
|
27371
27760
|
return toSlash(abs);
|
|
@@ -27375,7 +27764,7 @@ var Env = class {
|
|
|
27375
27764
|
return this.description;
|
|
27376
27765
|
this.description = null;
|
|
27377
27766
|
try {
|
|
27378
|
-
const raw = JSON.parse(readFileSync19(
|
|
27767
|
+
const raw = JSON.parse(readFileSync19(join13(repoRoot, SYSTEM_DESCRIPTION_REL), "utf-8"));
|
|
27379
27768
|
const checked = checkSystemDescription(raw);
|
|
27380
27769
|
if (checked.ok)
|
|
27381
27770
|
this.description = checked.description;
|
|
@@ -27418,7 +27807,7 @@ var Env = class {
|
|
|
27418
27807
|
if (!r.ok)
|
|
27419
27808
|
return null;
|
|
27420
27809
|
const d = r.value.trim();
|
|
27421
|
-
return d === "" ? null : realOrSelf(
|
|
27810
|
+
return d === "" ? null : realOrSelf(resolve5(dir, d));
|
|
27422
27811
|
}
|
|
27423
27812
|
};
|
|
27424
27813
|
function realOrSelf(p) {
|
|
@@ -27429,29 +27818,29 @@ function realOrSelf(p) {
|
|
|
27429
27818
|
}
|
|
27430
27819
|
}
|
|
27431
27820
|
function toSlash(p) {
|
|
27432
|
-
return
|
|
27821
|
+
return sep4 === "/" ? p : p.split(sep4).join("/");
|
|
27433
27822
|
}
|
|
27434
27823
|
function isInside(child, parent) {
|
|
27435
|
-
const rel =
|
|
27436
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
27824
|
+
const rel = relative6(parent, child);
|
|
27825
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
27437
27826
|
}
|
|
27438
27827
|
function resolveHome(text, home, base) {
|
|
27439
27828
|
if (text === "~")
|
|
27440
27829
|
return home;
|
|
27441
27830
|
if (text.startsWith("~/"))
|
|
27442
|
-
return
|
|
27443
|
-
return
|
|
27831
|
+
return join13(home, text.slice(2));
|
|
27832
|
+
return resolve5(base, text);
|
|
27444
27833
|
}
|
|
27445
27834
|
function realTarget(abs) {
|
|
27446
27835
|
const parent = dirname6(abs);
|
|
27447
27836
|
if (parent === abs)
|
|
27448
27837
|
return abs;
|
|
27449
|
-
return
|
|
27838
|
+
return join13(realOrSelf(parent), pathBasename(abs));
|
|
27450
27839
|
}
|
|
27451
27840
|
function wordPath(word, base, env) {
|
|
27452
27841
|
if (word.temp !== void 0) {
|
|
27453
|
-
const root = word.temp.base === "mktemp" ?
|
|
27454
|
-
return
|
|
27842
|
+
const root = word.temp.base === "mktemp" ? join13(env.tempDir, ".halfcycle-mktemp-new") : env.tempDir;
|
|
27843
|
+
return resolve5(`${root}${word.temp.suffix}`);
|
|
27455
27844
|
}
|
|
27456
27845
|
return word.literal ? resolveHome(word.text, env.home, base) : null;
|
|
27457
27846
|
}
|
|
@@ -27460,7 +27849,7 @@ function follow(start, prefix, dirs, m, env) {
|
|
|
27460
27849
|
let fresh = start.fresh;
|
|
27461
27850
|
let freshRoot = start.freshRoot;
|
|
27462
27851
|
const seen = [...prefix];
|
|
27463
|
-
const insideFresh = (p) => freshRoot !== void 0 && isInside(
|
|
27852
|
+
const insideFresh = (p) => freshRoot !== void 0 && isInside(resolve5(p), resolve5(freshRoot));
|
|
27464
27853
|
const mark = (by) => {
|
|
27465
27854
|
if (fresh !== void 0 && insideFresh(cwd))
|
|
27466
27855
|
return;
|
|
@@ -27589,14 +27978,14 @@ function expandBraces(text) {
|
|
|
27589
27978
|
return body.split(",").map((b) => `${head}${b}${tail}`);
|
|
27590
27979
|
}
|
|
27591
27980
|
function globPath(abs) {
|
|
27592
|
-
const root = abs.startsWith(
|
|
27593
|
-
const segments = abs.split(
|
|
27981
|
+
const root = abs.startsWith(sep4) ? sep4 : "";
|
|
27982
|
+
const segments = abs.split(sep4).filter((s) => s !== "");
|
|
27594
27983
|
let current = [root === "" ? "." : root];
|
|
27595
27984
|
for (const segment of segments) {
|
|
27596
27985
|
const next = [];
|
|
27597
27986
|
if (!/[*?[]/.test(segment)) {
|
|
27598
27987
|
for (const c of current)
|
|
27599
|
-
next.push(
|
|
27988
|
+
next.push(join13(c, segment));
|
|
27600
27989
|
} else {
|
|
27601
27990
|
const re = segmentPattern(segment);
|
|
27602
27991
|
for (const c of current) {
|
|
@@ -27610,7 +27999,7 @@ function globPath(abs) {
|
|
|
27610
27999
|
if (n.startsWith(".") && !segment.startsWith("."))
|
|
27611
28000
|
continue;
|
|
27612
28001
|
if (re.test(n))
|
|
27613
|
-
next.push(
|
|
28002
|
+
next.push(join13(c, n));
|
|
27614
28003
|
}
|
|
27615
28004
|
}
|
|
27616
28005
|
}
|
|
@@ -27675,7 +28064,7 @@ function isDirectory(p) {
|
|
|
27675
28064
|
}
|
|
27676
28065
|
function filesUnder(targets, repoRoot, env, keep, seen) {
|
|
27677
28066
|
const specs = targets.map((t) => {
|
|
27678
|
-
const rel = toSlash(
|
|
28067
|
+
const rel = toSlash(relative6(repoRoot, t));
|
|
27679
28068
|
return rel === "" ? "." : rel;
|
|
27680
28069
|
});
|
|
27681
28070
|
const tracked = env.git(["--literal-pathspecs", "ls-files", "--full-name", "-z", "--", ...specs], repoRoot);
|
|
@@ -27738,12 +28127,12 @@ function findKeeps(filter, roots, repoRoot) {
|
|
|
27738
28127
|
const names = filter.names.map((n) => findPattern(n.pattern, n.fold, false));
|
|
27739
28128
|
const excluded = filter.excluded.map((p) => findPattern(p, false, true));
|
|
27740
28129
|
const steps = (rel) => {
|
|
27741
|
-
const abs =
|
|
28130
|
+
const abs = join13(repoRoot, rel);
|
|
27742
28131
|
const out = [];
|
|
27743
28132
|
for (const r of roots) {
|
|
27744
28133
|
if (!isInside(abs, r.abs))
|
|
27745
28134
|
continue;
|
|
27746
|
-
const below = toSlash(
|
|
28135
|
+
const below = toSlash(relative6(r.abs, abs)).split("/").filter((x) => x !== "");
|
|
27747
28136
|
const rootName = r.text === null ? pathBasename(r.abs) : r.text.replace(/\/+$/, "").split("/").pop() || r.text;
|
|
27748
28137
|
const prefixes = r.text === null ? [] : [.../* @__PURE__ */ new Set([r.text, r.text.replace(/\/+$/, "") || r.text])];
|
|
27749
28138
|
const chain = [{ name: rootName, printed: r.text === null ? [] : [r.text] }];
|
|
@@ -27810,9 +28199,9 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
27810
28199
|
const out = [];
|
|
27811
28200
|
const stack = [];
|
|
27812
28201
|
for (const f of folders) {
|
|
27813
|
-
if (isDirectory(
|
|
28202
|
+
if (isDirectory(join13(repoRoot, f)))
|
|
27814
28203
|
stack.push(f);
|
|
27815
|
-
else if (pathExists(
|
|
28204
|
+
else if (pathExists(join13(repoRoot, f)) && isCredentialPath(f))
|
|
27816
28205
|
out.push(f);
|
|
27817
28206
|
}
|
|
27818
28207
|
let seen = 0;
|
|
@@ -27820,7 +28209,7 @@ function credentialFilesIn(folders, repoRoot, deadline, skip) {
|
|
|
27820
28209
|
const rel = stack.pop();
|
|
27821
28210
|
let entries;
|
|
27822
28211
|
try {
|
|
27823
|
-
entries = readdirSync8(
|
|
28212
|
+
entries = readdirSync8(join13(repoRoot, rel), { withFileTypes: true });
|
|
27824
28213
|
} catch {
|
|
27825
28214
|
continue;
|
|
27826
28215
|
}
|
|
@@ -27917,7 +28306,7 @@ function deleteFilesFacts(m, env) {
|
|
|
27917
28306
|
for (const raw of expanded) {
|
|
27918
28307
|
const abs = realTarget(raw);
|
|
27919
28308
|
written.set(abs, word.literal ? word.text : null);
|
|
27920
|
-
if (abs ===
|
|
28309
|
+
if (abs === sep4 || abs === env.home || abs !== env.projectDir && isInside(env.projectDir, abs)) {
|
|
27921
28310
|
guarded.push(abs);
|
|
27922
28311
|
} else if (isInside(abs, env.projectDir) || !env.isTemp(abs) && env.sameRepository(abs)) {
|
|
27923
28312
|
if (pathExists(abs))
|
|
@@ -27961,7 +28350,7 @@ function deleteFilesFacts(m, env) {
|
|
|
27961
28350
|
...base.pre.length === 0 ? {
|
|
27962
28351
|
save: {
|
|
27963
28352
|
repoRoot,
|
|
27964
|
-
paths: targets.map((t) => toSlash(
|
|
28353
|
+
paths: targets.map((t) => toSlash(relative6(repoRoot, t)) || "."),
|
|
27965
28354
|
...keep === void 0 ? {} : { keep: (rel) => keep.deletes(rel) },
|
|
27966
28355
|
lines: []
|
|
27967
28356
|
}
|
|
@@ -27973,12 +28362,12 @@ function closeFoldsAll(targets, env) {
|
|
|
27973
28362
|
const root = recordRoot(t, env);
|
|
27974
28363
|
if (root === null || !isInside(t, root))
|
|
27975
28364
|
return false;
|
|
27976
|
-
const rel = toSlash(
|
|
28365
|
+
const rel = toSlash(relative6(root, t));
|
|
27977
28366
|
if (rel === "" || recordPatternFor(rel)?.deleteAsked !== false)
|
|
27978
28367
|
return false;
|
|
27979
28368
|
let at = root;
|
|
27980
28369
|
for (const seg of rel.split("/")) {
|
|
27981
|
-
at =
|
|
28370
|
+
at = join13(at, seg);
|
|
27982
28371
|
try {
|
|
27983
28372
|
if (lstatSync2(at).isSymbolicLink())
|
|
27984
28373
|
return false;
|
|
@@ -28167,11 +28556,11 @@ function cleanFiles(m, where, env) {
|
|
|
28167
28556
|
const repoRoot = env.repoRoot(where.cwd);
|
|
28168
28557
|
if (repoRoot === null)
|
|
28169
28558
|
return null;
|
|
28170
|
-
const abs = named.map((n) =>
|
|
28559
|
+
const abs = named.map((n) => resolve5(where.cwd, n));
|
|
28171
28560
|
const files = filesUnder(abs, repoRoot, env);
|
|
28172
28561
|
if (files === null)
|
|
28173
28562
|
return null;
|
|
28174
|
-
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)) || ".") };
|
|
28175
28564
|
}
|
|
28176
28565
|
function stashFiles(m, where, env) {
|
|
28177
28566
|
let refs;
|
|
@@ -28561,7 +28950,7 @@ function publishFacts(m, env) {
|
|
|
28561
28950
|
const start = startFolder(m, env);
|
|
28562
28951
|
if (start !== null && PACKAGE_JSON_PROGRAMS.has(m.program)) {
|
|
28563
28952
|
try {
|
|
28564
|
-
const pkg = JSON.parse(readFileSync19(
|
|
28953
|
+
const pkg = JSON.parse(readFileSync19(join13(start, "package.json"), "utf-8"));
|
|
28565
28954
|
if (typeof pkg["name"] === "string" && typeof pkg["version"] === "string" && pkg["name"] !== "" && pkg["version"] !== "") {
|
|
28566
28955
|
name = pkg["name"];
|
|
28567
28956
|
version2 = pkg["version"];
|
|
@@ -28674,7 +29063,7 @@ function recordFileAt(abs, env) {
|
|
|
28674
29063
|
const root = recordRoot(abs, env);
|
|
28675
29064
|
if (root === null || !isInside(abs, root))
|
|
28676
29065
|
return null;
|
|
28677
|
-
const rel = toSlash(
|
|
29066
|
+
const rel = toSlash(relative6(root, abs));
|
|
28678
29067
|
if (rel === "")
|
|
28679
29068
|
return null;
|
|
28680
29069
|
const row = recordPatternFor(rel);
|
|
@@ -28684,11 +29073,11 @@ function recordFilesUnder(folder, env) {
|
|
|
28684
29073
|
const root = recordRoot(folder, env);
|
|
28685
29074
|
if (root === null)
|
|
28686
29075
|
return [];
|
|
28687
|
-
const relFolder = toSlash(
|
|
29076
|
+
const relFolder = toSlash(relative6(root, folder));
|
|
28688
29077
|
const starts = /* @__PURE__ */ new Set();
|
|
28689
29078
|
for (const { pattern } of PROJECT_RECORD_PATTERNS) {
|
|
28690
29079
|
if (relFolder === "" || patternOwns(relFolder, pattern))
|
|
28691
|
-
starts.add(
|
|
29080
|
+
starts.add(join13(root, pattern));
|
|
28692
29081
|
else if (patternOwns(pattern, relFolder))
|
|
28693
29082
|
starts.add(folder);
|
|
28694
29083
|
}
|
|
@@ -28714,7 +29103,7 @@ function recordFilesUnder(folder, env) {
|
|
|
28714
29103
|
continue;
|
|
28715
29104
|
}
|
|
28716
29105
|
for (const n of names)
|
|
28717
|
-
stack.push(
|
|
29106
|
+
stack.push(join13(p, n));
|
|
28718
29107
|
} else if (st.isFile()) {
|
|
28719
29108
|
const r = recordFileAt(p, env);
|
|
28720
29109
|
if (r !== null)
|
|
@@ -28735,7 +29124,7 @@ function recordHistory(files, env) {
|
|
|
28735
29124
|
byTree.set(top, list);
|
|
28736
29125
|
}
|
|
28737
29126
|
for (const [top, list] of byTree) {
|
|
28738
|
-
const specs = list.map((abs) => toSlash(
|
|
29127
|
+
const specs = list.map((abs) => toSlash(relative6(top, abs)));
|
|
28739
29128
|
const tracked = trackedOf(specs, top, env);
|
|
28740
29129
|
if (tracked === null)
|
|
28741
29130
|
continue;
|
|
@@ -28792,8 +29181,14 @@ function parseNumstatZ(text) {
|
|
|
28792
29181
|
// dist/risky/sentence.js
|
|
28793
29182
|
var REASON_MAX_CHARS = 200;
|
|
28794
29183
|
var MAX_LISTED = 3;
|
|
28795
|
-
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.`;
|
|
28796
29190
|
var NOTHING_LOST_ON_NO = "Nothing is lost if you say no.";
|
|
29191
|
+
var IF_YES = "If you say yes, ";
|
|
28797
29192
|
var DESCRIBED_TOOLS = /* @__PURE__ */ new Set(["Bash", "Agent"]);
|
|
28798
29193
|
function reasonOf(call) {
|
|
28799
29194
|
if (!DESCRIBED_TOOLS.has(call.toolName))
|
|
@@ -28822,10 +29217,43 @@ function questionFor(facts, description) {
|
|
|
28822
29217
|
return couldNotTellQuestion(description);
|
|
28823
29218
|
if (staked.length === 1) {
|
|
28824
29219
|
const only = staked[0];
|
|
28825
|
-
return
|
|
29220
|
+
return `${PAUSED_OPENER}${stepOf(whatOf(only))}. ${reason} ${NOTHING_LOST_ON_NO} ${IF_YES}${lostOf(only)}`;
|
|
28826
29221
|
}
|
|
28827
|
-
const shown2 = staked.slice(0, MAX_LISTED).map((f) => `${whatOf(f)}
|
|
28828
|
-
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)}`;
|
|
28829
29257
|
}
|
|
28830
29258
|
function orderedStaked(facts) {
|
|
28831
29259
|
const said = /* @__PURE__ */ new Set();
|
|
@@ -28840,7 +29268,7 @@ function orderedStaked(facts) {
|
|
|
28840
29268
|
function moreLine(more) {
|
|
28841
29269
|
return more > 0 ? ` And ${more} more risky ${more === 1 ? "step" : "steps"}.` : "";
|
|
28842
29270
|
}
|
|
28843
|
-
var UNNAMED_PUSH_WHAT = "Replace a
|
|
29271
|
+
var UNNAMED_PUSH_WHAT = "Replace a remote branch.";
|
|
28844
29272
|
function whatsText(facts, extraPush = false) {
|
|
28845
29273
|
const staked = orderedStaked(facts);
|
|
28846
29274
|
const items = staked.map((f) => ({ order: riskyTypeOrder(f.type), what: whatOf(f) }));
|
|
@@ -28859,11 +29287,12 @@ function typeSentenceFor(facts, extraPush = false) {
|
|
|
28859
29287
|
function savedWhatFor(facts) {
|
|
28860
29288
|
return whatsText(facts);
|
|
28861
29289
|
}
|
|
28862
|
-
|
|
28863
|
-
return `${question} ${TURN_QUESTIONS_ON_LINE}`;
|
|
28864
|
-
}
|
|
29290
|
+
var COULD_NOT_TELL_STEP = "one that can delete, overwrite, publish or show something that cannot be taken back";
|
|
28865
29291
|
function couldNotTellQuestion(description) {
|
|
28866
|
-
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");
|
|
28867
29296
|
}
|
|
28868
29297
|
function whatOf(f) {
|
|
28869
29298
|
switch (f.type) {
|
|
@@ -28892,23 +29321,45 @@ function lostOf(f) {
|
|
|
28892
29321
|
case "delete-files":
|
|
28893
29322
|
return deleteLost(f);
|
|
28894
29323
|
case "discard-unsaved":
|
|
28895
|
-
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.`;
|
|
28896
29325
|
case "rewrite-shared-history":
|
|
28897
29326
|
return pushLost(f);
|
|
28898
29327
|
case "delete-unmerged-work":
|
|
28899
29328
|
return unmergedLost(f);
|
|
28900
29329
|
case "wipe-data":
|
|
28901
|
-
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.";
|
|
28902
29331
|
case "read-credentials":
|
|
28903
|
-
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.";
|
|
28904
29333
|
case "publish":
|
|
28905
|
-
return "
|
|
29334
|
+
return "anyone can install it straight away, and a published version cannot be taken back.";
|
|
28906
29335
|
case "deploy":
|
|
28907
29336
|
return "what is live now is replaced at once; Halfcycle cannot tell whether going back is possible.";
|
|
28908
29337
|
case "shrink-record":
|
|
28909
29338
|
return shrinkLost(f);
|
|
28910
29339
|
}
|
|
28911
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
|
+
}
|
|
28912
29363
|
function deleteWhat(f) {
|
|
28913
29364
|
const thing = thingText(f.thing) ?? listNames(f.match.targets.map((t) => t.text));
|
|
28914
29365
|
if (f.files === null)
|
|
@@ -28917,24 +29368,39 @@ function deleteWhat(f) {
|
|
|
28917
29368
|
}
|
|
28918
29369
|
function deleteLost(f) {
|
|
28919
29370
|
if (f.files === null || f.neverSaved === null) {
|
|
28920
|
-
return "
|
|
29371
|
+
return "any file it deletes that was never committed could not be brought back; Halfcycle cannot tell which files those are.";
|
|
29372
|
+
}
|
|
29373
|
+
const n = f.files;
|
|
29374
|
+
const k = f.neverSaved;
|
|
29375
|
+
if (k === 0)
|
|
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.";
|
|
29377
|
+
if (k === n) {
|
|
29378
|
+
if (n === 1)
|
|
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.`;
|
|
29381
|
+
}
|
|
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";
|
|
28921
29387
|
}
|
|
28922
29388
|
const n = f.files;
|
|
28923
29389
|
const k = f.neverSaved;
|
|
28924
29390
|
if (k === 0)
|
|
28925
|
-
return
|
|
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`;
|
|
28926
29392
|
if (k === n) {
|
|
28927
29393
|
if (n === 1)
|
|
28928
|
-
return "that file was never
|
|
28929
|
-
return `${n === 2 ? "both" : `all ${n}`} of those files were never
|
|
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`;
|
|
28930
29396
|
}
|
|
28931
|
-
return `${k} of those files ${k === 1 ? "was" : "were"} never
|
|
29397
|
+
return `${k} of those files ${k === 1 ? "was" : "were"} never committed, so ${k === 1 ? "it" : "they"} could not be brought back`;
|
|
28932
29398
|
}
|
|
28933
29399
|
function discardWhat(f) {
|
|
28934
29400
|
if (f.files === null)
|
|
28935
|
-
return "Throw away
|
|
29401
|
+
return "Throw away uncommitted changes in this project.";
|
|
28936
29402
|
const thing = thingText(f.thing) ?? "this project";
|
|
28937
|
-
return `Throw away the
|
|
29403
|
+
return `Throw away the uncommitted changes to ${thing} \u2014 ${f.files} ${f.files === 1 ? "file" : "files"}.`;
|
|
28938
29404
|
}
|
|
28939
29405
|
function pushLines(f) {
|
|
28940
29406
|
const lines2 = f.lines.filter((l) => l.dropped !== 0);
|
|
@@ -28945,19 +29411,30 @@ function pushWhat(f) {
|
|
|
28945
29411
|
const verb = lines2.length > 0 && lines2.every((l) => l.deletes) ? "Delete" : "Replace";
|
|
28946
29412
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
28947
29413
|
if (named.length === 0)
|
|
28948
|
-
return verb === "Replace" ? UNNAMED_PUSH_WHAT : `${verb} a
|
|
28949
|
-
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)}`;
|
|
28950
29422
|
}
|
|
28951
29423
|
function pushLost(f) {
|
|
28952
29424
|
const lines2 = pushLines(f);
|
|
28953
|
-
const
|
|
28954
|
-
if (
|
|
28955
|
-
|
|
28956
|
-
return `whatever is on it now might no longer be there, because Halfcycle cannot tell what is on ${where}.`;
|
|
28957
|
-
}
|
|
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}.`;
|
|
28958
29428
|
const n = lines2.reduce((s, l) => s + (l.dropped ?? 0), 0);
|
|
28959
29429
|
const them = n === 1 ? "it" : "them";
|
|
28960
|
-
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"}`;
|
|
28961
29438
|
}
|
|
28962
29439
|
function unmergedLines(f) {
|
|
28963
29440
|
const lines2 = f.lines.filter((l) => l.commits !== 0);
|
|
@@ -28967,24 +29444,34 @@ function unmergedWhat(f) {
|
|
|
28967
29444
|
const lines2 = unmergedLines(f);
|
|
28968
29445
|
if (f.match.form === "reset-to") {
|
|
28969
29446
|
const l = lines2[0];
|
|
28970
|
-
const which = l?.branch == null ? "
|
|
29447
|
+
const which = l?.branch == null ? "the current branch" : `the branch ${quote(l.branch)}`;
|
|
28971
29448
|
if (l?.commits == null)
|
|
28972
29449
|
return `Move ${which} back.`;
|
|
28973
|
-
return `Move ${which} back ${l.commits}
|
|
29450
|
+
return `Move ${which} back ${l.commits} ${l.commits === 1 ? "commit" : "commits"}.`;
|
|
28974
29451
|
}
|
|
28975
29452
|
const named = lines2.filter((l) => l.branch !== null).map((l) => l.branch);
|
|
28976
29453
|
if (named.length === 0)
|
|
28977
|
-
return "Delete a
|
|
28978
|
-
return `Delete the ${named.length === 1 ? "
|
|
29454
|
+
return "Delete a branch.";
|
|
29455
|
+
return `Delete the ${named.length === 1 ? "branch" : "branches"} ${listNames(named)}.`;
|
|
28979
29456
|
}
|
|
28980
|
-
function
|
|
29457
|
+
function unmergedCount(f) {
|
|
28981
29458
|
const lines2 = unmergedLines(f);
|
|
28982
|
-
|
|
28983
|
-
|
|
28984
|
-
|
|
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.";
|
|
28985
29467
|
}
|
|
28986
|
-
|
|
28987
|
-
|
|
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`;
|
|
28988
29475
|
}
|
|
28989
29476
|
function wipeWhat(f) {
|
|
28990
29477
|
const m = f.match;
|
|
@@ -29028,7 +29515,7 @@ function sqlTarget(statement) {
|
|
|
29028
29515
|
}
|
|
29029
29516
|
function credentialsWhat(f) {
|
|
29030
29517
|
if (f.match.form === "gh-token")
|
|
29031
|
-
return "Show the agent the sign-in token this computer
|
|
29518
|
+
return "Show the agent the GitHub sign-in token on this computer.";
|
|
29032
29519
|
if (f.match.form === "keychain")
|
|
29033
29520
|
return "Show the agent a password kept in this computer's keychain.";
|
|
29034
29521
|
const file2 = f.file ?? f.match.file;
|
|
@@ -29067,6 +29554,20 @@ function shrinkLost(f) {
|
|
|
29067
29554
|
return "Halfcycle cannot tell how much of it could be brought back from the project's history.";
|
|
29068
29555
|
}
|
|
29069
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
|
+
}
|
|
29070
29571
|
var NOTHING_WAS_CHANGED = "Nothing was changed.";
|
|
29071
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.';
|
|
29072
29573
|
function ownCopyRefusal(f, description) {
|
|
@@ -29127,24 +29628,188 @@ function listPlain(items) {
|
|
|
29127
29628
|
|
|
29128
29629
|
// dist/risky/check.js
|
|
29129
29630
|
function riskyAnswer(call, contextOf) {
|
|
29130
|
-
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));
|
|
29131
29633
|
if (matches.length === 0)
|
|
29132
29634
|
return { silent: true };
|
|
29133
|
-
const reason = reasonOf(
|
|
29635
|
+
const reason = calls.length === 0 ? null : reasonOf(calls[0]);
|
|
29134
29636
|
try {
|
|
29135
29637
|
const staked = factsFor(matches, contextOf()).filter((f) => f.atStake);
|
|
29136
29638
|
if (staked.length === 0)
|
|
29137
29639
|
return { silent: true };
|
|
29138
|
-
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);
|
|
29139
29795
|
} catch {
|
|
29140
|
-
return { silent: false, question: couldNotTellQuestion(reason), staked: [] };
|
|
29141
29796
|
}
|
|
29142
29797
|
}
|
|
29143
29798
|
|
|
29144
29799
|
// dist/risky/decide.js
|
|
29145
29800
|
var QUESTIONS_ON_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "auto"]);
|
|
29146
|
-
function answerModeFor(permissionMode) {
|
|
29147
|
-
|
|
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
|
+
}
|
|
29148
29813
|
}
|
|
29149
29814
|
var REFUSED_IN_EVERY_MODE = /* @__PURE__ */ new Set(["outside-own-copy"]);
|
|
29150
29815
|
function autonomousRowOf(type) {
|
|
@@ -29194,6 +29859,9 @@ function decide(action, facts, mode, mayPublishNow = () => false) {
|
|
|
29194
29859
|
}
|
|
29195
29860
|
}
|
|
29196
29861
|
}
|
|
29862
|
+
function allowableByTyping(action, facts) {
|
|
29863
|
+
return decide(action, facts, "questions-on") === "ask";
|
|
29864
|
+
}
|
|
29197
29865
|
function decideAutonomous(facts, mayPublishNow) {
|
|
29198
29866
|
if (facts.length === 0)
|
|
29199
29867
|
return { autonomous: "could-not-tell" };
|
|
@@ -29210,8 +29878,8 @@ function decideAutonomous(facts, mayPublishNow) {
|
|
|
29210
29878
|
}
|
|
29211
29879
|
|
|
29212
29880
|
// dist/risky/own-copy.js
|
|
29213
|
-
import { readFileSync as
|
|
29214
|
-
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";
|
|
29215
29883
|
var OWN_COPY_START_TOOL = "Agent";
|
|
29216
29884
|
var OWN_COPY_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
29217
29885
|
var OWN_COPY_ISOLATION = "worktree";
|
|
@@ -29232,7 +29900,7 @@ function stringField2(o, key) {
|
|
|
29232
29900
|
}
|
|
29233
29901
|
function readOr(deps, path) {
|
|
29234
29902
|
try {
|
|
29235
|
-
return (deps.readFile ?? ((p) =>
|
|
29903
|
+
return (deps.readFile ?? ((p) => readFileSync21(p, "utf-8")))(path);
|
|
29236
29904
|
} catch {
|
|
29237
29905
|
return null;
|
|
29238
29906
|
}
|
|
@@ -29272,7 +29940,7 @@ function typeCanChangeFiles(subagentType, deps) {
|
|
|
29272
29940
|
return true;
|
|
29273
29941
|
if (AGENT_FILE_NAME.test(type)) {
|
|
29274
29942
|
for (const base of [deps.projectDir, deps.home]) {
|
|
29275
|
-
const text = readOr(deps,
|
|
29943
|
+
const text = readOr(deps, join14(base, ".claude", "agents", `${type}.md`));
|
|
29276
29944
|
if (text === null)
|
|
29277
29945
|
continue;
|
|
29278
29946
|
const tools = frontmatterTools(text);
|
|
@@ -29378,9 +30046,9 @@ function judgeChange(input, call, deps) {
|
|
|
29378
30046
|
|
|
29379
30047
|
// dist/risky/save.js
|
|
29380
30048
|
import { randomBytes } from "node:crypto";
|
|
29381
|
-
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";
|
|
29382
30050
|
import { homedir as homedir8 } from "node:os";
|
|
29383
|
-
import { dirname as dirname7, isAbsolute as
|
|
30051
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join15, resolve as resolve6 } from "node:path";
|
|
29384
30052
|
var SAVE_LIMIT_BYTES = 2e8;
|
|
29385
30053
|
var SAVE_BUDGET_MS = 5e3;
|
|
29386
30054
|
function notSaved(reason) {
|
|
@@ -29389,7 +30057,7 @@ function notSaved(reason) {
|
|
|
29389
30057
|
function savedFolderOf(engagementId, home = homedir8()) {
|
|
29390
30058
|
if (!isEngagementId(engagementId))
|
|
29391
30059
|
return null;
|
|
29392
|
-
return
|
|
30060
|
+
return join15(home, HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, SAVED_DIR_NAME);
|
|
29393
30061
|
}
|
|
29394
30062
|
function saveFirst(staked, ctx) {
|
|
29395
30063
|
const now = ctx.now ?? Date.now;
|
|
@@ -29420,20 +30088,20 @@ function saveFirst(staked, ctx) {
|
|
|
29420
30088
|
const id = freshId(folder, ctx.newId);
|
|
29421
30089
|
if (id === null)
|
|
29422
30090
|
return notSaved(AUTONOMY_SAVE_FAILED_REASON);
|
|
29423
|
-
saveDir =
|
|
30091
|
+
saveDir = join15(folder, id);
|
|
29424
30092
|
const repository = repositoryOf(g);
|
|
29425
30093
|
if (repository === null)
|
|
29426
30094
|
return failed(saveDir);
|
|
29427
|
-
|
|
30095
|
+
writeFileSync13(join15(saveDir, SAVED_REPOSITORY_FILE_NAME), `${repository}
|
|
29428
30096
|
`, { mode: 384 });
|
|
29429
|
-
const privateIndex =
|
|
30097
|
+
const privateIndex = join15(saveDir, PRIVATE_INDEX_NAME);
|
|
29430
30098
|
const commit = saveCommit(id, headCommit2, found2.tracked, plan.lines, privateIndex, g, now());
|
|
29431
30099
|
rmSync2(privateIndex, { force: true });
|
|
29432
30100
|
if (commit === null)
|
|
29433
30101
|
return failed(saveDir);
|
|
29434
|
-
const copiesDir =
|
|
30102
|
+
const copiesDir = join15(saveDir, SAVED_COPIES_DIR_NAME);
|
|
29435
30103
|
for (const rel of found2.copies) {
|
|
29436
|
-
if (now() > deadline || !copyOne(
|
|
30104
|
+
if (now() > deadline || !copyOne(join15(plan.root, rel), join15(copiesDir, rel)))
|
|
29437
30105
|
return failed(saveDir);
|
|
29438
30106
|
}
|
|
29439
30107
|
const description = savedSchema.safeParse({
|
|
@@ -29449,10 +30117,10 @@ function saveFirst(staked, ctx) {
|
|
|
29449
30117
|
});
|
|
29450
30118
|
if (!description.success)
|
|
29451
30119
|
return failed(saveDir);
|
|
29452
|
-
const file2 =
|
|
29453
|
-
|
|
30120
|
+
const file2 = join15(saveDir, SAVED_FILE_NAME);
|
|
30121
|
+
writeFileSync13(`${file2}.part`, `${JSON.stringify(description.data, null, 2)}
|
|
29454
30122
|
`, { mode: 384 });
|
|
29455
|
-
|
|
30123
|
+
renameSync11(`${file2}.part`, file2);
|
|
29456
30124
|
ctx.beforeRef?.();
|
|
29457
30125
|
const ref = g.run(["update-ref", "--no-deref", savedRefOf(id), commit, ""]);
|
|
29458
30126
|
if (!ref.ok)
|
|
@@ -29480,7 +30148,7 @@ function planOf(staked) {
|
|
|
29480
30148
|
const t = f.save;
|
|
29481
30149
|
if (t === void 0)
|
|
29482
30150
|
return { ok: false, reason: f.cannotTell === void 0 ? AUTONOMY_SAVE_NOT_COVERED_REASON : AUTONOMY_COULD_NOT_TELL_REASON };
|
|
29483
|
-
const r = realOrSelf2(
|
|
30151
|
+
const r = realOrSelf2(resolve6(t.repoRoot));
|
|
29484
30152
|
if (root === null)
|
|
29485
30153
|
root = r;
|
|
29486
30154
|
else if (root !== r)
|
|
@@ -29499,7 +30167,7 @@ function planOf(staked) {
|
|
|
29499
30167
|
function isInsideRel(p) {
|
|
29500
30168
|
if (p === ".")
|
|
29501
30169
|
return true;
|
|
29502
|
-
if (p === "" ||
|
|
30170
|
+
if (p === "" || isAbsolute4(p) || p.includes("\\"))
|
|
29503
30171
|
return false;
|
|
29504
30172
|
return p.split("/").every((s) => s !== "" && s !== "." && s !== "..");
|
|
29505
30173
|
}
|
|
@@ -29529,7 +30197,7 @@ function filesOf(plan, g, deadline) {
|
|
|
29529
30197
|
trackedAll.add(path);
|
|
29530
30198
|
if (!reached(path, plan.specs))
|
|
29531
30199
|
continue;
|
|
29532
|
-
const st = lstatOrNull(
|
|
30200
|
+
const st = lstatOrNull(join15(plan.root, path));
|
|
29533
30201
|
if (st !== null && (st.isFile() || st.isSymbolicLink()))
|
|
29534
30202
|
tracked.add(path);
|
|
29535
30203
|
}
|
|
@@ -29541,7 +30209,7 @@ function filesOf(plan, g, deadline) {
|
|
|
29541
30209
|
const rel = stack.pop();
|
|
29542
30210
|
if (rel === ".git")
|
|
29543
30211
|
continue;
|
|
29544
|
-
const abs = rel === "." ? plan.root :
|
|
30212
|
+
const abs = rel === "." ? plan.root : join15(plan.root, rel);
|
|
29545
30213
|
const st = lstatOrNull(abs);
|
|
29546
30214
|
if (st === null)
|
|
29547
30215
|
continue;
|
|
@@ -29585,7 +30253,7 @@ function saveCommit(id, head, tracked, lines2, privateIndex, g, at) {
|
|
|
29585
30253
|
const indexPath = g.run(["rev-parse", "--git-path", "index"]);
|
|
29586
30254
|
if (!indexPath.ok)
|
|
29587
30255
|
return null;
|
|
29588
|
-
const projectIndex =
|
|
30256
|
+
const projectIndex = resolve6(g.root, indexPath.value.trim());
|
|
29589
30257
|
const env = commitEnv(privateIndex, at);
|
|
29590
30258
|
if (lstatOrNull(projectIndex) !== null)
|
|
29591
30259
|
copyFileSync(projectIndex, privateIndex);
|
|
@@ -29654,7 +30322,7 @@ function freshId(folder, newId) {
|
|
|
29654
30322
|
if (!savedIdSchema.safeParse(id).success)
|
|
29655
30323
|
return null;
|
|
29656
30324
|
try {
|
|
29657
|
-
mkdirSync8(
|
|
30325
|
+
mkdirSync8(join15(folder, id), { mode: 448 });
|
|
29658
30326
|
return id;
|
|
29659
30327
|
} catch (e) {
|
|
29660
30328
|
if (e.code !== "EEXIST")
|
|
@@ -29675,11 +30343,11 @@ function repositoryOf(g) {
|
|
|
29675
30343
|
if (!r.ok)
|
|
29676
30344
|
return null;
|
|
29677
30345
|
const dir = r.value.trim();
|
|
29678
|
-
return dir === "" ? null : realOrSelf2(
|
|
30346
|
+
return dir === "" ? null : realOrSelf2(resolve6(g.root, dir));
|
|
29679
30347
|
}
|
|
29680
30348
|
function markedRepository(dir) {
|
|
29681
30349
|
try {
|
|
29682
|
-
const text =
|
|
30350
|
+
const text = readFileSync22(join15(dir, SAVED_REPOSITORY_FILE_NAME), "utf-8").trim();
|
|
29683
30351
|
return text === "" ? null : text;
|
|
29684
30352
|
} catch {
|
|
29685
30353
|
return null;
|
|
@@ -29713,7 +30381,7 @@ function pruneSaves(root, engagementId, ctx = {}) {
|
|
|
29713
30381
|
if (!savedIdSchema.safeParse(id).success)
|
|
29714
30382
|
continue;
|
|
29715
30383
|
here.add(id);
|
|
29716
|
-
const dir =
|
|
30384
|
+
const dir = join15(folder, id);
|
|
29717
30385
|
const st = lstatOrNull(dir);
|
|
29718
30386
|
const answer = savedPruneAnswer({
|
|
29719
30387
|
refMadeAt: refs.get(id) ?? null,
|
|
@@ -29752,19 +30420,19 @@ function realOrSelf2(p) {
|
|
|
29752
30420
|
|
|
29753
30421
|
// dist/autonomy-state.js
|
|
29754
30422
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29755
|
-
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";
|
|
29756
30424
|
import { homedir as homedir9, platform as platform2 } from "node:os";
|
|
29757
|
-
import { dirname as dirname8, join as
|
|
30425
|
+
import { dirname as dirname8, join as join16 } from "node:path";
|
|
29758
30426
|
var AUTONOMY_STATE_FILENAME = "autonomy.json";
|
|
29759
30427
|
var AUTONOMY_STATE_DEADLINE_MS = 1500;
|
|
29760
30428
|
function autonomyStateFilePath(engagementId, home) {
|
|
29761
|
-
return
|
|
30429
|
+
return join16(home ?? homedir9(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, AUTONOMY_STATE_FILENAME);
|
|
29762
30430
|
}
|
|
29763
30431
|
function readCachedAutonomy(engagementId, nowMs, home) {
|
|
29764
30432
|
if (!isEngagementId(engagementId))
|
|
29765
30433
|
return null;
|
|
29766
30434
|
try {
|
|
29767
|
-
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(
|
|
30435
|
+
const parsed = autonomyStateFileSchema.safeParse(JSON.parse(readFileSync23(autonomyStateFilePath(engagementId, home), "utf-8")));
|
|
29768
30436
|
if (!parsed.success || !parsed.data.on)
|
|
29769
30437
|
return null;
|
|
29770
30438
|
const age = nowMs - Date.parse(parsed.data.readAt);
|
|
@@ -29810,14 +30478,14 @@ function deleteAutonomyStateFile(engagementId, home) {
|
|
|
29810
30478
|
}
|
|
29811
30479
|
function writeAutonomyStateFile(engagementId, file2, home) {
|
|
29812
30480
|
const target = autonomyStateFilePath(engagementId, home);
|
|
29813
|
-
const temp =
|
|
30481
|
+
const temp = join16(dirname8(target), `.${AUTONOMY_STATE_FILENAME}.${process.pid}.tmp`);
|
|
29814
30482
|
try {
|
|
29815
30483
|
mkdirSync9(dirname8(target), { recursive: true, mode: 448 });
|
|
29816
|
-
|
|
30484
|
+
writeFileSync14(temp, `${JSON.stringify(file2)}
|
|
29817
30485
|
`, { mode: 384 });
|
|
29818
30486
|
if (platform2() !== "win32")
|
|
29819
30487
|
chmodSync3(temp, 384);
|
|
29820
|
-
|
|
30488
|
+
renameSync12(temp, target);
|
|
29821
30489
|
return true;
|
|
29822
30490
|
} catch {
|
|
29823
30491
|
try {
|
|
@@ -29871,6 +30539,76 @@ async function cacheAutonomySetting(opts) {
|
|
|
29871
30539
|
}
|
|
29872
30540
|
}
|
|
29873
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
|
+
|
|
29874
30612
|
// dist/hooks/pre-tool-use.js
|
|
29875
30613
|
import { performance } from "node:perf_hooks";
|
|
29876
30614
|
import { homedir as homedir10 } from "node:os";
|
|
@@ -29882,13 +30620,15 @@ var OWN_COPY_UNKNOWN_NOTICE = "Halfcycle could not tell which copy of the projec
|
|
|
29882
30620
|
var CHECKED = new Set(CHECKED_TOOLS);
|
|
29883
30621
|
var RECORDED_ONLY = new Set(RECORDED_ONLY_TOOLS);
|
|
29884
30622
|
function runPreToolUse(deps) {
|
|
29885
|
-
const
|
|
30623
|
+
const parsed = parseObject(deps.input);
|
|
30624
|
+
const input = parsed === null ? null : hookInputForRule(parsed, profileOf(deps).configFolder);
|
|
29886
30625
|
const at = clockOf(deps)();
|
|
29887
30626
|
const stamped = { ...deps, now: () => at };
|
|
29888
30627
|
recordTimeEvent("action", input ?? {}, stamped);
|
|
29889
30628
|
recordOwnShell(input, at, { ...deps.liveness?.dir === void 0 ? {} : { dir: deps.liveness.dir } });
|
|
29890
30629
|
const seen = recordLiveness(input ?? {}, deps.liveness);
|
|
29891
30630
|
const paceDeps = {
|
|
30631
|
+
configFolder: profileOf(deps).configFolder,
|
|
29892
30632
|
...deps.pace ?? {},
|
|
29893
30633
|
...deps.env === void 0 ? {} : { env: deps.env },
|
|
29894
30634
|
...deps.home === void 0 ? {} : { home: deps.home },
|
|
@@ -29900,14 +30640,15 @@ function runPreToolUse(deps) {
|
|
|
29900
30640
|
const toolName = input === null ? void 0 : stringOf(input, "tool_name");
|
|
29901
30641
|
if (toolName !== void 0 && RECORDED_ONLY.has(toolName))
|
|
29902
30642
|
return recordedOnlyReply(mode);
|
|
29903
|
-
const
|
|
29904
|
-
if (
|
|
30643
|
+
const calls = input === null ? null : toolCallsOf(input, profileOf(deps));
|
|
30644
|
+
if (calls === null || !calls.every((c) => CHECKED.has(c.toolName)))
|
|
29905
30645
|
return unreadable(deps, input);
|
|
30646
|
+
const call = calls[0] ?? { toolName: stringOf(input, "tool_name") ?? "", toolInput: {} };
|
|
29906
30647
|
let answer;
|
|
29907
30648
|
try {
|
|
29908
|
-
answer = riskyAnswer(
|
|
30649
|
+
answer = riskyAnswer(calls, () => {
|
|
29909
30650
|
const cwd = stringOf(input, "cwd") ?? (deps.cwd ?? (() => process.cwd()))();
|
|
29910
|
-
const projectDir =
|
|
30651
|
+
const projectDir = projectDirOf(deps);
|
|
29911
30652
|
const tempDir = (deps.env ?? process.env)["TMPDIR"];
|
|
29912
30653
|
return {
|
|
29913
30654
|
cwd,
|
|
@@ -29920,7 +30661,7 @@ function runPreToolUse(deps) {
|
|
|
29920
30661
|
} catch {
|
|
29921
30662
|
answer = { silent: true };
|
|
29922
30663
|
}
|
|
29923
|
-
const own =
|
|
30664
|
+
const own = ownCopyOfAll(input, calls, deps);
|
|
29924
30665
|
try {
|
|
29925
30666
|
if (own.kind === "refused") {
|
|
29926
30667
|
const staked = [...answer.silent ? [] : answer.staked, own.facts];
|
|
@@ -29931,12 +30672,12 @@ function runPreToolUse(deps) {
|
|
|
29931
30672
|
}
|
|
29932
30673
|
}
|
|
29933
30674
|
if (mode === "autonomous") {
|
|
29934
|
-
const sendsSaves =
|
|
30675
|
+
const sendsSaves = calls.some((c) => c.toolName === "Bash" && typeof c.toolInput["command"] === "string" && pushesSavedRefs(c.toolInput["command"]));
|
|
29935
30676
|
if (!answer.silent || sendsSaves)
|
|
29936
30677
|
return autonomousReply(call, answer.silent ? [] : answer.staked, sendsSaves, mode, autonomy, deps);
|
|
29937
30678
|
}
|
|
29938
30679
|
if (answer.silent) {
|
|
29939
|
-
if (call.toolName === PACE_HELD_TOOL) {
|
|
30680
|
+
if (calls.length === 1 && call.toolName === PACE_HELD_TOOL) {
|
|
29940
30681
|
const held = paceHoldText(input, call.toolInput, paceDeps);
|
|
29941
30682
|
if (held !== null)
|
|
29942
30683
|
return `${preToolUseReplyText("refuse", held)}
|
|
@@ -29949,7 +30690,16 @@ function runPreToolUse(deps) {
|
|
|
29949
30690
|
return `${preToolUseReplyText("ask", answer.question)}
|
|
29950
30691
|
`;
|
|
29951
30692
|
}
|
|
29952
|
-
|
|
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)}
|
|
29953
30703
|
`;
|
|
29954
30704
|
} catch {
|
|
29955
30705
|
return "";
|
|
@@ -29963,7 +30713,7 @@ function answerModeOf(input, deps, at) {
|
|
|
29963
30713
|
const cached2 = engagementId === void 0 ? null : readCachedAutonomy(engagementId, at.getTime(), deps.home);
|
|
29964
30714
|
if (engagementId !== void 0 && cached2 !== null)
|
|
29965
30715
|
return { mode: "autonomous", autonomy: { engagementId, mayPublish: cached2.mayPublish } };
|
|
29966
|
-
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 };
|
|
29967
30717
|
}
|
|
29968
30718
|
function autonomousReply(call, staked, sendsSaves, mode, autonomy, deps) {
|
|
29969
30719
|
try {
|
|
@@ -30043,11 +30793,40 @@ function recordedOnlyReply(mode) {
|
|
|
30043
30793
|
}
|
|
30044
30794
|
}
|
|
30045
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
|
+
}
|
|
30046
30825
|
function ownCopyOf(input, call, deps) {
|
|
30047
30826
|
try {
|
|
30048
30827
|
const walk2 = deps.walk ?? deps.liveness?.walk;
|
|
30049
30828
|
return judgeOwnCopy(input, call, {
|
|
30050
|
-
projectDir:
|
|
30829
|
+
projectDir: projectDirOf(deps),
|
|
30051
30830
|
home: deps.home ?? homedir10(),
|
|
30052
30831
|
...walk2 === void 0 ? {} : { walk: walk2 },
|
|
30053
30832
|
...deps.liveness === void 0 ? {} : { liveness: deps.liveness }
|
|
@@ -30057,7 +30836,7 @@ function ownCopyOf(input, call, deps) {
|
|
|
30057
30836
|
}
|
|
30058
30837
|
}
|
|
30059
30838
|
function ownCopyNotice(input, deps) {
|
|
30060
|
-
const folder = stringOf(input, "cwd") ??
|
|
30839
|
+
const folder = stringOf(input, "cwd") ?? projectDirOf(deps);
|
|
30061
30840
|
return firstNoticeThisSession("outside-own-copy-unknown", folder, stringOf(input, "session_id"), deps.liveness?.dir) ? `${preToolUseNoticeText(OWN_COPY_UNKNOWN_NOTICE)}
|
|
30062
30841
|
` : "";
|
|
30063
30842
|
}
|
|
@@ -30073,23 +30852,16 @@ function stringOf(input, key) {
|
|
|
30073
30852
|
const v = input[key];
|
|
30074
30853
|
return typeof v === "string" && v !== "" ? v : void 0;
|
|
30075
30854
|
}
|
|
30076
|
-
function toolCallOf(input) {
|
|
30077
|
-
const toolName = stringOf(input, "tool_name");
|
|
30078
|
-
const toolInput = input["tool_input"];
|
|
30079
|
-
if (toolName === void 0 || typeof toolInput !== "object" || toolInput === null || Array.isArray(toolInput))
|
|
30080
|
-
return null;
|
|
30081
|
-
return { toolName, toolInput };
|
|
30082
|
-
}
|
|
30083
30855
|
function unreadable(deps, input) {
|
|
30084
30856
|
const session = (input === null ? void 0 : stringOf(input, "session_id")) ?? /"session_id"\s*:\s*"([^"\\]+)"/.exec(deps.input)?.[1];
|
|
30085
|
-
const folder = (input === null ? void 0 : stringOf(input, "cwd")) ??
|
|
30857
|
+
const folder = (input === null ? void 0 : stringOf(input, "cwd")) ?? projectDirOf(deps);
|
|
30086
30858
|
return firstNoticeThisSession("pre-tool-use-unreadable", folder, session) ? `${preToolUseNoticeText(UNREADABLE_INPUT_NOTICE)}
|
|
30087
30859
|
` : "";
|
|
30088
30860
|
}
|
|
30089
30861
|
|
|
30090
30862
|
// dist/hooks/session-diff.js
|
|
30091
|
-
import { unlinkSync as
|
|
30092
|
-
import { join as
|
|
30863
|
+
import { unlinkSync as unlinkSync10 } from "node:fs";
|
|
30864
|
+
import { join as join17 } from "node:path";
|
|
30093
30865
|
var STOP_HOOK_EVENT = {
|
|
30094
30866
|
stop: "Stop",
|
|
30095
30867
|
"subagent-stop": "SubagentStop"
|
|
@@ -30124,14 +30896,14 @@ function collectDiff(repoRoot, base) {
|
|
|
30124
30896
|
getDiff: (relPath) => getWorkingTreeFileDiff(relPath, repoRoot)
|
|
30125
30897
|
};
|
|
30126
30898
|
}
|
|
30127
|
-
async function runSessionDiff(input, kind, readSeat = readSeatConfiguration, afterPass) {
|
|
30128
|
-
const reply = hookReply(STOP_HOOK_EVENT[kind]);
|
|
30129
|
-
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);
|
|
30130
30902
|
if (afterPass)
|
|
30131
30903
|
await afterPass(reply);
|
|
30132
30904
|
reply.flush();
|
|
30133
30905
|
}
|
|
30134
|
-
async function evaluateSession(input, kind, reply, readSeat) {
|
|
30906
|
+
async function evaluateSession(input, kind, reply, readSeat, profile) {
|
|
30135
30907
|
const repoRootResult = getRepoRoot(process.cwd());
|
|
30136
30908
|
if (!repoRootResult.ok) {
|
|
30137
30909
|
if (firstNoRepoNoticeThisSession(process.cwd(), input.session_id)) {
|
|
@@ -30154,7 +30926,7 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30154
30926
|
const candidates = ownEditedFiles(repoRoot, sessionId).filter((relPath) => !tracked.has(relPath));
|
|
30155
30927
|
const untracked = getUntrackedFiles(repoRoot, candidates);
|
|
30156
30928
|
const created = untracked.ok ? untracked.value.filter((relPath) => !tracked.has(relPath)) : [];
|
|
30157
|
-
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);
|
|
30158
30930
|
const { evaluated } = partitionInstallerOutput(collected.files);
|
|
30159
30931
|
const { evaluated: createdEvaluated } = partitionInstallerOutput(created);
|
|
30160
30932
|
const changedFiles2 = [...ownChangedFiles(repoRoot, evaluated, sessionId), ...createdEvaluated];
|
|
@@ -30180,11 +30952,11 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30180
30952
|
await emitRunRecord({ runType: "hook", outcome: "unconfigured", changeSet, diffBase });
|
|
30181
30953
|
if (!firstNoticeThisSession("credential-missing", repoRoot, sessionId))
|
|
30182
30954
|
return;
|
|
30183
|
-
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()));
|
|
30955
|
+
emitNeverConfigured(UNCONFIGURED_EXIT_STOP_FAMILY, readNeverConfiguredReason(), pinnedEngagementId(projectFolder()), reply);
|
|
30184
30956
|
return;
|
|
30185
30957
|
}
|
|
30186
30958
|
const { guardServiceUrl, guardServiceToken, guardEngagementId } = configResult.config;
|
|
30187
|
-
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat);
|
|
30959
|
+
const seatConfiguration = gatherSeatConfiguration(repoRoot, readSeat, profile);
|
|
30188
30960
|
const evaluation = await evaluateWithRefresh({
|
|
30189
30961
|
guardServiceUrl,
|
|
30190
30962
|
token: guardServiceToken,
|
|
@@ -30215,7 +30987,8 @@ async function evaluateSession(input, kind, reply, readSeat) {
|
|
|
30215
30987
|
// The remedy the renewal attempt earned: the sign-in when the account
|
|
30216
30988
|
// credential is what was refused, and otherwise the plane's own sentence
|
|
30217
30989
|
// about why it would not mint.
|
|
30218
|
-
evaluation.remedy
|
|
30990
|
+
evaluation.remedy,
|
|
30991
|
+
reply
|
|
30219
30992
|
);
|
|
30220
30993
|
return;
|
|
30221
30994
|
}
|
|
@@ -30271,7 +31044,7 @@ function removeFindingsFile(sessionId) {
|
|
|
30271
31044
|
if (path === null)
|
|
30272
31045
|
return;
|
|
30273
31046
|
try {
|
|
30274
|
-
|
|
31047
|
+
unlinkSync10(path);
|
|
30275
31048
|
} catch {
|
|
30276
31049
|
}
|
|
30277
31050
|
}
|
|
@@ -30317,9 +31090,9 @@ async function readHookStdin() {
|
|
|
30317
31090
|
}
|
|
30318
31091
|
}
|
|
30319
31092
|
function readStdin2() {
|
|
30320
|
-
return new Promise((
|
|
31093
|
+
return new Promise((resolve10) => {
|
|
30321
31094
|
if (process.stdin.isTTY) {
|
|
30322
|
-
|
|
31095
|
+
resolve10("");
|
|
30323
31096
|
return;
|
|
30324
31097
|
}
|
|
30325
31098
|
const chunks = [];
|
|
@@ -30329,7 +31102,7 @@ function readStdin2() {
|
|
|
30329
31102
|
return;
|
|
30330
31103
|
settled = true;
|
|
30331
31104
|
clearTimeout(timer);
|
|
30332
|
-
|
|
31105
|
+
resolve10(Buffer.concat(chunks).toString("utf-8"));
|
|
30333
31106
|
};
|
|
30334
31107
|
const timer = setTimeout(done, HOOK_STDIN_GRACE_MS);
|
|
30335
31108
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -30339,11 +31112,11 @@ function readStdin2() {
|
|
|
30339
31112
|
}
|
|
30340
31113
|
|
|
30341
31114
|
// dist/hooks/subagent-stop.js
|
|
30342
|
-
async function runSubagentStop() {
|
|
30343
|
-
const input = await readHookStdin();
|
|
31115
|
+
async function runSubagentStop(profile = AGENT_PROFILES["claude-code"]) {
|
|
31116
|
+
const input = hookInputForRule(await readHookStdin(), profile.configFolder);
|
|
30344
31117
|
recordTimeEvent("turn-end", input);
|
|
30345
|
-
subagentStopped(input);
|
|
30346
|
-
await runSessionDiff(input, "subagent-stop");
|
|
31118
|
+
subagentStopped(input, { pace: { configFolder: profile.configFolder } });
|
|
31119
|
+
await runSessionDiff(input, "subagent-stop", void 0, void 0, profile);
|
|
30347
31120
|
}
|
|
30348
31121
|
function subagentStopped(input, deps = {}) {
|
|
30349
31122
|
if (clearLiveness(input, deps.liveness) === 0)
|
|
@@ -30351,14 +31124,14 @@ function subagentStopped(input, deps = {}) {
|
|
|
30351
31124
|
}
|
|
30352
31125
|
|
|
30353
31126
|
// dist/hooks/stop.js
|
|
30354
|
-
async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}) {
|
|
30355
|
-
const input = await readHookStdin();
|
|
31127
|
+
async function runStop(observeDeps = {}, timeDeps = {}, paceDeps = {}, profile = AGENT_PROFILES["claude-code"]) {
|
|
31128
|
+
const input = hookInputForRule(await readHookStdin(), profile.configFolder);
|
|
30356
31129
|
recordTimeEvent("turn-end", input);
|
|
30357
31130
|
await runSessionDiff(input, "stop", readSeatConfiguration, async (reply) => {
|
|
30358
31131
|
await observeChanges(input.session_id, reply, observeDeps);
|
|
30359
31132
|
await sendTimeRecord(timeDeps);
|
|
30360
|
-
await paceAtTurnEnd(input, reply, paceDeps);
|
|
30361
|
-
});
|
|
31133
|
+
await paceAtTurnEnd(input, reply, { configFolder: profile.configFolder, ...paceDeps });
|
|
31134
|
+
}, profile);
|
|
30362
31135
|
}
|
|
30363
31136
|
|
|
30364
31137
|
// dist/hooks/scope-phrase.js
|
|
@@ -30514,20 +31287,20 @@ function matchReviewPhraseShape(shape, reviews) {
|
|
|
30514
31287
|
}
|
|
30515
31288
|
|
|
30516
31289
|
// dist/hooks/shown-record.js
|
|
30517
|
-
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";
|
|
30518
31291
|
import { homedir as homedir11, platform as platform3 } from "node:os";
|
|
30519
|
-
import { join as
|
|
31292
|
+
import { join as join18 } from "node:path";
|
|
30520
31293
|
function recordDir(engagementId, home) {
|
|
30521
31294
|
if (!isEngagementId(engagementId))
|
|
30522
31295
|
return null;
|
|
30523
|
-
return
|
|
31296
|
+
return join18(home ?? homedir11(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId);
|
|
30524
31297
|
}
|
|
30525
31298
|
function readShownRecord(engagementId, home) {
|
|
30526
31299
|
const dir = recordDir(engagementId, home);
|
|
30527
31300
|
if (dir === null)
|
|
30528
31301
|
return { entries: [], reviews: [] };
|
|
30529
31302
|
try {
|
|
30530
|
-
return parseShownRecord(
|
|
31303
|
+
return parseShownRecord(readFileSync24(join18(dir, SHOWN_PLANS_FILENAME), "utf-8"));
|
|
30531
31304
|
} catch {
|
|
30532
31305
|
return { entries: [], reviews: [] };
|
|
30533
31306
|
}
|
|
@@ -30548,24 +31321,24 @@ function moveListedReviewForward(engagementId, home, listed, reviewVersion, deci
|
|
|
30548
31321
|
const dir = recordDir(engagementId, home);
|
|
30549
31322
|
if (dir === null)
|
|
30550
31323
|
return false;
|
|
30551
|
-
const path =
|
|
30552
|
-
const current = parseShownRecord(
|
|
31324
|
+
const path = join18(dir, SHOWN_PLANS_FILENAME);
|
|
31325
|
+
const current = parseShownRecord(readFileSync24(path, "utf-8"));
|
|
30553
31326
|
const at = current.reviews.findIndex((r) => r.phase === listed.phase && r.shownAt === listed.shownAt && r.reviewVersion === listed.reviewVersion);
|
|
30554
31327
|
if (at === -1)
|
|
30555
31328
|
return false;
|
|
30556
31329
|
const acts = new Map(decided.map((d) => [d.part, d.act]));
|
|
30557
31330
|
const reviews = current.reviews.map((review, i) => i === at ? { ...review, reviewVersion, parts: review.parts.map((p) => movedPart(p, acts.get(p.part))) } : review);
|
|
30558
31331
|
const body = serialiseShownRecord({ entries: current.entries, reviews });
|
|
30559
|
-
temp =
|
|
30560
|
-
|
|
31332
|
+
temp = join18(dir, `.${SHOWN_PLANS_FILENAME}.${process.pid}.tmp`);
|
|
31333
|
+
writeFileSync15(temp, body, { mode: 384 });
|
|
30561
31334
|
if (platform3() !== "win32")
|
|
30562
31335
|
chmodSync4(temp, 384);
|
|
30563
|
-
|
|
31336
|
+
renameSync13(temp, path);
|
|
30564
31337
|
return true;
|
|
30565
31338
|
} catch {
|
|
30566
31339
|
if (temp !== null) {
|
|
30567
31340
|
try {
|
|
30568
|
-
|
|
31341
|
+
unlinkSync11(temp);
|
|
30569
31342
|
} catch {
|
|
30570
31343
|
return false;
|
|
30571
31344
|
}
|
|
@@ -30909,16 +31682,63 @@ function autonomyLineForAnswer(answer) {
|
|
|
30909
31682
|
}
|
|
30910
31683
|
}
|
|
30911
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
|
+
}
|
|
30912
31712
|
|
|
30913
31713
|
// dist/hooks/user-prompt-submit.js
|
|
30914
31714
|
var CHAT_REQUEST_TIMEOUT_MS = 6e3;
|
|
30915
31715
|
var SHOWN_ENTRY_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
30916
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) {
|
|
30917
31734
|
let act = null;
|
|
30918
31735
|
let autonomyAct = null;
|
|
30919
31736
|
const review = { recognised: false };
|
|
30920
31737
|
try {
|
|
30921
31738
|
const prompt = promptOf(opts.input);
|
|
31739
|
+
if (isAllowReply(prompt))
|
|
31740
|
+
return `${decideAllow(opts)}
|
|
31741
|
+
`;
|
|
30922
31742
|
const autonomyShape = autonomyPhraseShape(prompt);
|
|
30923
31743
|
if (autonomyShape !== null) {
|
|
30924
31744
|
autonomyAct = autonomyShape;
|
|
@@ -30948,9 +31768,35 @@ async function runUserPromptSubmit(opts) {
|
|
|
30948
31768
|
`;
|
|
30949
31769
|
}
|
|
30950
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
|
+
}
|
|
30951
31794
|
async function decideAutonomy(act, opts) {
|
|
30952
31795
|
const env = opts.env ?? process.env;
|
|
30953
|
-
|
|
31796
|
+
const typed = whoTyped(opts);
|
|
31797
|
+
if (typed === "cannot-tell")
|
|
31798
|
+
return codexAutonomyLine();
|
|
31799
|
+
if (typed === "not-attended")
|
|
30954
31800
|
return autonomyNotAttendedLine();
|
|
30955
31801
|
const read = readConfig(env);
|
|
30956
31802
|
if (read.config === null) {
|
|
@@ -30996,12 +31842,18 @@ async function decideReview(shape, opts, progress) {
|
|
|
30996
31842
|
if (match2.kind === "nothing")
|
|
30997
31843
|
return "";
|
|
30998
31844
|
progress.recognised = true;
|
|
30999
|
-
|
|
31845
|
+
const typed2 = whoTyped(opts);
|
|
31846
|
+
if (typed2 === "cannot-tell")
|
|
31847
|
+
return codexReviewLine("accept");
|
|
31848
|
+
if (typed2 === "not-attended")
|
|
31000
31849
|
return reviewNotAttendedLine();
|
|
31001
31850
|
return checkAndSendReview(match2, read2.config.guardEngagementId, read2.config.guardServiceToken, env, opts);
|
|
31002
31851
|
}
|
|
31003
31852
|
progress.recognised = true;
|
|
31004
|
-
|
|
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")
|
|
31005
31857
|
return reviewNotAttendedLine();
|
|
31006
31858
|
const read = readConfig(env);
|
|
31007
31859
|
if (read.config === null) {
|
|
@@ -31107,7 +31959,10 @@ async function postReview(bulk, entry, request, engagementId, credential, env, o
|
|
|
31107
31959
|
async function decide2(shape, opts) {
|
|
31108
31960
|
const { act } = shape;
|
|
31109
31961
|
const env = opts.env ?? process.env;
|
|
31110
|
-
|
|
31962
|
+
const typed = whoTyped(opts);
|
|
31963
|
+
if (typed === "cannot-tell")
|
|
31964
|
+
return codexScopeLine(act);
|
|
31965
|
+
if (typed === "not-attended")
|
|
31111
31966
|
return notAttendedLine(act);
|
|
31112
31967
|
const read = readConfig(env);
|
|
31113
31968
|
if (read.config === null) {
|
|
@@ -31140,6 +31995,16 @@ async function decide2(shape, opts) {
|
|
|
31140
31995
|
return showFirstLine(act);
|
|
31141
31996
|
return post(act, entry, request.data, guardEngagementId, guardServiceToken, env, opts.fetchFn);
|
|
31142
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
|
+
}
|
|
31143
32008
|
function promptOf(input) {
|
|
31144
32009
|
try {
|
|
31145
32010
|
const parsed = JSON.parse(input);
|
|
@@ -31201,17 +32066,17 @@ function runUserPromptFindings(opts) {
|
|
|
31201
32066
|
}
|
|
31202
32067
|
|
|
31203
32068
|
// dist/resolve-engagement.js
|
|
31204
|
-
import { existsSync as existsSync8, readFileSync as
|
|
31205
|
-
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";
|
|
31206
32071
|
import { homedir as homedir12 } from "node:os";
|
|
31207
32072
|
function defaultRegistryPath() {
|
|
31208
|
-
return
|
|
32073
|
+
return join19(homedir12(), ".halfcycle", "registry.json");
|
|
31209
32074
|
}
|
|
31210
32075
|
function readRegistry(registryPath = defaultRegistryPath()) {
|
|
31211
32076
|
if (!existsSync8(registryPath))
|
|
31212
32077
|
return {};
|
|
31213
32078
|
try {
|
|
31214
|
-
const parsed = JSON.parse(
|
|
32079
|
+
const parsed = JSON.parse(readFileSync25(registryPath, "utf-8"));
|
|
31215
32080
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
31216
32081
|
return {};
|
|
31217
32082
|
const out = {};
|
|
@@ -31247,7 +32112,7 @@ function resolveEngagement(cwd, registry2) {
|
|
|
31247
32112
|
function isSelfOrAncestor(ancestor, target) {
|
|
31248
32113
|
if (ancestor === target)
|
|
31249
32114
|
return true;
|
|
31250
|
-
const withSep = ancestor.endsWith(
|
|
32115
|
+
const withSep = ancestor.endsWith(sep5) ? ancestor : ancestor + sep5;
|
|
31251
32116
|
return target.startsWith(withSep);
|
|
31252
32117
|
}
|
|
31253
32118
|
function runResolveEngagement(cwd, registryPath) {
|
|
@@ -31259,13 +32124,27 @@ function runResolveEngagement(cwd, registryPath) {
|
|
|
31259
32124
|
}
|
|
31260
32125
|
|
|
31261
32126
|
// dist/refresh-credential.js
|
|
31262
|
-
import { readFileSync as
|
|
31263
|
-
import { resolve as
|
|
32127
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
32128
|
+
import { resolve as resolve9 } from "node:path";
|
|
31264
32129
|
|
|
31265
32130
|
// dist/mcp-headers.js
|
|
31266
|
-
import { readFileSync as
|
|
32131
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
31267
32132
|
import { homedir as homedir13 } from "node:os";
|
|
31268
|
-
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
|
+
}
|
|
31269
32148
|
function defaultWriteErr(text) {
|
|
31270
32149
|
process.stderr.write(text);
|
|
31271
32150
|
}
|
|
@@ -31278,12 +32157,12 @@ function headerLine(bearer, clientVersion) {
|
|
|
31278
32157
|
}
|
|
31279
32158
|
function projectRootFromBinary(binaryPath) {
|
|
31280
32159
|
const depth = VENDORED_BIN_REL.split("/").length - 1;
|
|
31281
|
-
return
|
|
32160
|
+
return resolve8(dirname9(binaryPath), ...Array(depth).fill(".."));
|
|
31282
32161
|
}
|
|
31283
32162
|
function readPinnedVersion(root) {
|
|
31284
32163
|
let raw;
|
|
31285
32164
|
try {
|
|
31286
|
-
raw =
|
|
32165
|
+
raw = readFileSync26(resolve8(root, BUNDLE_PIN_REL), "utf-8");
|
|
31287
32166
|
} catch {
|
|
31288
32167
|
return void 0;
|
|
31289
32168
|
}
|
|
@@ -31325,7 +32204,7 @@ function sentences(lines2) {
|
|
|
31325
32204
|
}
|
|
31326
32205
|
function badIdSentence(root) {
|
|
31327
32206
|
return sentences([
|
|
31328
|
-
`${
|
|
32207
|
+
`${resolve8(root, BUNDLE_PIN_REL)} names an engagement id that is not a Halfcycle project id,`,
|
|
31329
32208
|
'so no credential was read. Delete that file and run "npx halfcycle" again: this repository',
|
|
31330
32209
|
"is then set up as a new Halfcycle project."
|
|
31331
32210
|
]);
|
|
@@ -31339,7 +32218,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
31339
32218
|
]);
|
|
31340
32219
|
case "no-id":
|
|
31341
32220
|
return sentences([
|
|
31342
|
-
`${
|
|
32221
|
+
`${resolve8(root, BUNDLE_PIN_REL)} names no engagement id.`,
|
|
31343
32222
|
'run "npx halfcycle" here to rewrite it.'
|
|
31344
32223
|
]);
|
|
31345
32224
|
case "bad-id":
|
|
@@ -31356,7 +32235,7 @@ function storeProblemSentence(problem, root, home, engagementId) {
|
|
|
31356
32235
|
case "no-credential":
|
|
31357
32236
|
return sentences([
|
|
31358
32237
|
`this machine holds no credential for engagement ${engagementId ?? ""}.`,
|
|
31359
|
-
`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.`
|
|
31360
32239
|
]);
|
|
31361
32240
|
}
|
|
31362
32241
|
}
|
|
@@ -31418,13 +32297,14 @@ async function chooseBearer(opts) {
|
|
|
31418
32297
|
}
|
|
31419
32298
|
return NO_CREDENTIAL_PLACEHOLDER;
|
|
31420
32299
|
}
|
|
32300
|
+
const registrationFile = opts.registrationFile ?? MCP_REGISTRATION_REL;
|
|
31421
32301
|
const want = urlOrigin(recorded);
|
|
31422
32302
|
const got = requested === void 0 ? void 0 : urlOrigin(requested);
|
|
31423
32303
|
if (want === void 0 || got !== want) {
|
|
31424
32304
|
writeErr(sentences([
|
|
31425
|
-
|
|
32305
|
+
`${registrationFile} asks for this repository's Halfcycle credential to be sent to ${shownUrl(requested ?? "")},`,
|
|
31426
32306
|
`which is not this machine's Halfcycle server (${want ?? "none recorded"}). The credential was NOT sent.`,
|
|
31427
|
-
|
|
32307
|
+
`if nobody meant to change ${registrationFile}, treat that change as suspect; "npx halfcycle" restores the entry.`
|
|
31428
32308
|
]));
|
|
31429
32309
|
return NO_CREDENTIAL_PLACEHOLDER;
|
|
31430
32310
|
}
|
|
@@ -31444,13 +32324,18 @@ async function chooseBearer(opts) {
|
|
|
31444
32324
|
async function mcpHeaders(opts) {
|
|
31445
32325
|
const writeErr = opts.writeErr ?? defaultWriteErr;
|
|
31446
32326
|
const root = projectRootFromBinary(opts.binaryPath);
|
|
32327
|
+
const variableUrl = readMcpServerUrl();
|
|
31447
32328
|
try {
|
|
31448
32329
|
return headerLine(await chooseBearer({
|
|
31449
|
-
|
|
32330
|
+
binaryPath: opts.binaryPath,
|
|
32331
|
+
home: opts.home,
|
|
32332
|
+
fetchFn: opts.fetchFn,
|
|
32333
|
+
deadlineMs: opts.deadlineMs,
|
|
31450
32334
|
writeErr,
|
|
31451
|
-
requestedUrl:
|
|
32335
|
+
requestedUrl: variableUrl ?? opts.url,
|
|
31452
32336
|
renewal: "renew-here",
|
|
31453
|
-
environmentTokenFirst: true
|
|
32337
|
+
environmentTokenFirst: true,
|
|
32338
|
+
registrationFile: variableUrl === void 0 && opts.url !== void 0 ? AGENT_PROFILES.codex.mcpRegistration.file : void 0
|
|
31454
32339
|
}), readPinnedVersion(root));
|
|
31455
32340
|
} catch (err) {
|
|
31456
32341
|
try {
|
|
@@ -31464,6 +32349,25 @@ async function mcpHeaders(opts) {
|
|
|
31464
32349
|
return headerLine(NO_CREDENTIAL_PLACEHOLDER, readPinnedVersion(root));
|
|
31465
32350
|
}
|
|
31466
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
|
+
}
|
|
31467
32371
|
|
|
31468
32372
|
// dist/refresh-credential.js
|
|
31469
32373
|
var UNAUTHORIZED2 = 401;
|
|
@@ -31504,13 +32408,19 @@ async function refreshCredentialToken(opts) {
|
|
|
31504
32408
|
return { token: refreshed.token };
|
|
31505
32409
|
}
|
|
31506
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
|
+
}
|
|
31507
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.`;
|
|
31508
32418
|
var INITIALIZE_BODY = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} });
|
|
31509
32419
|
var SESSION_START_PROBE_ATTEMPTS = 2;
|
|
31510
32420
|
function registeredMcpUrl(projectRoot) {
|
|
31511
32421
|
let parsed;
|
|
31512
32422
|
try {
|
|
31513
|
-
parsed = JSON.parse(
|
|
32423
|
+
parsed = JSON.parse(readFileSync27(resolve9(projectRoot, MCP_REGISTRATION_REL), "utf-8"));
|
|
31514
32424
|
} catch {
|
|
31515
32425
|
return void 0;
|
|
31516
32426
|
}
|
|
@@ -31682,6 +32592,7 @@ function defaultWriteErr2(text) {
|
|
|
31682
32592
|
}
|
|
31683
32593
|
async function runRefreshCredential(opts) {
|
|
31684
32594
|
const writeErr = opts.writeErr ?? defaultWriteErr2;
|
|
32595
|
+
const profile = opts.profile ?? AGENT_PROFILES["claude-code"];
|
|
31685
32596
|
const startedAt = Date.now();
|
|
31686
32597
|
try {
|
|
31687
32598
|
const renewalSaid = [];
|
|
@@ -31695,7 +32606,7 @@ async function runRefreshCredential(opts) {
|
|
|
31695
32606
|
]);
|
|
31696
32607
|
renewalOpen = false;
|
|
31697
32608
|
const parts = [];
|
|
31698
|
-
if (
|
|
32609
|
+
if (isCheckedSessionStart(input, profile)) {
|
|
31699
32610
|
const connection = await checkConnection(renewedToken, { ...opts, writeErr });
|
|
31700
32611
|
if (connection !== void 0) {
|
|
31701
32612
|
parts.push(connection.sentence);
|
|
@@ -31717,7 +32628,7 @@ async function runRefreshCredential(opts) {
|
|
|
31717
32628
|
}
|
|
31718
32629
|
}
|
|
31719
32630
|
}
|
|
31720
|
-
if (
|
|
32631
|
+
if (isCheckedSessionStart(input, profile)) {
|
|
31721
32632
|
const config2 = readConfig().config;
|
|
31722
32633
|
if (config2 !== null) {
|
|
31723
32634
|
const remainingForMargin = SESSION_START_HOOK_TIMEOUT_MS - SESSION_START_MARGIN_MS - (Date.now() - startedAt);
|
|
@@ -31757,7 +32668,7 @@ async function runRefreshCredential(opts) {
|
|
|
31757
32668
|
import { fileURLToPath } from "node:url";
|
|
31758
32669
|
|
|
31759
32670
|
// dist/capture.js
|
|
31760
|
-
import { readFileSync as
|
|
32671
|
+
import { readFileSync as readFileSync28, writeFileSync as writeFileSync16, mkdirSync as mkdirSync10, existsSync as existsSync9 } from "node:fs";
|
|
31761
32672
|
import { dirname as dirname10 } from "node:path";
|
|
31762
32673
|
function runCapture(provider, endpoint, opts = {}) {
|
|
31763
32674
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
@@ -31794,7 +32705,7 @@ function runCapture(provider, endpoint, opts = {}) {
|
|
|
31794
32705
|
const manifestFile = manifestPath(repoRoot);
|
|
31795
32706
|
try {
|
|
31796
32707
|
mkdirSync10(dirname10(fixture), { recursive: true });
|
|
31797
|
-
|
|
32708
|
+
writeFileSync16(fixture, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
31798
32709
|
} catch (err) {
|
|
31799
32710
|
const msg = err instanceof Error ? err.message : String(err);
|
|
31800
32711
|
return { ok: false, kind: "write-failed", message: `could not write fixture: ${msg}` };
|
|
@@ -31825,7 +32736,7 @@ function readInputFile(fromFile) {
|
|
|
31825
32736
|
if (!fromFile)
|
|
31826
32737
|
return void 0;
|
|
31827
32738
|
try {
|
|
31828
|
-
return
|
|
32739
|
+
return readFileSync28(fromFile, "utf-8");
|
|
31829
32740
|
} catch {
|
|
31830
32741
|
return void 0;
|
|
31831
32742
|
}
|
|
@@ -31837,10 +32748,10 @@ function relativeFixturePath(repoRoot, fixture) {
|
|
|
31837
32748
|
|
|
31838
32749
|
// dist/prove.js
|
|
31839
32750
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
31840
|
-
import { createHash as
|
|
31841
|
-
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";
|
|
31842
32753
|
import { homedir as homedir14 } from "node:os";
|
|
31843
|
-
import { dirname as dirname11, join as
|
|
32754
|
+
import { dirname as dirname11, join as join20 } from "node:path";
|
|
31844
32755
|
|
|
31845
32756
|
// dist/test-counts.js
|
|
31846
32757
|
function sum(values) {
|
|
@@ -31949,10 +32860,10 @@ function isProofTestPath(path) {
|
|
|
31949
32860
|
function testRunsDir(engagementId, home) {
|
|
31950
32861
|
if (!isEngagementId(engagementId))
|
|
31951
32862
|
return null;
|
|
31952
|
-
return
|
|
32863
|
+
return join20(home ?? homedir14(), HALFCYCLE_DIR_NAME, ENGAGEMENTS_DIR_NAME, engagementId, TEST_RUNS_DIR_NAME);
|
|
31953
32864
|
}
|
|
31954
32865
|
function commandIdOf(command) {
|
|
31955
|
-
return
|
|
32866
|
+
return createHash6("sha256").update(command, "utf8").digest("hex").slice(0, 16);
|
|
31956
32867
|
}
|
|
31957
32868
|
var PLAIN_WORD = /^[A-Za-z0-9_./:=@%+,-]+$/;
|
|
31958
32869
|
function commandFromWords(words, platform4 = process.platform) {
|
|
@@ -32037,19 +32948,19 @@ function parseProveArgs(args2) {
|
|
|
32037
32948
|
function writeJsonFile2(path, value) {
|
|
32038
32949
|
mkdirSync11(dirname11(path), { recursive: true, mode: 448 });
|
|
32039
32950
|
const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
|
|
32040
|
-
|
|
32041
|
-
|
|
32951
|
+
writeFileSync17(tmp, JSON.stringify(value), { mode: 384 });
|
|
32952
|
+
renameSync14(tmp, path);
|
|
32042
32953
|
}
|
|
32043
32954
|
function subjectKey(subject) {
|
|
32044
32955
|
return subject.kind === "task" ? ["task", subject.phase, subject.task] : ["change", subject.change];
|
|
32045
32956
|
}
|
|
32046
32957
|
function keptCheckPath(dir, subject, label) {
|
|
32047
|
-
const key =
|
|
32048
|
-
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`);
|
|
32049
32960
|
}
|
|
32050
32961
|
function readKeptCheck(dir, subject, label) {
|
|
32051
32962
|
try {
|
|
32052
|
-
const raw = JSON.parse(
|
|
32963
|
+
const raw = JSON.parse(readFileSync29(keptCheckPath(dir, subject, label), "utf-8"));
|
|
32053
32964
|
if (typeof raw.command !== "string" || raw.command === "" || typeof raw.commandId !== "string")
|
|
32054
32965
|
return null;
|
|
32055
32966
|
const b = raw.before;
|
|
@@ -32060,21 +32971,21 @@ function readKeptCheck(dir, subject, label) {
|
|
|
32060
32971
|
}
|
|
32061
32972
|
}
|
|
32062
32973
|
function queuePath(dir, runId) {
|
|
32063
|
-
return
|
|
32974
|
+
return join20(dir, QUEUE_DIR_NAME, `${runId}.json`);
|
|
32064
32975
|
}
|
|
32065
32976
|
function readQueue(dir) {
|
|
32066
32977
|
let names;
|
|
32067
32978
|
try {
|
|
32068
|
-
names = readdirSync10(
|
|
32979
|
+
names = readdirSync10(join20(dir, QUEUE_DIR_NAME)).filter((n) => n.endsWith(".json"));
|
|
32069
32980
|
} catch {
|
|
32070
32981
|
return [];
|
|
32071
32982
|
}
|
|
32072
32983
|
const reports = [];
|
|
32073
32984
|
for (const name of names) {
|
|
32074
|
-
const path =
|
|
32985
|
+
const path = join20(dir, QUEUE_DIR_NAME, name);
|
|
32075
32986
|
let parsed = null;
|
|
32076
32987
|
try {
|
|
32077
|
-
parsed = testRunReportSchema.safeParse(JSON.parse(
|
|
32988
|
+
parsed = testRunReportSchema.safeParse(JSON.parse(readFileSync29(path, "utf-8")));
|
|
32078
32989
|
} catch {
|
|
32079
32990
|
parsed = null;
|
|
32080
32991
|
}
|
|
@@ -32087,7 +32998,7 @@ function readQueue(dir) {
|
|
|
32087
32998
|
}
|
|
32088
32999
|
function removeFile(path) {
|
|
32089
33000
|
try {
|
|
32090
|
-
|
|
33001
|
+
unlinkSync12(path);
|
|
32091
33002
|
} catch {
|
|
32092
33003
|
}
|
|
32093
33004
|
}
|
|
@@ -32154,7 +33065,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
32154
33065
|
mkdirSync11(dirname11(logPath), { recursive: true, mode: 448 });
|
|
32155
33066
|
const log = createWriteStream(logPath, { mode: 384 });
|
|
32156
33067
|
const began = process.hrtime.bigint();
|
|
32157
|
-
return new Promise((
|
|
33068
|
+
return new Promise((resolve10) => {
|
|
32158
33069
|
let timedOut = false;
|
|
32159
33070
|
let started = true;
|
|
32160
33071
|
let settled = false;
|
|
@@ -32189,7 +33100,7 @@ function runCommand(command, repoRoot, logPath, env, deps) {
|
|
|
32189
33100
|
process.off("SIGINT", onSignal);
|
|
32190
33101
|
process.off("SIGTERM", onSignal);
|
|
32191
33102
|
const durationMs = Number((process.hrtime.bigint() - began) / 1000000n);
|
|
32192
|
-
log.end(() =>
|
|
33103
|
+
log.end(() => resolve10({ exitCode: timedOut || !started ? null : code, timedOut, started, durationMs }));
|
|
32193
33104
|
};
|
|
32194
33105
|
child.on("error", (e) => {
|
|
32195
33106
|
started = false;
|
|
@@ -32243,7 +33154,7 @@ function checkCommand(command, reason, repoRoot, env, home) {
|
|
|
32243
33154
|
...home === void 0 ? {} : { home }
|
|
32244
33155
|
}));
|
|
32245
33156
|
} catch {
|
|
32246
|
-
return { silent: false, question: couldNotTellQuestion(reason ?? null), staked: [] };
|
|
33157
|
+
return { silent: false, question: couldNotTellQuestion(reason ?? null), refusal: couldNotTellRefusal(reason ?? null), staked: [] };
|
|
32247
33158
|
}
|
|
32248
33159
|
}
|
|
32249
33160
|
async function runProve(args2, deps = {}) {
|
|
@@ -32311,13 +33222,13 @@ ${PROVE_USAGE}`);
|
|
|
32311
33222
|
if (request.when === "before" && changedOutsideTests > 0)
|
|
32312
33223
|
say(lateBeforeLine(changedOutsideTests));
|
|
32313
33224
|
const runId = randomUUID3();
|
|
32314
|
-
const logPath =
|
|
33225
|
+
const logPath = join20(dir, `${runId}.log`);
|
|
32315
33226
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
32316
33227
|
const deadline = deps.deadlineMs ?? PROVE_RUN_DEADLINE_MS;
|
|
32317
33228
|
const end = await runCommand(command, repoRoot, logPath, env, deps);
|
|
32318
33229
|
let output = "";
|
|
32319
33230
|
try {
|
|
32320
|
-
output =
|
|
33231
|
+
output = readFileSync29(logPath, "utf-8");
|
|
32321
33232
|
} catch {
|
|
32322
33233
|
output = "";
|
|
32323
33234
|
}
|
|
@@ -32367,7 +33278,7 @@ ${PROVE_USAGE}`);
|
|
|
32367
33278
|
|
|
32368
33279
|
// dist/statusline.js
|
|
32369
33280
|
import { spawn as spawn3 } from "node:child_process";
|
|
32370
|
-
import { readFileSync as
|
|
33281
|
+
import { readFileSync as readFileSync30 } from "node:fs";
|
|
32371
33282
|
import nodePath5 from "node:path";
|
|
32372
33283
|
var OWN_STATUS_LINE_DEADLINE_MS = 5e3;
|
|
32373
33284
|
var PERSON_SETTINGS_FILENAME = "settings.json";
|
|
@@ -32405,7 +33316,7 @@ function halfcycleStatusLineOf(figures) {
|
|
|
32405
33316
|
}
|
|
32406
33317
|
function personStatusLineCommand(configFolder) {
|
|
32407
33318
|
try {
|
|
32408
|
-
const v = JSON.parse(
|
|
33319
|
+
const v = JSON.parse(readFileSync30(nodePath5.join(configFolder, PERSON_SETTINGS_FILENAME), "utf-8"));
|
|
32409
33320
|
const line2 = v["statusLine"];
|
|
32410
33321
|
if (typeof line2 !== "object" || line2 === null)
|
|
32411
33322
|
return null;
|
|
@@ -32429,14 +33340,14 @@ function personCommandEnv(env) {
|
|
|
32429
33340
|
}
|
|
32430
33341
|
return out;
|
|
32431
33342
|
}
|
|
32432
|
-
var runOwnCommandReal = (command, input, env) => new Promise((
|
|
33343
|
+
var runOwnCommandReal = (command, input, env) => new Promise((resolve10) => {
|
|
32433
33344
|
let out = "";
|
|
32434
33345
|
let settled = false;
|
|
32435
33346
|
const done = (v) => {
|
|
32436
33347
|
if (settled)
|
|
32437
33348
|
return;
|
|
32438
33349
|
settled = true;
|
|
32439
|
-
|
|
33350
|
+
resolve10(v);
|
|
32440
33351
|
};
|
|
32441
33352
|
try {
|
|
32442
33353
|
const child = spawn3(command, {
|
|
@@ -32546,7 +33457,12 @@ async function main() {
|
|
|
32546
33457
|
return;
|
|
32547
33458
|
}
|
|
32548
33459
|
if (cmd === "refresh-credential") {
|
|
32549
|
-
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 });
|
|
32550
33466
|
process.stdout.write(reply, () => process.exit(0));
|
|
32551
33467
|
return;
|
|
32552
33468
|
}
|
|
@@ -32631,7 +33547,7 @@ async function main() {
|
|
|
32631
33547
|
return;
|
|
32632
33548
|
}
|
|
32633
33549
|
if (cmd === "mcp-headers") {
|
|
32634
|
-
const line2 = await
|
|
33550
|
+
const line2 = await headersVerbLine(fileURLToPath(import.meta.url), args.slice(1));
|
|
32635
33551
|
process.stdout.write(line2, () => process.exit(0));
|
|
32636
33552
|
return;
|
|
32637
33553
|
}
|
|
@@ -32673,25 +33589,30 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32673
33589
|
return;
|
|
32674
33590
|
}
|
|
32675
33591
|
if (cmd === "hook") {
|
|
33592
|
+
const profile = agentFromArguments(args.slice(2));
|
|
33593
|
+
if (profile === null) {
|
|
33594
|
+
unknownCommand();
|
|
33595
|
+
return;
|
|
33596
|
+
}
|
|
32676
33597
|
if (sub === "post-tool-use") {
|
|
32677
|
-
await runPostToolUse();
|
|
32678
|
-
process.exit(
|
|
33598
|
+
await runPostToolUse(void 0, profile);
|
|
33599
|
+
process.exit(hookExitCode(profile));
|
|
32679
33600
|
return;
|
|
32680
33601
|
}
|
|
32681
33602
|
if (sub === "stop") {
|
|
32682
|
-
await runStop();
|
|
32683
|
-
process.exit(
|
|
33603
|
+
await runStop(void 0, void 0, void 0, profile);
|
|
33604
|
+
process.exit(hookExitCode(profile));
|
|
32684
33605
|
return;
|
|
32685
33606
|
}
|
|
32686
33607
|
if (sub === "subagent-stop") {
|
|
32687
|
-
await runSubagentStop();
|
|
32688
|
-
process.exit(
|
|
33608
|
+
await runSubagentStop(profile);
|
|
33609
|
+
process.exit(hookExitCode(profile));
|
|
32689
33610
|
return;
|
|
32690
33611
|
}
|
|
32691
33612
|
if (sub === "pre-tool-use") {
|
|
32692
33613
|
let reply = "";
|
|
32693
33614
|
try {
|
|
32694
|
-
reply = runPreToolUse({ input: await readStdin2() });
|
|
33615
|
+
reply = runPreToolUse({ input: await readStdin2(), profile });
|
|
32695
33616
|
} catch {
|
|
32696
33617
|
reply = "";
|
|
32697
33618
|
}
|
|
@@ -32701,7 +33622,7 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32701
33622
|
if (sub === "user-prompt-submit") {
|
|
32702
33623
|
let reply = "";
|
|
32703
33624
|
try {
|
|
32704
|
-
reply = await runUserPromptSubmit({ input: await readStdin2() });
|
|
33625
|
+
reply = promptReplyFor(await runUserPromptSubmit({ input: await readStdin2(), agent: profile.id }), profile.id);
|
|
32705
33626
|
} catch {
|
|
32706
33627
|
reply = "";
|
|
32707
33628
|
}
|
|
@@ -32724,17 +33645,20 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32724
33645
|
process.exit(1);
|
|
32725
33646
|
return;
|
|
32726
33647
|
}
|
|
32727
|
-
|
|
33648
|
+
unknownCommand(cmd ?? "");
|
|
33649
|
+
}
|
|
33650
|
+
function unknownCommand(name = args.join(" ")) {
|
|
33651
|
+
process.stderr.write(`halfcycle-runner: unknown command "${name}"
|
|
32728
33652
|
Usage: halfcycle-runner ci
|
|
32729
|
-
halfcycle-runner hook post-tool-use
|
|
32730
|
-
halfcycle-runner hook pre-tool-use
|
|
32731
|
-
halfcycle-runner hook stop
|
|
32732
|
-
halfcycle-runner hook subagent-stop
|
|
32733
|
-
halfcycle-runner hook user-prompt-submit
|
|
32734
|
-
halfcycle-runner hook user-prompt-findings
|
|
32735
|
-
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>]
|
|
32736
33660
|
halfcycle-runner check-connection
|
|
32737
|
-
halfcycle-runner mcp-headers
|
|
33661
|
+
halfcycle-runner mcp-headers [--url <address>]
|
|
32738
33662
|
halfcycle-runner observe
|
|
32739
33663
|
halfcycle-runner observe --change <sortingId>
|
|
32740
33664
|
halfcycle-runner time
|
|
@@ -32748,16 +33672,20 @@ Commit the fixture; the next guard evaluation of this endpoint will pass.
|
|
|
32748
33672
|
process.exit(1);
|
|
32749
33673
|
}
|
|
32750
33674
|
function readStdin3() {
|
|
32751
|
-
return new Promise((
|
|
33675
|
+
return new Promise((resolve10, reject) => {
|
|
32752
33676
|
const chunks = [];
|
|
32753
33677
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
32754
|
-
process.stdin.on("end", () =>
|
|
33678
|
+
process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString("utf-8")));
|
|
32755
33679
|
process.stdin.on("error", reject);
|
|
32756
33680
|
});
|
|
32757
33681
|
}
|
|
32758
33682
|
main().catch((err) => {
|
|
32759
|
-
|
|
32760
|
-
|
|
32761
|
-
|
|
32762
|
-
|
|
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));
|
|
32763
33691
|
});
|