@fusengine/harness 0.1.66 → 0.1.67

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/cli/bin.mjs CHANGED
@@ -4,7 +4,7 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
4
4
  import { t as claudeHome } from "../home-state-D0RLWP8J.mjs";
5
5
  import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-BfJytXnW.mjs";
6
6
  import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
7
- import { J as runningVersion, Lt as todayUtc, Y as versionBanner, q as runDoctor, t as handleHook } from "../handle-BBlvYhek.mjs";
7
+ import { J as runningVersion, Lt as todayUtc, Y as versionBanner, q as runDoctor, t as handleHook } from "../handle-CU5UdAei.mjs";
8
8
  import { p as readStdin$1 } from "../claude-Cu6rPxUZ.mjs";
9
9
  import { delimiter, join } from "node:path";
10
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
@@ -7885,6 +7885,92 @@ function activityFor(event) {
7885
7885
  return out;
7886
7886
  }
7887
7887
  //#endregion
7888
+ //#region src/freshness/codex-spawn-evidence.ts
7889
+ /**
7890
+ * Codex multi_agent_v2 `spawn_agent` -> session evidence bridge. Codex's custom
7891
+ * agents (fusengine multi_agent_v2, e.g. sniper/explore-codebase) are launched
7892
+ * via a `spawn_agent` TOOL CALL inside the SAME session -- Codex has no
7893
+ * SubagentStart/SubagentStop lifecycle for them, so the existing Claude-side
7894
+ * evidence paths (agent-evidence-record.ts `classifyAgentEvidence`, the
7895
+ * SubagentStop harvest) never observe them; the harness only ever saw a
7896
+ * generic tool call. This module credits the SAME session track
7897
+ * ({@link recordAgent}, read back by `agent-evidence-record.ts`
7898
+ * `agentsFreshInTrack`) straight from that PostToolUse `spawn_agent` call.
7899
+ *
7900
+ * SOURCE (openai/codex@44918ea1, tag rust-v0.144.1 -- verified upstream,
7901
+ * not re-checked here): the hook `tool_name` is `"spawn_agent"` normally, or
7902
+ * the SEPARATOR-LESS namespace-prefixed `{namespace}spawn_agent` when the
7903
+ * `namespace_tools` capability provider is active (e.g.
7904
+ * `"fusengine_agentsspawn_agent"`, `"collaborationspawn_agent"`). Detection is
7905
+ * `tool_name === "spawn_agent" || tool_name.endsWith("spawn_agent")` -- a tool
7906
+ * that merely CONTAINS the substring without being an exact/suffix match
7907
+ * (`"spawn_agentX"`, `"myspawn_agent_tool"`) is correctly rejected.
7908
+ * `tool_input.agent_type` carries the spawned agent's type only when Codex's
7909
+ * `hide_spawn_agent_metadata=false`; treat it as always-optional.
7910
+ *
7911
+ * INVARIANT (multi-harness parity): every entry point here is gated on
7912
+ * `id === "codex"` FIRST -- claude-code/cursor/cline/gemini-cli/hermes always
7913
+ * take the early return, touching neither the track nor disk. Byte-identical
7914
+ * elsewhere by construction.
7915
+ */
7916
+ /**
7917
+ * True when `tool` is Codex's `spawn_agent` primitive, bare or
7918
+ * namespace-prefixed (no separator). Rejects any tool that merely contains
7919
+ * the substring elsewhere in its name.
7920
+ * @param tool - Raw/normalized `tool_name` from the hook payload.
7921
+ */
7922
+ function isCodexSpawnAgentTool(tool) {
7923
+ return tool === "spawn_agent" || tool.endsWith("spawn_agent");
7924
+ }
7925
+ /**
7926
+ * Extract the spawned agent's type from a `spawn_agent` `tool_input`, or
7927
+ * `undefined` when absent/blank (`hide_spawn_agent_metadata=true`, or a
7928
+ * future Codex build omitting it) -- never throws.
7929
+ * @param input - Raw `tool_input` payload (harness `event.input`).
7930
+ */
7931
+ function codexSpawnAgentType(input) {
7932
+ const v = input?.agent_type;
7933
+ return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
7934
+ }
7935
+ /**
7936
+ * Pure classifier + folder: credit a Codex `spawn_agent` call into
7937
+ * `track.agents` under the SAME `subagent-<name>` convention the Claude-side
7938
+ * explore/research evidence uses -- so an `agent_type` that happens to match
7939
+ * a `REQUIRED_AGENTS` name (e.g. `"explore-codebase"`) is picked up by
7940
+ * `agentsFreshInTrack`'s substring match exactly like genuine Claude
7941
+ * Task/Agent evidence. Immutable: returns `track` UNCHANGED (same reference)
7942
+ * for any other harness `id`, any non-spawn tool, or a missing `agent_type`.
7943
+ * @param id - Harness id (only `"codex"` ever credits).
7944
+ * @param tool - Normalized event tool name (raw `tool_name`).
7945
+ * @param input - Raw `tool_input` payload.
7946
+ * @param track - The current session track.
7947
+ * @param ts - Epoch-ms timestamp of the tool call.
7948
+ * @returns The credited track, or `track` itself when nothing was credited.
7949
+ */
7950
+ function creditCodexSpawnAgent(id, tool, input, track, ts) {
7951
+ if (id !== "codex" || !isCodexSpawnAgentTool(tool)) return track;
7952
+ const agentType = codexSpawnAgentType(input);
7953
+ if (!agentType) return track;
7954
+ return recordAgent(track, `subagent-${agentType}`, ts, "sufficient");
7955
+ }
7956
+ /**
7957
+ * I/O wiring for {@link creditCodexSpawnAgent}: load the session track, fold
7958
+ * in the credit, and persist only when something actually changed (skips a
7959
+ * write for the strict no-op cases). Mirrors `agent-evidence-record.ts`
7960
+ * `recordAgentEvidence`'s async load/save shape.
7961
+ * @param file - Session track file path (same one the freshness gate reads).
7962
+ * @param id - Harness id (only `"codex"` ever credits).
7963
+ * @param tool - Normalized event tool name (raw `tool_name`).
7964
+ * @param input - Raw `tool_input` payload.
7965
+ * @param ts - Epoch-ms timestamp of the tool call.
7966
+ */
7967
+ async function recordCodexSpawnEvidence(file, id, tool, input, ts) {
7968
+ if (id !== "codex" || !isCodexSpawnAgentTool(tool)) return;
7969
+ const track = await loadTrack(file);
7970
+ const next = creditCodexSpawnAgent(id, tool, input, track, ts);
7971
+ if (next !== track) await saveTrack(file, next);
7972
+ }
7973
+ //#endregion
7888
7974
  //#region src/runtime/handle-post.ts
