@event4u/agent-config 9.31.0 → 9.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +51 -0
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +32 -1
- package/dist/agent-src/rules/delegation-policy.md +11 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
- package/dist/agent-src/templates/scripts/work_engine/state.ts +74 -0
- package/dist/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
- package/dist/cli-delegate/chunk-2WELHO3P.js +582 -0
- package/dist/cli-delegate/{chunk-2WCLVDWC.js → chunk-542DZR4P.js} +117 -147
- package/dist/cli-delegate/chunk-PZWRPRWP.js +38 -0
- package/dist/cli-delegate/cmd_conformance.js +81 -30
- package/dist/cli-delegate/cmd_doctor.js +2 -1
- package/dist/cli-delegate/cmd_handoff.js +166 -57
- package/dist/cli-delegate/cmd_session_recycle.js +17 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +3 -3
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1118 -690
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +79 -29
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +6 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +332 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +106 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +50 -15
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +49 -15
- package/src/agent-src/templates/scripts/work_engine/state.ts +74 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +6 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +25 -0
- package/src/scripts/_cli/handoff_generate.ts +36 -1
- package/src/scripts/_cli/handoff_sessions.ts +140 -9
- package/src/scripts/_lib/carrier_divergence.ts +98 -27
- package/src/scripts/_lib/envelope_grounding.ts +206 -0
- package/src/scripts/_lib/host_capability.ts +79 -0
- package/src/scripts/_lib/session_eol.ts +85 -0
- package/src/scripts/_lib/subagent_capsule.ts +256 -1
- package/src/scripts/check_secret_leak.test.ts +43 -0
- package/src/scripts/check_secret_leak.ts +12 -0
- package/src/scripts/handoff_context_hook.ts +66 -18
- package/src/scripts/hooks/session_eol_hook.ts +12 -12
- package/src/scripts/report_carrier_divergence.ts +94 -29
- package/src/scripts/report_conformance_funnel.ts +11 -4
- package/src/scripts/routing_doctor.ts +25 -2
- package/src/scripts/schemas/dod.schema.json +45 -0
- package/dist/cli-delegate/chunk-RVGBH4XK.js +0 -142
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/scripts/_lib/git_common_dir.ts
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
function realpath_or_self(p) {
|
|
7
|
+
try {
|
|
8
|
+
return fs.realpathSync(p);
|
|
9
|
+
} catch {
|
|
10
|
+
return p;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function git_common_dir(project_root) {
|
|
14
|
+
const dot_git = path.join(project_root, ".git");
|
|
15
|
+
try {
|
|
16
|
+
const st = fs.statSync(dot_git);
|
|
17
|
+
if (st.isDirectory()) {
|
|
18
|
+
return realpath_or_self(dot_git);
|
|
19
|
+
}
|
|
20
|
+
const m = fs.readFileSync(dot_git, "utf-8").match(/^gitdir:\s*(.+)\s*$/m);
|
|
21
|
+
if (m === null) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const gitdir = path.resolve(project_root, m[1].trim());
|
|
25
|
+
const commondir_file = path.join(gitdir, "commondir");
|
|
26
|
+
if (fs.existsSync(commondir_file)) {
|
|
27
|
+
const common = path.resolve(gitdir, fs.readFileSync(commondir_file, "utf-8").trim());
|
|
28
|
+
return realpath_or_self(common);
|
|
29
|
+
}
|
|
30
|
+
return realpath_or_self(gitdir);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
git_common_dir
|
|
38
|
+
};
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
_run_checks,
|
|
15
15
|
_run_checks_no_manifest,
|
|
16
16
|
_scan_foreign
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-542DZR4P.js";
|
|
18
18
|
import "./chunk-FTAISGDQ.js";
|
|
19
19
|
import {
|
|
20
20
|
atomic_write_json,
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
is_replay_mode
|
|
23
23
|
} from "./chunk-EJITJBJX.js";
|
|
24
24
|
import "./chunk-LVRYBKTI.js";
|
|
25
|
+
import "./chunk-PZWRPRWP.js";
|
|
25
26
|
import "./chunk-736JCWLM.js";
|
|
26
27
|
import {
|
|
27
28
|
atomicAppendLine,
|
|
@@ -142,15 +143,36 @@ function comparePair(a, b) {
|
|
|
142
143
|
}
|
|
143
144
|
return "body-diff";
|
|
144
145
|
}
|
|
145
|
-
function
|
|
146
|
-
if (!text.startsWith("---")) return text;
|
|
146
|
+
function splitFrontmatter(text) {
|
|
147
|
+
if (!text.startsWith("---")) return { hadFrontmatter: false, frontmatter: "", body: text };
|
|
147
148
|
const end = text.indexOf("\n---", 3);
|
|
148
|
-
if (end === -1) return text;
|
|
149
|
+
if (end === -1) return { hadFrontmatter: false, frontmatter: "", body: text };
|
|
149
150
|
const afterFence = text.indexOf("\n", end + 1);
|
|
150
|
-
return
|
|
151
|
+
return {
|
|
152
|
+
hadFrontmatter: true,
|
|
153
|
+
frontmatter: text.slice(3, end),
|
|
154
|
+
body: afterFence === -1 ? "" : text.slice(afterFence + 1)
|
|
155
|
+
};
|
|
151
156
|
}
|
|
152
157
|
function proseEqual(a, b) {
|
|
153
|
-
|
|
158
|
+
const sa = splitFrontmatter(a);
|
|
159
|
+
const sb = splitFrontmatter(b);
|
|
160
|
+
if (!sa.hadFrontmatter && !sb.hadFrontmatter) return false;
|
|
161
|
+
const leading = /^\n+/;
|
|
162
|
+
return sa.body.replace(leading, "") === sb.body.replace(leading, "");
|
|
163
|
+
}
|
|
164
|
+
function frontmatterPaths(text) {
|
|
165
|
+
const { hadFrontmatter, frontmatter } = splitFrontmatter(text);
|
|
166
|
+
if (!hadFrontmatter) return null;
|
|
167
|
+
const lines = frontmatter.split("\n");
|
|
168
|
+
const start = lines.findIndex((l) => /^paths:/.test(l));
|
|
169
|
+
if (start === -1) return null;
|
|
170
|
+
const block = [lines[start]];
|
|
171
|
+
for (const line of lines.slice(start + 1)) {
|
|
172
|
+
if (/^\s/.test(line) && line.trim() !== "") block.push(line);
|
|
173
|
+
else break;
|
|
174
|
+
}
|
|
175
|
+
return block.map((l) => l.trim()).join(" ");
|
|
154
176
|
}
|
|
155
177
|
function censusRuleDir(dir) {
|
|
156
178
|
let files = 0;
|
|
@@ -1974,10 +1996,7 @@ function _ruleType(file) {
|
|
|
1974
1996
|
} catch {
|
|
1975
1997
|
return "";
|
|
1976
1998
|
}
|
|
1977
|
-
|
|
1978
|
-
const end = text.indexOf("\n---", 3);
|
|
1979
|
-
const fm = end === -1 ? text : text.slice(0, end);
|
|
1980
|
-
const m = /^type:\s*"?([a-z]+)"?/m.exec(fm);
|
|
1999
|
+
const m = /^type:\s*"?([a-z]+)"?/m.exec(splitFrontmatter(text).frontmatter);
|
|
1981
2000
|
return m === null ? "" : m[1];
|
|
1982
2001
|
}
|
|
1983
2002
|
function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
@@ -1990,6 +2009,7 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
1990
2009
|
identical: [],
|
|
1991
2010
|
provenanceOnly: [],
|
|
1992
2011
|
frontmatterOnly: [],
|
|
2012
|
+
pathsScopeDiff: [],
|
|
1993
2013
|
bodyDiff: [],
|
|
1994
2014
|
projectOnly: [],
|
|
1995
2015
|
globalOnly: [],
|
|
@@ -2014,12 +2034,12 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
2014
2034
|
}
|
|
2015
2035
|
out.shared += 1;
|
|
2016
2036
|
let verdict;
|
|
2017
|
-
let
|
|
2037
|
+
let projectCopy;
|
|
2038
|
+
let globalCopy;
|
|
2018
2039
|
try {
|
|
2019
|
-
|
|
2020
|
-
|
|
2040
|
+
projectCopy = fs8.readFileSync(path8.join(projectDir, rule));
|
|
2041
|
+
globalCopy = fs8.readFileSync(path8.join(globalDir, rule));
|
|
2021
2042
|
verdict = comparePair(projectCopy, globalCopy);
|
|
2022
|
-
proseSame = proseEqual(projectCopy.toString("utf-8"), globalCopy.toString("utf-8"));
|
|
2023
2043
|
} catch {
|
|
2024
2044
|
out.shared -= 1;
|
|
2025
2045
|
out.unreadable.push(rule);
|
|
@@ -2027,8 +2047,16 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
2027
2047
|
}
|
|
2028
2048
|
if (verdict === "identical") out.identical.push(rule);
|
|
2029
2049
|
else if (verdict === "provenance-only") out.provenanceOnly.push(rule);
|
|
2030
|
-
else
|
|
2031
|
-
|
|
2050
|
+
else {
|
|
2051
|
+
const projectText = projectCopy.toString("utf-8");
|
|
2052
|
+
const globalText = globalCopy.toString("utf-8");
|
|
2053
|
+
if (proseEqual(projectText, globalText)) {
|
|
2054
|
+
out.frontmatterOnly.push(rule);
|
|
2055
|
+
if (frontmatterPaths(projectText) !== frontmatterPaths(globalText)) {
|
|
2056
|
+
out.pathsScopeDiff.push(rule);
|
|
2057
|
+
}
|
|
2058
|
+
} else out.bodyDiff.push(rule);
|
|
2059
|
+
}
|
|
2032
2060
|
}
|
|
2033
2061
|
return out;
|
|
2034
2062
|
}
|
|
@@ -2057,6 +2085,7 @@ function render2(d) {
|
|
|
2057
2085
|
lines.push(` byte-identical ${d.identical.length}`);
|
|
2058
2086
|
lines.push(` differ ONLY in the install stamp ${d.provenanceOnly.length}`);
|
|
2059
2087
|
lines.push(` differ ONLY in frontmatter ${d.frontmatterOnly.length}`);
|
|
2088
|
+
lines.push(` of which disagree on \`paths:\` ${d.pathsScopeDiff.length} \u2190 ACTIONABLE`);
|
|
2060
2089
|
lines.push(` differ in PROSE ${d.bodyDiff.length}`);
|
|
2061
2090
|
if (d.unreadable.length > 0) {
|
|
2062
2091
|
lines.push(` unreadable on one side ${d.unreadable.length} (${d.unreadable.join(", ")})`);
|
|
@@ -2081,16 +2110,27 @@ function render2(d) {
|
|
|
2081
2110
|
for (const r of d.bodyDiff) lines.push(` - ${r}`);
|
|
2082
2111
|
lines.push("");
|
|
2083
2112
|
}
|
|
2084
|
-
if (d.
|
|
2085
|
-
lines.push(` ${String(d.
|
|
2086
|
-
lines.push("
|
|
2087
|
-
lines.push("
|
|
2088
|
-
lines.push("
|
|
2089
|
-
lines.push("
|
|
2090
|
-
lines.push("
|
|
2091
|
-
lines.push("
|
|
2092
|
-
lines.push("
|
|
2093
|
-
|
|
2113
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
2114
|
+
lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) \u2014 same prose, different load schedule.`);
|
|
2115
|
+
lines.push(" ACT ON THESE. `paths` is the one frontmatter key this host reads: with it, a");
|
|
2116
|
+
lines.push(" rule fires when a matching file is read and is NOT re-injected after");
|
|
2117
|
+
lines.push(" /compact; without it, the rule loads unconditionally at launch. So the copy");
|
|
2118
|
+
lines.push(" that lacks `paths` DEFEATS the other copy's scoping \u2014 the rule the project");
|
|
2119
|
+
lines.push(" layer meant to deliver conditionally is delivered always, and an obligation");
|
|
2120
|
+
lines.push(" a maintainer deliberately scoped is silently unscoped on any machine that");
|
|
2121
|
+
lines.push(" carries both. Identical prose does not make this inert.");
|
|
2122
|
+
for (const r of d.pathsScopeDiff) lines.push(` - ${r}`);
|
|
2123
|
+
lines.push("");
|
|
2124
|
+
}
|
|
2125
|
+
const inert = d.frontmatterOnly.length - d.pathsScopeDiff.length;
|
|
2126
|
+
if (inert > 0) {
|
|
2127
|
+
lines.push(` ${String(inert)} further pair(s) differ ONLY in frontmatter the host does not read \u2014`);
|
|
2128
|
+
lines.push(" prose byte-identical, `paths` in agreement, nothing to act on. Two writers,");
|
|
2129
|
+
lines.push(" two policies: `generate-tools` emits `paths` where a rule is path-scoped and");
|
|
2130
|
+
lines.push(" nothing otherwise; `install.ts` writes agent-config's own vocabulary plus its");
|
|
2131
|
+
lines.push(" ownership stamp, and none of those keys is one this host reads");
|
|
2132
|
+
lines.push(" (claude-code-rules-dir-contract.md). Reported as a count on purpose: naming");
|
|
2133
|
+
lines.push(" every pair would bury the two classes above, which are the actionable ones.");
|
|
2094
2134
|
lines.push("");
|
|
2095
2135
|
}
|
|
2096
2136
|
if (d.provenanceOnly.length > 0) {
|
|
@@ -2129,6 +2169,10 @@ function render2(d) {
|
|
|
2129
2169
|
if (d.bodyDiff.length === 0) {
|
|
2130
2170
|
lines.push("");
|
|
2131
2171
|
lines.push(" No prose divergence \u2014 no rule's governed text differs between the carriers.");
|
|
2172
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
2173
|
+
lines.push(" NOT an all-clear: the `paths:` disagreements above are still open, and they");
|
|
2174
|
+
lines.push(" change when a rule loads rather than what it says.");
|
|
2175
|
+
}
|
|
2132
2176
|
lines.push(" The condition is transient \u2014 it returns whenever the checkout moves ahead of");
|
|
2133
2177
|
lines.push(" the installed release \u2014 so this is a reading of right now, not a property of");
|
|
2134
2178
|
lines.push(" the repo. Re-run it, do not cite it. That holds doubly here: the reading also");
|
|
@@ -2736,7 +2780,7 @@ function render5(r) {
|
|
|
2736
2780
|
lines.push(" substituted with dist/, which would answer a different question.");
|
|
2737
2781
|
} else {
|
|
2738
2782
|
lines.push(
|
|
2739
|
-
` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} prose-diff \xB7 ${d.frontmatterOnly.length} frontmatter-only \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
|
|
2783
|
+
` Divergence: ${d.shared} shared \xB7 ${d.bodyDiff.length} prose-diff \xB7 ${d.frontmatterOnly.length} frontmatter-only (${d.pathsScopeDiff.length} on \`paths:\`) \xB7 ${d.provenanceOnly.length} provenance-only \xB7 ${d.projectOnly.length} project-only \xB7 ${d.globalOnly.length} global-only` + (d.manualOnlyGlobal.length > 0 ? ` (${d.manualOnlyGlobal.length} by design, ADR-004 manual)` : "")
|
|
2740
2784
|
);
|
|
2741
2785
|
if (d.bodyDiff.length > 0) {
|
|
2742
2786
|
lines.push(` PROSE DIVERGENCE \u2014 both copies reach the model: ${d.bodyDiff.join(", ")}`);
|
|
@@ -2745,9 +2789,16 @@ function render5(r) {
|
|
|
2745
2789
|
lines.push(" (claude-code-rules-dir-contract.md). The project copy is merely the");
|
|
2746
2790
|
lines.push(" newer one \u2014 recency, not precedence.");
|
|
2747
2791
|
}
|
|
2748
|
-
if (d.
|
|
2749
|
-
lines.push(` ${String(d.
|
|
2750
|
-
lines.push("
|
|
2792
|
+
if (d.pathsScopeDiff.length > 0) {
|
|
2793
|
+
lines.push(` \`paths:\` SCOPE DISAGREEMENT (${String(d.pathsScopeDiff.length)}) \u2014 same prose, different load`);
|
|
2794
|
+
lines.push(" schedule. `paths` is the one frontmatter key the host reads, so the copy");
|
|
2795
|
+
lines.push(" without it defeats the other copy's scoping: a deliberately scoped");
|
|
2796
|
+
lines.push(" obligation is delivered unconditionally. Act on these.");
|
|
2797
|
+
}
|
|
2798
|
+
const inertFm = d.frontmatterOnly.length - d.pathsScopeDiff.length;
|
|
2799
|
+
if (inertFm > 0) {
|
|
2800
|
+
lines.push(` ${String(inertFm)} further pair(s) differ only in frontmatter the host does not read \u2014`);
|
|
2801
|
+
lines.push(" prose byte-identical, `paths` in agreement, nothing to act on.");
|
|
2751
2802
|
}
|
|
2752
2803
|
if (d.unreadable.length > 0) {
|
|
2753
2804
|
lines.push(` Unreadable on one side (${d.unreadable.length}) \u2014 a broken install, not a disagreement.`);
|
|
@@ -66,10 +66,11 @@ import {
|
|
|
66
66
|
_wizard_state_path,
|
|
67
67
|
main,
|
|
68
68
|
shutilWhich
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-542DZR4P.js";
|
|
70
70
|
import "./chunk-FTAISGDQ.js";
|
|
71
71
|
import "./chunk-EJITJBJX.js";
|
|
72
72
|
import "./chunk-LVRYBKTI.js";
|
|
73
|
+
import "./chunk-PZWRPRWP.js";
|
|
73
74
|
import "./chunk-736JCWLM.js";
|
|
74
75
|
import "./chunk-63WSP6YZ.js";
|
|
75
76
|
import "./chunk-GRRPEPRN.js";
|
|
@@ -9,8 +9,13 @@ import {
|
|
|
9
9
|
RECYCLE_CONSUMED_REL,
|
|
10
10
|
RECYCLE_ENVELOPE_REL,
|
|
11
11
|
RECYCLE_MAX_AGE_HOURS,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
collectRepoAnchor,
|
|
13
|
+
describeDrift,
|
|
14
|
+
hasBoundaryMarker,
|
|
15
|
+
scanEnvelopeDirectives,
|
|
16
|
+
validateRecycleEnvelope,
|
|
17
|
+
wrapAsPriorSessionData
|
|
18
|
+
} from "./chunk-2WELHO3P.js";
|
|
14
19
|
import "./chunk-3EWYC3YK.js";
|
|
15
20
|
import {
|
|
16
21
|
readHookStdin
|
|
@@ -19,6 +24,7 @@ import {
|
|
|
19
24
|
_RAW_KEY_PREFIXES,
|
|
20
25
|
hardenedSpawnEnv
|
|
21
26
|
} from "./chunk-LVRYBKTI.js";
|
|
27
|
+
import "./chunk-PZWRPRWP.js";
|
|
22
28
|
import "./chunk-JEJABGKB.js";
|
|
23
29
|
import "./chunk-NDJLNC6Q.js";
|
|
24
30
|
import "./chunk-OY53J6II.js";
|
|
@@ -26,9 +32,9 @@ import "./chunk-J55WI3RR.js";
|
|
|
26
32
|
|
|
27
33
|
// src/scripts/_cli/cmd_handoff.ts
|
|
28
34
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
29
|
-
import * as
|
|
35
|
+
import * as fs5 from "node:fs";
|
|
30
36
|
import process4 from "node:process";
|
|
31
|
-
import * as
|
|
37
|
+
import * as path6 from "node:path";
|
|
32
38
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
33
39
|
|
|
34
40
|
// src/scripts/_cli/handoff_generate.ts
|
|
@@ -208,6 +214,15 @@ function _is_replay_mode() {
|
|
|
208
214
|
function _handoff_context_path(root) {
|
|
209
215
|
return process2.env.AGENT_HANDOFF_CONTEXT_FILE || path.join(root, HANDOFF_CONTEXT_REL);
|
|
210
216
|
}
|
|
217
|
+
function guardedInjection(block, reason) {
|
|
218
|
+
if (!hasBoundaryMarker(block)) {
|
|
219
|
+
return {
|
|
220
|
+
action: "discard",
|
|
221
|
+
reason: "refused: injected block carries no prior-session boundary marker"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return { action: "inject", reason, context: block };
|
|
225
|
+
}
|
|
211
226
|
function consume_handoff_context(root, now = /* @__PURE__ */ new Date()) {
|
|
212
227
|
const target = _handoff_context_path(root);
|
|
213
228
|
let text;
|
|
@@ -238,14 +253,12 @@ function consume_handoff_context(root, now = /* @__PURE__ */ new Date()) {
|
|
|
238
253
|
return { action: "discard", reason: `stale: ${ageHours.toFixed(1)}h > ${MAX_AGE_HOURS}h` };
|
|
239
254
|
}
|
|
240
255
|
const sourceSession = text.match(/^Source-Session: (.+)$/m)?.[1]?.trim() ?? "unknown";
|
|
241
|
-
const block =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"</handoff-context>"
|
|
246
|
-
].join("\n");
|
|
256
|
+
const block = wrapAsPriorSessionData(text, {
|
|
257
|
+
kind: `handoff session=${sourceSession}`,
|
|
258
|
+
source: HANDOFF_CONTEXT_REL
|
|
259
|
+
});
|
|
247
260
|
remove();
|
|
248
|
-
return
|
|
261
|
+
return guardedInjection(block, `handoff consumed (session=${sourceSession})`);
|
|
249
262
|
}
|
|
250
263
|
function consume_recycle_envelope(root, now = /* @__PURE__ */ new Date()) {
|
|
251
264
|
const target = process2.env.AGENT_RECYCLE_ENVELOPE_FILE || path.join(root, RECYCLE_ENVELOPE_REL);
|
|
@@ -297,16 +310,24 @@ function consume_recycle_envelope(root, now = /* @__PURE__ */ new Date()) {
|
|
|
297
310
|
consume();
|
|
298
311
|
return { action: "discard", reason: "recycle envelope workspace does not resolve" };
|
|
299
312
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
313
|
+
const drift = describeDrift(envelope, collectRepoAnchor(root));
|
|
314
|
+
const warnings = scanEnvelopeDirectives(envelope);
|
|
315
|
+
const focus = (process2.env.AGENT_RESUME_FOCUS ?? "").trim();
|
|
316
|
+
const block = wrapAsPriorSessionData(JSON.stringify(envelope, null, 2), {
|
|
317
|
+
kind: "recycle-envelope",
|
|
318
|
+
source: RECYCLE_ENVELOPE_REL,
|
|
319
|
+
warnings: [
|
|
320
|
+
...drift,
|
|
321
|
+
...focus ? [`FOCUS: attack "${focus}" first \u2014 the rest of this envelope is context.`] : [],
|
|
322
|
+
"Re-derive everything under not_carried_forward from source before trusting it.",
|
|
323
|
+
...warnings
|
|
324
|
+
]
|
|
325
|
+
});
|
|
308
326
|
consume();
|
|
309
|
-
return
|
|
327
|
+
return guardedInjection(
|
|
328
|
+
block,
|
|
329
|
+
warnings.length > 0 ? `recycle envelope consumed (${warnings.length} directive warning(s) surfaced)` : "recycle envelope consumed"
|
|
330
|
+
);
|
|
310
331
|
}
|
|
311
332
|
function main() {
|
|
312
333
|
let envelope = {};
|
|
@@ -365,6 +386,7 @@ var WORD_CAP = 1200;
|
|
|
365
386
|
var SNIPPET_CHARS = 200;
|
|
366
387
|
var DONE_SNIPPET_CHARS = 400;
|
|
367
388
|
var MAX_ERRORS = 5;
|
|
389
|
+
var MAX_ERROR_CHARS = 400;
|
|
368
390
|
var MAX_DECISIONS = 5;
|
|
369
391
|
var MAX_FILES = 15;
|
|
370
392
|
var FAILURE_RE = /\b(fail(ing|ed|ure)?|error|exit[= ][1-9]|red\b|broken)\b/i;
|
|
@@ -570,7 +592,11 @@ function build_handoff(session, options = {}) {
|
|
|
570
592
|
lastUser ? [`continue from the last user ask: ${_snippet(lastUser, SNIPPET_CHARS)}`] : []
|
|
571
593
|
);
|
|
572
594
|
const errors = _redact_items(
|
|
573
|
-
toolish.filter((t) => FAILURE_RE.test(t.text)).slice(-MAX_ERRORS).map((t) =>
|
|
595
|
+
toolish.filter((t) => FAILURE_RE.test(t.text)).slice(-MAX_ERRORS).map((t) => {
|
|
596
|
+
const line = t.text.split("\n").find((l) => FAILURE_RE.test(l))?.trim() ?? t.text.trim();
|
|
597
|
+
const prefix = t.tool ? `${t.tool}: ` : "";
|
|
598
|
+
return `${prefix}${line}`.slice(0, MAX_ERROR_CHARS);
|
|
599
|
+
})
|
|
574
600
|
);
|
|
575
601
|
const decisions = _redact_items(
|
|
576
602
|
assistants.filter((t) => DECISION_RE.test(t.text)).slice(-MAX_DECISIONS).map((t) => _snippet(t.text, SNIPPET_CHARS))
|
|
@@ -622,6 +648,14 @@ function build_handoff(session, options = {}) {
|
|
|
622
648
|
}
|
|
623
649
|
for (const s of sections) {
|
|
624
650
|
lines.push("", `## ${s.title}`, "");
|
|
651
|
+
if (s.title === "Resume pointer") {
|
|
652
|
+
lines.push(
|
|
653
|
+
"> Precision contract: identify code by signature or `path:line`, never by",
|
|
654
|
+
"> description; quote error strings verbatim; give every resume step its",
|
|
655
|
+
'> expected outcome, so "done" is checkable rather than felt.',
|
|
656
|
+
""
|
|
657
|
+
);
|
|
658
|
+
}
|
|
625
659
|
if (s.items.length === 0) {
|
|
626
660
|
if (s.verbatim && verbatim.withheldLines > 0) {
|
|
627
661
|
lines.push(`- [${verbatim.withheldLines} line(s) withheld by privacy floor]`);
|
|
@@ -670,11 +704,49 @@ function write_handoff(text, options = {}) {
|
|
|
670
704
|
}
|
|
671
705
|
|
|
672
706
|
// src/scripts/_cli/handoff_sessions.ts
|
|
673
|
-
import * as
|
|
707
|
+
import * as fs4 from "node:fs";
|
|
674
708
|
import * as os from "node:os";
|
|
709
|
+
import * as path5 from "node:path";
|
|
710
|
+
|
|
711
|
+
// src/scripts/_lib/session_eol.ts
|
|
712
|
+
import * as crypto from "node:crypto";
|
|
713
|
+
import * as fs3 from "node:fs";
|
|
714
|
+
import * as path4 from "node:path";
|
|
715
|
+
|
|
716
|
+
// src/scripts/_lib/cc_transcript.ts
|
|
675
717
|
import * as path3 from "node:path";
|
|
718
|
+
import { homedir } from "node:os";
|
|
719
|
+
var DEFAULT_PROJECTS_ROOT = path3.join(homedir(), ".claude", "projects");
|
|
720
|
+
|
|
721
|
+
// src/scripts/_lib/session_eol.ts
|
|
722
|
+
function eolStateDir(workspaceRoot) {
|
|
723
|
+
return path4.join(workspaceRoot, "agents", "runtime", "state", "session-eol");
|
|
724
|
+
}
|
|
725
|
+
function eolStateFile(workspaceRoot, sessionKey) {
|
|
726
|
+
return path4.join(eolStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
727
|
+
}
|
|
728
|
+
function eolSessionKey(raw) {
|
|
729
|
+
return crypto.createHash("sha256").update(raw || "unknown-session").digest("hex");
|
|
730
|
+
}
|
|
731
|
+
function readEolCounters(workspaceRoot, sessionKey) {
|
|
732
|
+
try {
|
|
733
|
+
const raw = fs3.readFileSync(eolStateFile(workspaceRoot, sessionKey), "utf-8");
|
|
734
|
+
const parsed = JSON.parse(raw);
|
|
735
|
+
const counters = parsed["counters"];
|
|
736
|
+
if (typeof counters !== "object" || counters === null || Array.isArray(counters)) {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
return counters;
|
|
740
|
+
} catch {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/scripts/_cli/handoff_sessions.ts
|
|
676
746
|
var DEFAULT_CAP = 15;
|
|
747
|
+
var FETCH_OVERSHOOT = 3;
|
|
677
748
|
var SUMMARY_SNIPPET_CHARS = 120;
|
|
749
|
+
var SUBSTANTIVE_TOKEN_FLOOR = 1e4;
|
|
678
750
|
function encode_project_path(cwd) {
|
|
679
751
|
return cwd.replace(/[/.]/g, "-");
|
|
680
752
|
}
|
|
@@ -690,7 +762,7 @@ function _summarize(first, last) {
|
|
|
690
762
|
}
|
|
691
763
|
function _read_lines2(file) {
|
|
692
764
|
try {
|
|
693
|
-
return
|
|
765
|
+
return fs4.readFileSync(file, "utf-8").split("\n");
|
|
694
766
|
} catch {
|
|
695
767
|
return [];
|
|
696
768
|
}
|
|
@@ -707,10 +779,10 @@ function _parse_json2(line) {
|
|
|
707
779
|
}
|
|
708
780
|
return null;
|
|
709
781
|
}
|
|
710
|
-
var CHAT_HISTORY_REL =
|
|
782
|
+
var CHAT_HISTORY_REL = path5.join("agents", "runtime", ".agent-chat-history");
|
|
711
783
|
function _chat_history_path(opts) {
|
|
712
784
|
if (opts.chatHistoryPath) return opts.chatHistoryPath;
|
|
713
|
-
return process.env.AGENT_CHAT_HISTORY_FILE ||
|
|
785
|
+
return process.env.AGENT_CHAT_HISTORY_FILE || path5.join(opts.cwd ?? process.cwd(), CHAT_HISTORY_REL);
|
|
714
786
|
}
|
|
715
787
|
function _chat_history_summaries(histPath) {
|
|
716
788
|
const bySession = /* @__PURE__ */ new Map();
|
|
@@ -795,19 +867,19 @@ function _scan_claude_transcript(file) {
|
|
|
795
867
|
return scan;
|
|
796
868
|
}
|
|
797
869
|
function _claude_transcript_sessions(opts, cap) {
|
|
798
|
-
const root = opts.claudeProjectsRoot ??
|
|
870
|
+
const root = opts.claudeProjectsRoot ?? path5.join(os.homedir(), ".claude", "projects");
|
|
799
871
|
const cwd = opts.cwd ?? process.cwd();
|
|
800
|
-
const projectDir =
|
|
872
|
+
const projectDir = path5.join(root, encode_project_path(cwd));
|
|
801
873
|
let names;
|
|
802
874
|
try {
|
|
803
|
-
names =
|
|
875
|
+
names = fs4.readdirSync(projectDir).filter((n) => n.endsWith(".jsonl"));
|
|
804
876
|
} catch {
|
|
805
877
|
return [];
|
|
806
878
|
}
|
|
807
879
|
const files = names.map((n) => {
|
|
808
|
-
const full =
|
|
880
|
+
const full = path5.join(projectDir, n);
|
|
809
881
|
try {
|
|
810
|
-
return { full, id:
|
|
882
|
+
return { full, id: path5.basename(n, ".jsonl"), mtime: fs4.statSync(full).mtimeMs };
|
|
811
883
|
} catch {
|
|
812
884
|
return null;
|
|
813
885
|
}
|
|
@@ -832,13 +904,13 @@ function _claude_transcript_sessions(opts, cap) {
|
|
|
832
904
|
function _codex_meta(file) {
|
|
833
905
|
let firstLine = "";
|
|
834
906
|
try {
|
|
835
|
-
const fd =
|
|
907
|
+
const fd = fs4.openSync(file, "r");
|
|
836
908
|
try {
|
|
837
909
|
const buf = Buffer.alloc(8192);
|
|
838
|
-
const n =
|
|
910
|
+
const n = fs4.readSync(fd, buf, 0, buf.length, 0);
|
|
839
911
|
firstLine = buf.toString("utf-8", 0, n).split("\n")[0] ?? "";
|
|
840
912
|
} finally {
|
|
841
|
-
|
|
913
|
+
fs4.closeSync(fd);
|
|
842
914
|
}
|
|
843
915
|
} catch {
|
|
844
916
|
return null;
|
|
@@ -863,17 +935,17 @@ function _codex_files(root) {
|
|
|
863
935
|
const dir = stack.pop();
|
|
864
936
|
let entries;
|
|
865
937
|
try {
|
|
866
|
-
entries =
|
|
938
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
867
939
|
} catch {
|
|
868
940
|
continue;
|
|
869
941
|
}
|
|
870
942
|
for (const e of entries) {
|
|
871
|
-
const full =
|
|
943
|
+
const full = path5.join(dir, e.name);
|
|
872
944
|
if (e.isDirectory()) stack.push(full);
|
|
873
945
|
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
|
|
874
946
|
}
|
|
875
947
|
}
|
|
876
|
-
return out.sort((a, b) =>
|
|
948
|
+
return out.sort((a, b) => path5.basename(a) < path5.basename(b) ? 1 : -1);
|
|
877
949
|
}
|
|
878
950
|
function _codex_prompt_text(rec) {
|
|
879
951
|
if (rec.type !== "event_msg") return null;
|
|
@@ -901,13 +973,13 @@ function _scan_codex_session(file) {
|
|
|
901
973
|
return scan;
|
|
902
974
|
}
|
|
903
975
|
function _matches_repo(sessionCwd, repoCwd) {
|
|
904
|
-
const norm = (p) =>
|
|
976
|
+
const norm = (p) => path5.resolve(p).replace(/\/+$/, "");
|
|
905
977
|
const a = norm(sessionCwd);
|
|
906
978
|
const b = norm(repoCwd);
|
|
907
|
-
return a === b || a.startsWith(b +
|
|
979
|
+
return a === b || a.startsWith(b + path5.sep);
|
|
908
980
|
}
|
|
909
981
|
function _codex_sessions(opts, cap) {
|
|
910
|
-
const root = opts.codexSessionsRoot ??
|
|
982
|
+
const root = opts.codexSessionsRoot ?? path5.join(os.homedir(), ".codex", "sessions");
|
|
911
983
|
const cwd = opts.cwd ?? process.cwd();
|
|
912
984
|
const matching = [];
|
|
913
985
|
for (const file of _codex_files(root)) {
|
|
@@ -937,21 +1009,58 @@ function _codex_sessions(opts, cap) {
|
|
|
937
1009
|
function _sort_key(s) {
|
|
938
1010
|
return s.endedAt ?? s.startedAt ?? "";
|
|
939
1011
|
}
|
|
1012
|
+
function is_substantive(counters) {
|
|
1013
|
+
if (counters === null) return true;
|
|
1014
|
+
if (!Number.isFinite(counters.assistant_records)) return true;
|
|
1015
|
+
if (counters.assistant_records < 1) return false;
|
|
1016
|
+
if (!Number.isFinite(counters.tool_calls)) return true;
|
|
1017
|
+
if (counters.tool_calls >= 1) return true;
|
|
1018
|
+
if (!Number.isFinite(counters.final_context_tokens)) return true;
|
|
1019
|
+
return counters.final_context_tokens >= SUBSTANTIVE_TOKEN_FLOOR;
|
|
1020
|
+
}
|
|
1021
|
+
function _derive_tag(id) {
|
|
1022
|
+
try {
|
|
1023
|
+
return derive_session_tag(id);
|
|
1024
|
+
} catch {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function _counters_for(session, byTag, workspaceRoot) {
|
|
1029
|
+
const native = session.source === "chat-history" ? byTag.get(session.id) : session;
|
|
1030
|
+
const candidates = [
|
|
1031
|
+
...session.source === "chat-history" ? [] : [session.id],
|
|
1032
|
+
native?.id,
|
|
1033
|
+
native?.transcriptPath
|
|
1034
|
+
];
|
|
1035
|
+
for (const candidate of candidates) {
|
|
1036
|
+
if (!candidate) continue;
|
|
1037
|
+
const counters = readEolCounters(workspaceRoot, eolSessionKey(candidate));
|
|
1038
|
+
if (counters !== null) return counters;
|
|
1039
|
+
}
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
940
1042
|
function list_handoff_sessions(opts = {}) {
|
|
941
1043
|
const cap = opts.cap ?? DEFAULT_CAP;
|
|
1044
|
+
const workspaceRoot = opts.workspaceRoot ?? opts.cwd ?? process.cwd();
|
|
1045
|
+
const selfId = opts.selfSessionId === null ? null : opts.selfSessionId ?? process.env.AGENT_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID ?? null;
|
|
1046
|
+
const selfTag = selfId ? _derive_tag(selfId) : null;
|
|
942
1047
|
const primary = _chat_history_sessions(opts);
|
|
943
1048
|
const knownTags = new Set(primary.map((s) => s.id));
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
...
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1049
|
+
const fetchCap = cap * FETCH_OVERSHOOT;
|
|
1050
|
+
const nativeAll = [
|
|
1051
|
+
..._claude_transcript_sessions(opts, fetchCap),
|
|
1052
|
+
..._codex_sessions(opts, fetchCap)
|
|
1053
|
+
];
|
|
1054
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1055
|
+
for (const s of nativeAll) {
|
|
1056
|
+
const tag = _derive_tag(s.id);
|
|
1057
|
+
if (tag) byTag.set(tag, s);
|
|
1058
|
+
}
|
|
1059
|
+
const native = nativeAll.filter((s) => {
|
|
1060
|
+
const tag = _derive_tag(s.id);
|
|
1061
|
+
return tag === null || !knownTags.has(tag);
|
|
953
1062
|
});
|
|
954
|
-
return [...primary, ...native].sort((a, b) => _sort_key(a) < _sort_key(b) ? 1 : _sort_key(a) > _sort_key(b) ? -1 : 0).slice(0, cap);
|
|
1063
|
+
return [...primary, ...native].filter((s) => s.id !== selfId && (selfTag === null || s.id !== selfTag)).filter((s) => is_substantive(_counters_for(s, byTag, workspaceRoot))).sort((a, b) => _sort_key(a) < _sort_key(b) ? 1 : _sort_key(a) > _sort_key(b) ? -1 : 0).slice(0, cap);
|
|
955
1064
|
}
|
|
956
1065
|
|
|
957
1066
|
// src/scripts/_cli/cmd_handoff.ts
|
|
@@ -1016,14 +1125,14 @@ function _default_runner(cmd) {
|
|
|
1016
1125
|
function _tty_read_line(prompt) {
|
|
1017
1126
|
try {
|
|
1018
1127
|
process4.stdout.write(prompt);
|
|
1019
|
-
const fd =
|
|
1128
|
+
const fd = fs5.openSync("/dev/tty", "rs");
|
|
1020
1129
|
try {
|
|
1021
1130
|
const buf = Buffer.alloc(1);
|
|
1022
1131
|
let line = "";
|
|
1023
1132
|
for (; ; ) {
|
|
1024
1133
|
let n = 0;
|
|
1025
1134
|
try {
|
|
1026
|
-
n =
|
|
1135
|
+
n = fs5.readSync(fd, buf, 0, 1, null);
|
|
1027
1136
|
} catch (exc) {
|
|
1028
1137
|
const code = exc.code;
|
|
1029
1138
|
if (code === "EAGAIN" || code === "EINTR") continue;
|
|
@@ -1036,7 +1145,7 @@ function _tty_read_line(prompt) {
|
|
|
1036
1145
|
}
|
|
1037
1146
|
return line;
|
|
1038
1147
|
} finally {
|
|
1039
|
-
|
|
1148
|
+
fs5.closeSync(fd);
|
|
1040
1149
|
}
|
|
1041
1150
|
} catch {
|
|
1042
1151
|
return null;
|
|
@@ -1130,7 +1239,7 @@ function main2(argv = null, options = {}) {
|
|
|
1130
1239
|
throw exc;
|
|
1131
1240
|
}
|
|
1132
1241
|
const envRoot = process4.env.AGENT_CONFIG_ROOT_OVERRIDE === "1" ? process4.env.AGENT_CONFIG_PROJECT_ROOT || null : null;
|
|
1133
|
-
const root =
|
|
1242
|
+
const root = path6.resolve(opts.root ?? envRoot ?? process4.cwd());
|
|
1134
1243
|
const sessions = list_handoff_sessions({ cwd: root });
|
|
1135
1244
|
if (opts.list) {
|
|
1136
1245
|
if (sessions.length === 0) {
|
|
@@ -1209,7 +1318,7 @@ ${PROG}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
1209
1318
|
`);
|
|
1210
1319
|
return runner(adapter.build_argv(text));
|
|
1211
1320
|
}
|
|
1212
|
-
const existed =
|
|
1321
|
+
const existed = fs5.existsSync(handoff_target_path(root));
|
|
1213
1322
|
const target = write_handoff(text, { cwd: root });
|
|
1214
1323
|
if (existed) out("note: replaced an unconsumed handoff \u2014 only the most recent one persists.\n");
|
|
1215
1324
|
out(`handoff written: ${target}
|
|
@@ -1226,13 +1335,13 @@ function _isCliEntry() {
|
|
|
1226
1335
|
if (process4.argv[1] === void 0) {
|
|
1227
1336
|
return false;
|
|
1228
1337
|
}
|
|
1229
|
-
const argvUrl = pathToFileURL(
|
|
1338
|
+
const argvUrl = pathToFileURL(path6.resolve(process4.argv[1])).href;
|
|
1230
1339
|
if (import.meta.url === argvUrl) {
|
|
1231
1340
|
return true;
|
|
1232
1341
|
}
|
|
1233
1342
|
try {
|
|
1234
|
-
const here =
|
|
1235
|
-
const argv =
|
|
1343
|
+
const here = fs5.realpathSync(fileURLToPath2(import.meta.url));
|
|
1344
|
+
const argv = fs5.realpathSync(path6.resolve(process4.argv[1]));
|
|
1236
1345
|
return here === argv;
|
|
1237
1346
|
} catch {
|
|
1238
1347
|
return false;
|