@fusengine/harness 0.1.75 → 0.1.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude/index.d.mts +1 -1
- package/dist/adapters/claude/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +1 -1
- package/dist/adapters/codex/index.mjs +1 -1
- package/dist/adapters/hermes/index.mjs +1 -1
- package/dist/{claude-BxQwhdZB.mjs → claude-51RSspJ1.mjs} +6 -13
- package/dist/cli/bin.mjs +2 -2
- package/dist/{handle-Cn6v5UpD.mjs → handle-BDlk2utY.mjs} +51 -51
- package/dist/{hermes-OVoZtUzT.mjs → hermes-rTmVdhUL.mjs} +1 -1
- package/dist/{index-a6kvHEPa.d.mts → index-DGxNjXW7.d.mts} +3 -11
- package/dist/runtime/index.d.mts +15 -6
- package/dist/runtime/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as denyResponse, c as informResponse, d as toClaudeResponse, i as contextResponse, l as readClaudeInput, n as attachSystemMessage, o as fileSizeGuard, r as blockResponse, s as guard, t as ClaudeHookInput, u as systemMessage } from "../../index-
|
|
1
|
+
import { a as denyResponse, c as informResponse, d as toClaudeResponse, i as contextResponse, l as readClaudeInput, n as attachSystemMessage, o as fileSizeGuard, r as blockResponse, s as guard, t as ClaudeHookInput, u as systemMessage } from "../../index-DGxNjXW7.mjs";
|
|
2
2
|
export { ClaudeHookInput, attachSystemMessage, blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, informResponse, readClaudeInput, systemMessage, toClaudeResponse };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as fileSizeGuard, c as readClaudeInput, i as denyResponse, l as systemMessage, n as blockResponse, o as guard, r as contextResponse, s as informResponse, t as attachSystemMessage, u as toClaudeResponse } from "../../claude-
|
|
1
|
+
import { a as fileSizeGuard, c as readClaudeInput, i as denyResponse, l as systemMessage, n as blockResponse, o as guard, r as contextResponse, s as informResponse, t as attachSystemMessage, u as toClaudeResponse } from "../../claude-51RSspJ1.mjs";
|
|
2
2
|
export { attachSystemMessage, blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, informResponse, readClaudeInput, systemMessage, toClaudeResponse };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as Prompt } from "../../types-DVbIl9md.mjs";
|
|
2
|
-
import { a as denyResponse, c as informResponse, i as contextResponse, l as readClaudeInput, t as ClaudeHookInput } from "../../index-
|
|
2
|
+
import { a as denyResponse, c as informResponse, i as contextResponse, l as readClaudeInput, t as ClaudeHookInput } from "../../index-DGxNjXW7.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/codex/index.d.ts
|
|
5
5
|
/**
|
|
@@ -2,7 +2,7 @@ import { t as evaluate } from "../../evaluate-ClfbCDiY.mjs";
|
|
|
2
2
|
import { f as countLines } from "../../home-state-BXf38Zi1.mjs";
|
|
3
3
|
import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
|
|
4
4
|
import { n as parseApplyPatch, t as isBypassPermissions } from "../../permission-mode-BN3MNgbm.mjs";
|
|
5
|
-
import { c as readClaudeInput, i as denyResponse, r as contextResponse, s as informResponse, v as commandToString } from "../../claude-
|
|
5
|
+
import { c as readClaudeInput, i as denyResponse, r as contextResponse, s as informResponse, v as commandToString } from "../../claude-51RSspJ1.mjs";
|
|
6
6
|
//#region src/adapters/codex/index.ts
|
|
7
7
|
/**
|
|
8
8
|
* OpenAI Codex CLI adapter (hook-mode). Codex's `PreToolUse` hook (since 2026)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as readHermesInput, r as toHermesResponse, t as guard } from "../../hermes-
|
|
1
|
+
import { n as readHermesInput, r as toHermesResponse, t as guard } from "../../hermes-rTmVdhUL.mjs";
|
|
2
2
|
export { guard, readHermesInput, toHermesResponse };
|
|
@@ -178,23 +178,19 @@ function informResponse(event, notice, context) {
|
|
|
178
178
|
}
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
|
-
/** Attach a
|
|
181
|
+
/** Attach a `systemMessage`, MERGING it with any already present (`\n`-joined) instead of overwriting — several producers may each call this on the same stdout in one hook invocation. */
|
|
182
182
|
function attachSystemMessage(stdout, notice) {
|
|
183
183
|
try {
|
|
184
|
+
const parsed = JSON.parse(stdout), prev = typeof parsed.systemMessage === "string" ? parsed.systemMessage : "";
|
|
184
185
|
return JSON.stringify({
|
|
185
|
-
...
|
|
186
|
-
systemMessage: notice
|
|
186
|
+
...parsed,
|
|
187
|
+
systemMessage: prev ? `${prev}\n${notice}` : notice
|
|
187
188
|
});
|
|
188
189
|
} catch {
|
|
189
190
|
return systemMessage(notice);
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Render a portable {@link Prompt} as a Claude Code hook response:
|
|
194
|
-
* `block` → `permissionDecision: deny`, `ask` → `permissionDecision: ask`
|
|
195
|
-
* (interactive confirm), `inform` → `additionalContext` — plus the user-visible
|
|
196
|
-
* `systemMessage` channel when the prompt carries a `userMessage`.
|
|
197
|
-
*/
|
|
193
|
+
/** Render a portable {@link Prompt} as a Claude Code hook response: `block` → deny, `ask` → interactive `permissionDecision`, `inform` → `additionalContext` (+ `systemMessage` when `userMessage` is set). */
|
|
198
194
|
function toClaudeResponse(event, prompt) {
|
|
199
195
|
const reason = formatPrompt(prompt);
|
|
200
196
|
if (prompt.kind === "block") return denyResponse(event, reason);
|
|
@@ -206,10 +202,7 @@ function toClaudeResponse(event, prompt) {
|
|
|
206
202
|
if (prompt.userMessage) return informResponse(event, prompt.userMessage, prompt.reason ? reason : "");
|
|
207
203
|
return contextResponse(event, reason);
|
|
208
204
|
}
|
|
209
|
-
/**
|
|
210
|
-
* Run the bundled policy over a Claude payload and return the native response
|
|
211
|
-
* string (deny/ask/additionalContext), or null to allow.
|
|
212
|
-
*/
|
|
205
|
+
/** Run the bundled policy over a Claude payload and return the native response string (deny/ask/additionalContext), or null to allow. */
|
|
213
206
|
function guard(input) {
|
|
214
207
|
const result = evaluate({
|
|
215
208
|
tool: input.tool_name ?? "Write",
|
package/dist/cli/bin.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
|
|
|
4
4
|
import { t as claudeHome } from "../home-state-BXf38Zi1.mjs";
|
|
5
5
|
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-3tyTW9Ud.mjs";
|
|
6
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
7
|
-
import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-
|
|
8
|
-
import { p as readStdin$1 } from "../claude-
|
|
7
|
+
import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-BDlk2utY.mjs";
|
|
8
|
+
import { p as readStdin$1 } from "../claude-51RSspJ1.mjs";
|
|
9
9
|
import { delimiter, join } from "node:path";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
@@ -14,8 +14,8 @@ import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cac
|
|
|
14
14
|
import { t as loadRefs } from "./loader-AGz4nK7d.mjs";
|
|
15
15
|
import { a as writeLastNonce, c as recordAgent, d as recordRefRead, f as recordTarget, h as apexAuthorizationGate, i as verifyTrack, l as recordBrainstormRequired, m as trivialCount, n as saveTrack, o as agentsFresh, p as recordTrivialEdit, r as signTrack, s as emptyTrack, t as loadTrack, u as recordDoc } from "./store-Bcn08y9m.mjs";
|
|
16
16
|
import { n as parseApplyPatch, t as isBypassPermissions } from "./permission-mode-BN3MNgbm.mjs";
|
|
17
|
-
import { _ as writeText, d as collectFiles, f as pathExists, g as spawnCapture, h as sleep, i as denyResponse, l as systemMessage, m as readText, n as blockResponse, r as contextResponse, s as informResponse, t as attachSystemMessage, v as commandToString } from "./claude-
|
|
18
|
-
import { r as toHermesResponse } from "./hermes-
|
|
17
|
+
import { _ as writeText, d as collectFiles, f as pathExists, g as spawnCapture, h as sleep, i as denyResponse, l as systemMessage, m as readText, n as blockResponse, r as contextResponse, s as informResponse, t as attachSystemMessage, v as commandToString } from "./claude-51RSspJ1.mjs";
|
|
18
|
+
import { r as toHermesResponse } from "./hermes-rTmVdhUL.mjs";
|
|
19
19
|
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
@@ -597,7 +597,7 @@ function promptSubmitContext(prompt, cwd) {
|
|
|
597
597
|
const ctx = buildClaudeMdContext(prompt, cwd);
|
|
598
598
|
if (!ctx) return "";
|
|
599
599
|
if (!oncePerWindow(claudeMdKey(prompt, ctx), 3e3)) return "";
|
|
600
|
-
return contextResponse("UserPromptSubmit", ctx);
|
|
600
|
+
return attachSystemMessage(contextResponse("UserPromptSubmit", ctx), "CLAUDE.md injected");
|
|
601
601
|
}
|
|
602
602
|
/**
|
|
603
603
|
* PreToolUse Task context injection: render the APEX sub-agent context as a
|
|
@@ -770,7 +770,7 @@ function sessionStartCore(cwd, home = homedir(), now = Date.now()) {
|
|
|
770
770
|
const dev = devContext(cwd);
|
|
771
771
|
runSessionStartCleanups(home, now);
|
|
772
772
|
const ctx = [md, dev].filter(Boolean).join("\n");
|
|
773
|
-
return ctx ? contextResponse("SessionStart", ctx) : "";
|
|
773
|
+
return ctx ? attachSystemMessage(contextResponse("SessionStart", ctx), "CLAUDE.md injected") : "";
|
|
774
774
|
}
|
|
775
775
|
//#endregion
|
|
776
776
|
//#region src/runtime/lifecycle/inject-rules.ts
|
|
@@ -801,7 +801,7 @@ function readRules(rulesDir) {
|
|
|
801
801
|
*/
|
|
802
802
|
function injectRules(pluginRoot, event) {
|
|
803
803
|
const content = readRules(join(pluginRoot, "rules"));
|
|
804
|
-
return content ? contextResponse(event, content) : "";
|
|
804
|
+
return content ? attachSystemMessage(contextResponse(event, content), "rules 00-08 injected") : "";
|
|
805
805
|
}
|
|
806
806
|
//#endregion
|
|
807
807
|
//#region src/runtime/lifecycle/solid-detect.ts
|
|
@@ -1393,18 +1393,20 @@ function trackAgentMemory(data, home = homedir(), now = Date.now()) {
|
|
|
1393
1393
|
//#region src/runtime/lifecycle/teammate-idle.ts
|
|
1394
1394
|
/**
|
|
1395
1395
|
* Handle TeammateIdle: when the teammate's session-changes file shows code was
|
|
1396
|
-
* modified, suggest sniper validation as
|
|
1397
|
-
* `
|
|
1396
|
+
* modified, suggest sniper validation as plain text — TeammateIdle has no
|
|
1397
|
+
* `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
|
|
1398
|
+
* ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
|
|
1399
|
+
* the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
|
|
1398
1400
|
* @param data - The raw hook payload.
|
|
1399
1401
|
* @param home - Home dir (defaults to `~`).
|
|
1400
|
-
* @returns The
|
|
1402
|
+
* @returns The plain notice text, or "" when no code file was modified.
|
|
1401
1403
|
*/
|
|
1402
1404
|
function validateTeammateOutput(data, home = homedir()) {
|
|
1403
1405
|
const teammate = String(data.teammate_name ?? "unknown");
|
|
1404
1406
|
const changes = loadSessionState(sanitizeSessionId(data.session_id) ?? "unknown", home).changes;
|
|
1405
1407
|
const count = changes?.cumulativeCodeFiles ?? 0;
|
|
1406
|
-
if (count
|
|
1407
|
-
return ""
|
|
1408
|
+
if (count === 0) return "";
|
|
1409
|
+
return `Teammate '${teammate}' going idle after modifying ${count} code file(s): ${(changes?.modifiedFiles ?? []).slice(0, 5).join(", ")}. Consider running sniper validation.`;
|
|
1408
1410
|
}
|
|
1409
1411
|
//#endregion
|
|
1410
1412
|
//#region src/runtime/lifecycle/tool-failure.ts
|
|
@@ -2210,19 +2212,12 @@ const IDLE_DEDUP_MS = 3e4;
|
|
|
2210
2212
|
function missingDeliverables(sessionId, home) {
|
|
2211
2213
|
return (loadSessionState(sessionId, home).changes?.modifiedFiles ?? []).filter((f) => typeof f === "string" && f !== "" && !existsSync(f));
|
|
2212
2214
|
}
|
|
2213
|
-
/** Pull the `additionalContext` body out of a `contextResponse` stdout ("" when empty/unparseable). */
|
|
2214
|
-
function bodyOf(stdout) {
|
|
2215
|
-
if (!stdout) return "";
|
|
2216
|
-
try {
|
|
2217
|
-
return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
|
|
2218
|
-
} catch {
|
|
2219
|
-
return "";
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
2215
|
/**
|
|
2223
2216
|
* Handle TeammateIdle: merge the existing sniper suggestion with a missing-
|
|
2224
|
-
* deliverable warning (deduped) into one `
|
|
2225
|
-
* neither fires.
|
|
2217
|
+
* deliverable warning (deduped) into one advisory `systemMessage`, or "" when
|
|
2218
|
+
* neither fires. TeammateIdle rejects `hookSpecificOutput` (no `additionalContext`
|
|
2219
|
+
* channel) — `systemMessage` is the only user-visible, non-blocking channel it
|
|
2220
|
+
* accepts.
|
|
2226
2221
|
* @param data - The raw TeammateIdle payload (`teammate_name`, `session_id`).
|
|
2227
2222
|
* @param cwd - Project root (state dir for the dedup sidecar).
|
|
2228
2223
|
* @param home - Home dir (defaults to `~`).
|
|
@@ -2230,7 +2225,7 @@ function bodyOf(stdout) {
|
|
|
2230
2225
|
* @returns The native hook stdout, or "".
|
|
2231
2226
|
*/
|
|
2232
2227
|
function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
|
|
2233
|
-
const sniper =
|
|
2228
|
+
const sniper = validateTeammateOutput(data, home);
|
|
2234
2229
|
const sessionId = sanitizeSessionId(data.session_id);
|
|
2235
2230
|
const teammate = String(data.teammate_name ?? data.team_name ?? "unknown");
|
|
2236
2231
|
let notice = "";
|
|
@@ -2244,7 +2239,7 @@ function teammateIdleContext(data, cwd, home = homedir(), now = Date.now()) {
|
|
|
2244
2239
|
const merged = [sniper, notice].filter(Boolean).join("\n\n");
|
|
2245
2240
|
if (!merged) return "";
|
|
2246
2241
|
notify("human");
|
|
2247
|
-
return
|
|
2242
|
+
return systemMessage(merged);
|
|
2248
2243
|
}
|
|
2249
2244
|
//#endregion
|
|
2250
2245
|
//#region src/policy/lessons/trigger-index.ts
|
|
@@ -3148,17 +3143,10 @@ function collectViolations(files, max) {
|
|
|
3148
3143
|
return violations;
|
|
3149
3144
|
}
|
|
3150
3145
|
/**
|
|
3151
|
-
* Handle TaskCompleted (ports `task-completed/validate-task-solid.py
|
|
3152
|
-
* receipt gate)
|
|
3153
|
-
*
|
|
3154
|
-
* that has no fresh passing tsc/test receipt. Also reused verbatim from `Stop`
|
|
3155
|
-
* for Codex's core scope ({@link stopCore}): Codex never emits `TaskCompleted`
|
|
3156
|
-
* (codex-plugins/docs/reference/hooks.md) so this is its only completion check;
|
|
3157
|
-
* `event` lets that caller stamp the real hook name instead of a false one.
|
|
3146
|
+
* Handle TaskCompleted (ports `task-completed/validate-task-solid.py` + the
|
|
3147
|
+
* receipt gate); also reused verbatim for Codex's `Stop` via {@link stopCore}
|
|
3148
|
+
* (Codex never emits `TaskCompleted`) — `event` stamps the real hook name.
|
|
3158
3149
|
* @param payload - The TaskCompleted/Stop payload (`task_id`, `task_subject`, `session_id`).
|
|
3159
|
-
* @param home - Home dir (defaults to `~`).
|
|
3160
|
-
* @param now - Clock (defaults to `Date.now()`).
|
|
3161
|
-
* @param stateDir - Track base dir (defaults to the cwd-derived state dir; matches `handleHook`).
|
|
3162
3150
|
* @param event - The hook event name to stamp on the SOLID-violation response (default `TaskCompleted`).
|
|
3163
3151
|
* @returns The native hook stdout, or `""` when the session is clean.
|
|
3164
3152
|
*/
|
|
@@ -3171,7 +3159,8 @@ function validateTaskSolid(payload, home = homedir(), now = Date.now(), stateDir
|
|
|
3171
3159
|
const violations = collectViolations(files, max);
|
|
3172
3160
|
if (violations.length === 0) return receiptGate(sid, files, now, stateDir) ?? "";
|
|
3173
3161
|
const taskId = String(payload.task_id ?? "");
|
|
3174
|
-
|
|
3162
|
+
const msg = `SOLID VIOLATION in task '${String(payload.task_subject ?? "")}' (${taskId}): ${violations.length} file(s) exceed ${max} lines: ` + violations.slice(0, 5).join("; ");
|
|
3163
|
+
return event === "TaskCompleted" ? systemMessage(msg) : contextResponse(event, msg);
|
|
3175
3164
|
}
|
|
3176
3165
|
//#endregion
|
|
3177
3166
|
//#region src/runtime/lifecycle/cartographer/fs-util.ts
|
|
@@ -4003,13 +3992,13 @@ function injectMemory(cwd, event, now) {
|
|
|
4003
3992
|
if (!content) return "";
|
|
4004
3993
|
const { content: curated, archive, report } = curateLessons(content, now, root);
|
|
4005
3994
|
if (curated !== content) content = persistCuration(file, root, curated, archive, content);
|
|
4006
|
-
const
|
|
4007
|
-
return report ? attachSystemMessage(
|
|
3995
|
+
const withNotice = attachSystemMessage(contextResponse(event, `Project lessons — never reproduce these:\n${capFragment("lessons", compressInjection(content))}\nYou may append OR refine/merge/dedupe bullets in MEMORY/LESSON.md — keep it terse.`), "lessons injected");
|
|
3996
|
+
return report ? attachSystemMessage(withNotice, `LESSON.md curation:\n${report}`) : withNotice;
|
|
4008
3997
|
}
|
|
4009
3998
|
/**
|
|
4010
3999
|
* Route a fuse-lessons event to its handler. Returns the native stdout for
|
|
4011
|
-
* context-injecting events (SessionStart/SubagentStart/
|
|
4012
|
-
* side-effect-only PostToolUse mark.
|
|
4000
|
+
* context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
|
|
4001
|
+
* MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
|
|
4013
4002
|
* @param event - The raw hook event name.
|
|
4014
4003
|
* @param payload - The raw hook payload.
|
|
4015
4004
|
* @param cwd - Project root for memory injection.
|
|
@@ -4019,7 +4008,8 @@ function injectMemory(cwd, event, now) {
|
|
|
4019
4008
|
function dispatchLessons(event, payload, cwd, now) {
|
|
4020
4009
|
switch (event) {
|
|
4021
4010
|
case "SessionStart":
|
|
4022
|
-
case "SubagentStart":
|
|
4011
|
+
case "SubagentStart":
|
|
4012
|
+
case "UserPromptSubmit": return injectMemory(cwd, event, now);
|
|
4023
4013
|
case "Stop": return remindWrite(payload, now);
|
|
4024
4014
|
case "PostToolUse":
|
|
4025
4015
|
markWrite(payload, now);
|
|
@@ -5332,7 +5322,10 @@ function sessionStart(input) {
|
|
|
5332
5322
|
function dispatchLifecycle(input) {
|
|
5333
5323
|
switch (input.event) {
|
|
5334
5324
|
case "SessionStart": return sessionStart(input);
|
|
5335
|
-
case "UserPromptSubmit":
|
|
5325
|
+
case "UserPromptSubmit":
|
|
5326
|
+
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
|
|
5327
|
+
if (input.scope === "lessons") return dispatchLessons("UserPromptSubmit", input.payload, input.cwd, input.now);
|
|
5328
|
+
return null;
|
|
5336
5329
|
case "SubagentStart":
|
|
5337
5330
|
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
|
|
5338
5331
|
if (input.scope === "aipilot") return "";
|
|
@@ -7192,18 +7185,25 @@ async function recordActivity(file, activity) {
|
|
|
7192
7185
|
* - cline: NO user-visible channel (`cancel`/`contextModification`/
|
|
7193
7186
|
* `errorMessage`-on-cancel only) — the notice is dropped silently (a pure
|
|
7194
7187
|
* notice returns "" = plain allow).
|
|
7195
|
-
|
|
7196
|
-
|
|
7188
|
+
* @param id - Harness id.
|
|
7189
|
+
* @param prompt - The portable prompt to render.
|
|
7190
|
+
* @param event - The firing hook event name for the claude-code/codex branch
|
|
7191
|
+
* (defaults to `"PreToolUse"`, the only phase every existing caller but
|
|
7192
|
+
* `handle-post.ts` renders) — a POST-phase caller MUST pass `"PostToolUse"`
|
|
7193
|
+
* (or its real raw event name) so `hookEventName` matches the event that
|
|
7194
|
+
* actually fired instead of a hardcoded, wrong `"PreToolUse"`.
|
|
7195
|
+
*/
|
|
7196
|
+
function respond(id, prompt, event = "PreToolUse") {
|
|
7197
7197
|
const message = formatPrompt(prompt);
|
|
7198
7198
|
const { kind, userMessage, reason } = prompt;
|
|
7199
7199
|
switch (id) {
|
|
7200
7200
|
case "claude-code":
|
|
7201
7201
|
case "codex":
|
|
7202
|
-
if (kind === "block") return denyResponse(
|
|
7203
|
-
if (kind === "inform") return userMessage ? informResponse(
|
|
7204
|
-
if (id === "codex") return denyResponse(
|
|
7202
|
+
if (kind === "block") return denyResponse(event, message);
|
|
7203
|
+
if (kind === "inform") return userMessage ? informResponse(event, userMessage, reason ? message : "") : contextResponse(event, message);
|
|
7204
|
+
if (id === "codex") return denyResponse(event, `[downgraded from ask — Codex has no interactive approval]\n${message}`);
|
|
7205
7205
|
return JSON.stringify({ hookSpecificOutput: {
|
|
7206
|
-
hookEventName:
|
|
7206
|
+
hookEventName: event,
|
|
7207
7207
|
permissionDecision: "ask",
|
|
7208
7208
|
permissionDecisionReason: message
|
|
7209
7209
|
} });
|
|
@@ -7616,9 +7616,9 @@ const KNOWN_DOMAINS = [
|
|
|
7616
7616
|
/** Gate writing design-system.md: requires phase >= 2 and the per-mode screenshot quota. */
|
|
7617
7617
|
function designSystemWriteGate(filePath, state) {
|
|
7618
7618
|
if (!filePath.endsWith("design-system.md")) return null;
|
|
7619
|
-
if (state.currentPhase < 2) return deny(`BLOCKED: Cannot write design-system.md at phase ${state.currentPhase}. RECOVERY: 1) Read identity templates from skills/design-system/ 2) Read design-inspiration.md 3) Browse and screenshot
|
|
7619
|
+
if (state.currentPhase < 2) return deny(`BLOCKED: Cannot write design-system.md at phase ${state.currentPhase}. RECOVERY: 1) Read identity templates from skills/design-system/ 2) Read design-inspiration.md 3) Browse sites and take a screenshot with mcp__fuse-browser__browser_screenshot on a LIVE session (note: browser_shots_batch/browser_site_shots do NOT advance the phase) 4) Then write design-system.md`);
|
|
7620
7620
|
const needed = MIN_SCREENSHOTS[state.mode];
|
|
7621
|
-
if (state.screenshotsCount < needed) return deny(`BLOCKED: ${state.screenshotsCount}/${needed} screenshots for mode '${state.mode}'. RECOVERY: 1) Take ${needed - state.screenshotsCount} more
|
|
7621
|
+
if (state.screenshotsCount < needed) return deny(`BLOCKED: ${state.screenshotsCount}/${needed} screenshots for mode '${state.mode}'. RECOVERY: 1) Take ${needed - state.screenshotsCount} more screenshots with mcp__fuse-browser__browser_screenshot (browser_shots_batch/browser_site_shots do NOT count) 2) Use browser_open + browser_navigate + browser_screenshot fullPage:true 3) Then write design-system.md`);
|
|
7622
7622
|
return null;
|
|
7623
7623
|
}
|
|
7624
7624
|
/** Gate Gemini create_frontend: requires phase >= 3 and a validated design system. */
|
|
@@ -8465,7 +8465,7 @@ async function handlePost(ctx) {
|
|
|
8465
8465
|
stdout: respond(id, userMessage ? {
|
|
8466
8466
|
...designWarn,
|
|
8467
8467
|
userMessage
|
|
8468
|
-
} : designWarn),
|
|
8468
|
+
} : designWarn, "PostToolUse"),
|
|
8469
8469
|
exit: 0
|
|
8470
8470
|
};
|
|
8471
8471
|
if (!userMessage) return {
|
|
@@ -8482,7 +8482,7 @@ async function handlePost(ctx) {
|
|
|
8482
8482
|
userMessage
|
|
8483
8483
|
};
|
|
8484
8484
|
if (!extra) return {
|
|
8485
|
-
stdout: respond(id, withUserMessage),
|
|
8485
|
+
stdout: respond(id, withUserMessage, "PostToolUse"),
|
|
8486
8486
|
exit: 0
|
|
8487
8487
|
};
|
|
8488
8488
|
if (id === "claude-code" || id === "codex") return {
|
|
@@ -8490,7 +8490,7 @@ async function handlePost(ctx) {
|
|
|
8490
8490
|
exit: 0
|
|
8491
8491
|
};
|
|
8492
8492
|
return {
|
|
8493
|
-
stdout: respond(id, withUserMessage) || extra,
|
|
8493
|
+
stdout: respond(id, withUserMessage, "PostToolUse") || extra,
|
|
8494
8494
|
exit: 0
|
|
8495
8495
|
};
|
|
8496
8496
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as evaluate } from "./evaluate-ClfbCDiY.mjs";
|
|
2
2
|
import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
|
|
3
|
-
import { c as readClaudeInput, v as commandToString } from "./claude-
|
|
3
|
+
import { c as readClaudeInput, v as commandToString } from "./claude-51RSspJ1.mjs";
|
|
4
4
|
//#region src/adapters/hermes/index.ts
|
|
5
5
|
/**
|
|
6
6
|
* Hermes Agent adapter (hook-mode). Nous Research's hermes-agent (2026) pipes a
|
|
@@ -31,19 +31,11 @@ declare function contextResponse(event: string, text: string): string;
|
|
|
31
31
|
declare function systemMessage(text: string): string;
|
|
32
32
|
/** An `inform` response with a user-visible notice (Python `allow_pass`/`post_pass` parity): a pure pass reuses {@link systemMessage} alone; with agent-facing `context` both channels merge in one JSON. */
|
|
33
33
|
declare function informResponse(event: string, notice: string, context: string): string;
|
|
34
|
-
/** Attach a
|
|
34
|
+
/** Attach a `systemMessage`, MERGING it with any already present (`\n`-joined) instead of overwriting — several producers may each call this on the same stdout in one hook invocation. */
|
|
35
35
|
declare function attachSystemMessage(stdout: string, notice: string): string;
|
|
36
|
-
/**
|
|
37
|
-
* Render a portable {@link Prompt} as a Claude Code hook response:
|
|
38
|
-
* `block` → `permissionDecision: deny`, `ask` → `permissionDecision: ask`
|
|
39
|
-
* (interactive confirm), `inform` → `additionalContext` — plus the user-visible
|
|
40
|
-
* `systemMessage` channel when the prompt carries a `userMessage`.
|
|
41
|
-
*/
|
|
36
|
+
/** Render a portable {@link Prompt} as a Claude Code hook response: `block` → deny, `ask` → interactive `permissionDecision`, `inform` → `additionalContext` (+ `systemMessage` when `userMessage` is set). */
|
|
42
37
|
declare function toClaudeResponse(event: string, prompt: Prompt): string;
|
|
43
|
-
/**
|
|
44
|
-
* Run the bundled policy over a Claude payload and return the native response
|
|
45
|
-
* string (deny/ask/additionalContext), or null to allow.
|
|
46
|
-
*/
|
|
38
|
+
/** Run the bundled policy over a Claude payload and return the native response string (deny/ask/additionalContext), or null to allow. */
|
|
47
39
|
declare function guard(input: ClaudeHookInput): string | null;
|
|
48
40
|
/** @deprecated use {@link guard}. Kept for back-compat. */
|
|
49
41
|
declare const fileSizeGuard: typeof guard;
|
package/dist/runtime/index.d.mts
CHANGED
|
@@ -219,8 +219,15 @@ declare function normalizeEvent(id: string, payload: Record<string, unknown>): N
|
|
|
219
219
|
* - cline: NO user-visible channel (`cancel`/`contextModification`/
|
|
220
220
|
* `errorMessage`-on-cancel only) — the notice is dropped silently (a pure
|
|
221
221
|
* notice returns "" = plain allow).
|
|
222
|
+
* @param id - Harness id.
|
|
223
|
+
* @param prompt - The portable prompt to render.
|
|
224
|
+
* @param event - The firing hook event name for the claude-code/codex branch
|
|
225
|
+
* (defaults to `"PreToolUse"`, the only phase every existing caller but
|
|
226
|
+
* `handle-post.ts` renders) — a POST-phase caller MUST pass `"PostToolUse"`
|
|
227
|
+
* (or its real raw event name) so `hookEventName` matches the event that
|
|
228
|
+
* actually fired instead of a hardcoded, wrong `"PreToolUse"`.
|
|
222
229
|
*/
|
|
223
|
-
declare function respond(id: string, prompt: Prompt): string;
|
|
230
|
+
declare function respond(id: string, prompt: Prompt, event?: string): string;
|
|
224
231
|
//#endregion
|
|
225
232
|
//#region src/runtime/mcp-key.d.ts
|
|
226
233
|
/** Cached-MCP freshness (ms) from `FUSE_MCP_TTL_SEC` (default 48h). */
|
|
@@ -442,11 +449,13 @@ declare function trackAgentMemory(data: Record<string, unknown>, home?: string,
|
|
|
442
449
|
//#region src/runtime/lifecycle/teammate-idle.d.ts
|
|
443
450
|
/**
|
|
444
451
|
* Handle TeammateIdle: when the teammate's session-changes file shows code was
|
|
445
|
-
* modified, suggest sniper validation as
|
|
446
|
-
* `
|
|
452
|
+
* modified, suggest sniper validation as plain text — TeammateIdle has no
|
|
453
|
+
* `additionalContext` channel (rejects `hookSpecificOutput`), so the caller
|
|
454
|
+
* ({@link module:teammate-idle-check.teammateIdleContext}) rides this text on
|
|
455
|
+
* the `systemMessage` channel instead. Ports `teammate-idle/validate-teammate-output.py`.
|
|
447
456
|
* @param data - The raw hook payload.
|
|
448
457
|
* @param home - Home dir (defaults to `~`).
|
|
449
|
-
* @returns The
|
|
458
|
+
* @returns The plain notice text, or "" when no code file was modified.
|
|
450
459
|
*/
|
|
451
460
|
declare function validateTeammateOutput(data: Record<string, unknown>, home?: string): string;
|
|
452
461
|
//#endregion
|
|
@@ -719,8 +728,8 @@ declare function saveSecurityState(state: Record<string, unknown>, now?: number,
|
|
|
719
728
|
//#region src/runtime/lifecycle/lessons/dispatch.d.ts
|
|
720
729
|
/**
|
|
721
730
|
* Route a fuse-lessons event to its handler. Returns the native stdout for
|
|
722
|
-
* context-injecting events (SessionStart/SubagentStart/
|
|
723
|
-
* side-effect-only PostToolUse mark.
|
|
731
|
+
* context-injecting events (SessionStart/SubagentStart/UserPromptSubmit inject
|
|
732
|
+
* MEMORY/LESSON.md, Stop reminds) or "" for the side-effect-only PostToolUse mark.
|
|
724
733
|
* @param event - The raw hook event name.
|
|
725
734
|
* @param payload - The raw hook payload.
|
|
726
735
|
* @param cwd - Project root for memory injection.
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
|
|
2
2
|
import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-BXf38Zi1.mjs";
|
|
3
|
-
import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-
|
|
3
|
+
import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-BDlk2utY.mjs";
|
|
4
4
|
//#region src/runtime/storage.ts
|
|
5
5
|
/**
|
|
6
6
|
* The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
4
4
|
"description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, per-harness adapters (Claude/Cursor/Cline/Gemini) and a cli-mode harness-check binary. Bun-native, with a built dist for Node + bundlers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|