7889
7975
  /**
7890
7976
  * Run the PostToolUse pipeline: store the MCP response, emit a design warning,
@@ -7909,6 +7995,7 @@ async function handlePost(ctx) {
7909
7995
  for (const activity of activities) await recordActivity(file, activity);
7910
7996
  const evidence = classifyAgentEvidence(event.tool, event.input, response);
7911
7997
  if (evidence) await recordAgentEvidence(file, evidence, opts.now, typeof payload.agent_id === "string" ? payload.agent_id : void 0);
7998
+ await recordCodexSpawnEvidence(file, id, event.tool, event.input, opts.now);
7912
7999
  if (event.tool === "Bash" && event.command) {
7913
8000
  const r = payload.tool_result ?? response;
7914
8001
  const out = `${typeof r?.stdout === "string" ? r.stdout : ""}\n${typeof r?.stderr === "string" ? r.stderr : ""}`;
@@ -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-D0RLWP8J.mjs";
3
- import { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as isProject, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, F as dispatchLessons, Ft as securityStateDir, G as getFileDesc, H as loadEnriched, I as cartoSessionStart, It as securityStatePath, K as listChildren, L as generateEcosystemMap, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as lessonsStateFileFor, R as writePluginMap, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as mergeLines, V as writeTree, W as countFiles, X as lessonsArchiveFileFor, Z as lessonsFileFor, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as generateProjectMap } from "../handle-BBlvYhek.mjs";
3
+ import { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as isProject, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, F as dispatchLessons, Ft as securityStateDir, G as getFileDesc, H as loadEnriched, I as cartoSessionStart, It as securityStatePath, K as listChildren, L as generateEcosystemMap, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as lessonsStateFileFor, R as writePluginMap, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as mergeLines, V as writeTree, W as countFiles, X as lessonsArchiveFileFor, Z as lessonsFileFor, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as generateProjectMap } from "../handle-CU5UdAei.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.66",
3
+ "version": "0.1.67",
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",