@event4u/agent-config 9.17.0 → 9.18.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/marketplace.json +1 -1
- package/CHANGELOG.md +122 -171
- package/dist/agent-src/commands/challenge-me/vision.md +5 -0
- package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
- package/dist/agent-src/commands/feature/plan.md +20 -0
- package/dist/agent-src/commands/feature/roadmap.md +19 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/roadmap/create.md +29 -0
- package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
- package/dist/agent-src/rules/active-remediation.md +2 -0
- package/dist/agent-src/rules/architecture.md +2 -0
- package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
- package/dist/agent-src/rules/brand-consistency.md +10 -62
- package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/cli-output-handling.md +5 -0
- package/dist/agent-src/rules/code-comment-discipline.md +3 -0
- package/dist/agent-src/rules/commit-conventions.md +2 -0
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/design-fidelity.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
- package/dist/agent-src/rules/docker-commands.md +4 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
- package/dist/agent-src/rules/improve-before-implement.md +4 -0
- package/dist/agent-src/rules/laravel-routing.md +4 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
- package/dist/agent-src/rules/media-governance-routing.md +5 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/no-roadmap-references.md +2 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
- package/dist/agent-src/rules/php-coding.md +2 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/rule-type-governance.md +2 -0
- package/dist/agent-src/rules/scale-discipline.md +3 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
- package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +4 -0
- package/dist/agent-src/rules/source-discovery-gate.md +3 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
- package/dist/agent-src/rules/think-before-action.md +5 -0
- package/dist/agent-src/rules/ui-audit-gate.md +2 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
- package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
- package/dist/agent-src/skills/design-review/SKILL.md +4 -141
- package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
- package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +12 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
- package/dist/cli-delegate/{chunk-IMSXPRVH.js → chunk-ELMFOQPW.js} +11 -0
- package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
- package/dist/cli-delegate/cmd_explain.js +36 -36
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +203 -2228
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_route_audit.js +249 -0
- package/dist/cli-delegate/cmd_route_explain.js +285 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +57 -57
- 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/install/install.mjs +11 -0
- package/dist/mcp/registry-manifest.json +1 -1
- package/dist/mcp/server.json +2 -2
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +12 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
- package/src/config/agent-settings.template.yml +46 -0
- package/src/config/evaluator-budgets.json +3 -3
- package/src/config/gate-coverage.yml +44 -0
- package/src/scripts/_cli/cmd_explain.ts +41 -36
- package/src/scripts/_cli/cmd_route_audit.ts +290 -0
- package/src/scripts/_cli/cmd_route_explain.ts +269 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/git_env.ts +40 -0
- package/src/scripts/_lib/md_table.ts +27 -0
- package/src/scripts/_lib/planning_settings.ts +43 -0
- package/src/scripts/_lib/release_highlights.ts +279 -0
- package/src/scripts/_lib/router_match.ts +301 -0
- package/src/scripts/annotate_r1_outcomes.ts +258 -0
- package/src/scripts/check_completion_review.ts +1322 -0
- package/src/scripts/check_release_highlights.ts +60 -152
- package/src/scripts/compile_router.ts +20 -3
- package/src/scripts/dispatch_r2_reviewer.ts +969 -0
- package/src/scripts/eval_ui_triviality.ts +156 -0
- package/src/scripts/lint_abstraction_thresholds.ts +3 -1
- package/src/scripts/lint_plan_risk_register.ts +931 -0
- package/src/scripts/lint_trigger_collisions.ts +257 -0
- package/src/scripts/release.ts +47 -13
- package/src/scripts/router_telemetry.ts +16 -290
- package/src/scripts/schemas/rule.schema.json +10 -0
- package/src/server/schemas/settings.ts +18 -0
|
@@ -1,2241 +1,216 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
derive_session_tag,
|
|
5
|
+
list_sessions,
|
|
6
|
+
readHookStdin,
|
|
7
|
+
read_entries
|
|
8
|
+
} from "./chunk-AIIUU73N.js";
|
|
3
9
|
import "./chunk-XGA2NTU4.js";
|
|
4
10
|
import {
|
|
5
11
|
_RAW_KEY_PREFIXES,
|
|
6
12
|
hardenedSpawnEnv
|
|
7
13
|
} from "./chunk-VSYT7OHO.js";
|
|
8
|
-
import
|
|
9
|
-
load_agent_settings
|
|
10
|
-
} from "./chunk-HJ6DAJHR.js";
|
|
14
|
+
import "./chunk-HJ6DAJHR.js";
|
|
11
15
|
import "./chunk-NDJLNC6Q.js";
|
|
12
16
|
import "./chunk-OY53J6II.js";
|
|
13
17
|
import "./chunk-J55WI3RR.js";
|
|
14
18
|
|
|
15
19
|
// src/scripts/_cli/cmd_handoff.ts
|
|
16
20
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17
|
-
import * as
|
|
21
|
+
import * as fs4 from "node:fs";
|
|
18
22
|
import process4 from "node:process";
|
|
19
|
-
import * as
|
|
20
|
-
import { fileURLToPath as
|
|
23
|
+
import * as path4 from "node:path";
|
|
24
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
21
25
|
|
|
22
26
|
// src/scripts/_cli/handoff_generate.ts
|
|
23
27
|
import { spawnSync } from "node:child_process";
|
|
24
|
-
import * as fs4 from "node:fs";
|
|
25
|
-
import * as path3 from "node:path";
|
|
26
|
-
import process3 from "node:process";
|
|
27
|
-
|
|
28
|
-
// src/scripts/ai_council/redact_low_impact_entry.ts
|
|
29
|
-
function _pyLen(s) {
|
|
30
|
-
let n = 0;
|
|
31
|
-
for (const _ of s) {
|
|
32
|
-
n += 1;
|
|
33
|
-
}
|
|
34
|
-
return n;
|
|
35
|
-
}
|
|
36
|
-
function _pySliceHead(s, n) {
|
|
37
|
-
let out = "";
|
|
38
|
-
let i = 0;
|
|
39
|
-
for (const ch of s) {
|
|
40
|
-
if (i >= n) {
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
out += ch;
|
|
44
|
-
i += 1;
|
|
45
|
-
}
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
function _strip(s) {
|
|
49
|
-
return s.trim();
|
|
50
|
-
}
|
|
51
|
-
function _reEscape(s) {
|
|
52
|
-
return s.replace(/[.*+?^${}()|[\]\\]/gu, (c) => "\\" + c);
|
|
53
|
-
}
|
|
54
|
-
function _violation(category, snippet, note = "") {
|
|
55
|
-
return { category, snippet, note };
|
|
56
|
-
}
|
|
57
|
-
function _pyRepr(s) {
|
|
58
|
-
const hasSingle = s.includes("'");
|
|
59
|
-
const hasDouble = s.includes('"');
|
|
60
|
-
const quote = hasSingle && !hasDouble ? '"' : "'";
|
|
61
|
-
let body = s.replace(/\\/g, "\\\\");
|
|
62
|
-
body = body.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
63
|
-
if (quote === "'") {
|
|
64
|
-
body = body.replace(/'/g, "\\'");
|
|
65
|
-
} else {
|
|
66
|
-
body = body.replace(/"/g, '\\"');
|
|
67
|
-
}
|
|
68
|
-
return `${quote}${body}${quote}`;
|
|
69
|
-
}
|
|
70
|
-
var RedactionResult = class {
|
|
71
|
-
ok;
|
|
72
|
-
violations;
|
|
73
|
-
constructor(ok, violations = []) {
|
|
74
|
-
this.ok = ok;
|
|
75
|
-
this.violations = violations;
|
|
76
|
-
}
|
|
77
|
-
summary() {
|
|
78
|
-
if (this.ok) {
|
|
79
|
-
return "redaction: clean";
|
|
80
|
-
}
|
|
81
|
-
const parts = this.violations.map((v) => `${v.category}: ${_pyRepr(v.snippet)}`);
|
|
82
|
-
return "redaction REFUSED \u2014 " + parts.join("; ");
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
var _UWORD = "[\\p{L}\\p{N}_]";
|
|
86
|
-
var _UNB_BEFORE = `(?<!${_UWORD})`;
|
|
87
|
-
var _UNB_AFTER = `(?!${_UWORD})`;
|
|
88
|
-
var _EMAIL_RE = new RegExp(
|
|
89
|
-
`${_UNB_BEFORE}[\\p{L}\\p{N}_.+-]+@[\\p{L}\\p{N}_-]+\\.[\\p{L}\\p{N}_.-]+${_UNB_AFTER}`,
|
|
90
|
-
"gu"
|
|
91
|
-
);
|
|
92
|
-
var _PATH_RE = new RegExp(
|
|
93
|
-
`(?:^|[\\s"'(])(?:/Users/|/home/|/opt/|/private/|[A-Z]:\\\\)[\\p{L}\\p{N}_.\\-/\\\\]+`,
|
|
94
|
-
"gmu"
|
|
95
|
-
);
|
|
96
|
-
var _INTERNAL_HOST_RE = new RegExp(
|
|
97
|
-
`${_UNB_BEFORE}[a-zA-Z0-9][\\p{L}\\p{N}_.-]*\\.(?:internal|local)${_UNB_AFTER}`,
|
|
98
|
-
"giu"
|
|
99
|
-
);
|
|
100
|
-
var _UD = "[\\p{Nd}]";
|
|
101
|
-
var _MONEY_RE = new RegExp(
|
|
102
|
-
`(?:[\\$\u20AC\xA3\xA5]\\s?${_UD}{1,3}(?:[,.]${_UD}{3})*(?:\\.${_UD}+)?|${_UNB_BEFORE}(?:USD|EUR|GBP|JPY)\\s?${_UD}+(?:[,.]${_UD}+)?)`,
|
|
103
|
-
"gu"
|
|
104
|
-
);
|
|
105
|
-
var _API_KEY_RE = new RegExp(
|
|
106
|
-
`${_UNB_BEFORE}api[_-]?key${_UNB_AFTER}\\s*[:=]\\s*[A-Za-z0-9+/=_\\-]{12,}`,
|
|
107
|
-
"giu"
|
|
108
|
-
);
|
|
109
|
-
var _CODE_FENCE_RE = /`([^`]{41,})`/gu;
|
|
110
|
-
function _checkSecrets(text) {
|
|
111
|
-
const hits = [];
|
|
112
|
-
for (const prefix of _RAW_KEY_PREFIXES) {
|
|
113
|
-
const pat = new RegExp(_reEscape(prefix) + "[A-Za-z0-9_\\-]{6,}", "u");
|
|
114
|
-
const m2 = pat.exec(text);
|
|
115
|
-
if (m2) {
|
|
116
|
-
hits.push(
|
|
117
|
-
_violation(
|
|
118
|
-
"secret",
|
|
119
|
-
_pySliceHead(m2[0], 8) + "\u2026",
|
|
120
|
-
`raw-key prefix ${_pyRepr(prefix)}`
|
|
121
|
-
)
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
_API_KEY_RE.lastIndex = 0;
|
|
126
|
-
const m = _API_KEY_RE.exec(text);
|
|
127
|
-
if (m) {
|
|
128
|
-
hits.push(_violation("secret", _pySliceHead(m[0], 20) + "\u2026", "inline api_key"));
|
|
129
|
-
}
|
|
130
|
-
return hits;
|
|
131
|
-
}
|
|
132
|
-
function _checkPatterns(text, repoRoot, privateDomains, customerNames, sqlIdentifiers) {
|
|
133
|
-
const hits = [];
|
|
134
|
-
for (const m of text.matchAll(_EMAIL_RE)) {
|
|
135
|
-
hits.push(_violation("email", m[0]));
|
|
136
|
-
}
|
|
137
|
-
for (const m of text.matchAll(_PATH_RE)) {
|
|
138
|
-
hits.push(_violation("project_path", _strip(m[0])));
|
|
139
|
-
}
|
|
140
|
-
if (repoRoot && text.includes(repoRoot)) {
|
|
141
|
-
hits.push(_violation("project_path", repoRoot, "configured repo root"));
|
|
142
|
-
}
|
|
143
|
-
for (const m of text.matchAll(_INTERNAL_HOST_RE)) {
|
|
144
|
-
hits.push(_violation("internal_hostname", m[0]));
|
|
145
|
-
}
|
|
146
|
-
for (const dom of privateDomains) {
|
|
147
|
-
if (dom && text.includes(dom)) {
|
|
148
|
-
hits.push(_violation("internal_hostname", dom, "configured private domain"));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
for (const m of text.matchAll(_MONEY_RE)) {
|
|
152
|
-
hits.push(_violation("monetary_amount", m[0]));
|
|
153
|
-
}
|
|
154
|
-
for (const name of customerNames) {
|
|
155
|
-
if (name && new RegExp(`${_UNB_BEFORE}${_reEscape(name)}${_UNB_AFTER}`, "iu").test(text)) {
|
|
156
|
-
hits.push(_violation("customer_name", name));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
for (const ident of sqlIdentifiers) {
|
|
160
|
-
if (ident && new RegExp(`${_UNB_BEFORE}${_reEscape(ident)}${_UNB_AFTER}`, "u").test(text)) {
|
|
161
|
-
hits.push(_violation("sql_identifier", ident));
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const m of text.matchAll(_CODE_FENCE_RE)) {
|
|
165
|
-
const inner = m[1];
|
|
166
|
-
hits.push(
|
|
167
|
-
_violation("long_code_excerpt", _pySliceHead(inner, 40) + "\u2026", `${_pyLen(inner)} chars`)
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
return hits;
|
|
171
|
-
}
|
|
172
|
-
function redact_low_impact_entry(text, opts = {}) {
|
|
173
|
-
const repoRoot = opts.repoRoot ?? null;
|
|
174
|
-
const privateDomains = opts.privateDomains ?? [];
|
|
175
|
-
const customerNames = opts.customerNames ?? [];
|
|
176
|
-
const sqlIdentifiers = opts.sqlIdentifiers ?? [];
|
|
177
|
-
const violations = [];
|
|
178
|
-
violations.push(..._checkSecrets(text));
|
|
179
|
-
violations.push(
|
|
180
|
-
..._checkPatterns(text, repoRoot, privateDomains, customerNames, sqlIdentifiers)
|
|
181
|
-
);
|
|
182
|
-
return new RedactionResult(violations.length === 0, violations);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// src/scripts/chat_history.ts
|
|
186
|
-
import { createHash } from "node:crypto";
|
|
187
28
|
import * as fs2 from "node:fs";
|
|
188
|
-
import * as
|
|
189
|
-
import
|
|
190
|
-
|
|
191
|
-
// src/scripts/
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
if (process.stdin.isTTY) return "";
|
|
198
|
-
return fs.readFileSync(0, "utf-8");
|
|
199
|
-
} catch {
|
|
200
|
-
return "";
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// src/scripts/chat_history.ts
|
|
205
|
-
var DEFAULT_FILE = "agents/runtime/.agent-chat-history";
|
|
206
|
-
var DEFAULT_SETTINGS_FILE = ".agent-settings.yml";
|
|
207
|
-
var SCHEMA_VERSION = 4;
|
|
208
|
-
var DEFAULT_MAX_SESSIONS = 5;
|
|
209
|
-
var VALID_FREQS = /* @__PURE__ */ new Set(["per_turn", "per_phase", "per_tool"]);
|
|
210
|
-
var VALID_OVERFLOW = /* @__PURE__ */ new Set(["rotate", "condense"]);
|
|
211
|
-
var REPLAY_ENV_VAR = "AGENT_CONFIG_REPLAY";
|
|
212
|
-
function _is_replay_mode() {
|
|
213
|
-
return (process.env[REPLAY_ENV_VAR] ?? "").trim() === "1";
|
|
214
|
-
}
|
|
215
|
-
var _WS_RE = /\s+/g;
|
|
216
|
-
var SESSION_ID_LEN = 16;
|
|
217
|
-
var SESSION_ID_UNKNOWN = "<unknown>";
|
|
218
|
-
var SESSION_ID_LEGACY = "<legacy>";
|
|
219
|
-
var AGENT_UNKNOWN = "<unknown>";
|
|
220
|
-
var DEFAULT_TEXT_LIMITS = {
|
|
221
|
-
user: 0,
|
|
222
|
-
agent: 5e3,
|
|
223
|
-
tool: 200,
|
|
224
|
-
phase: 200
|
|
225
|
-
};
|
|
226
|
-
var EXIT_OK = 0;
|
|
227
|
-
var EXIT_BAD_ARGS = 2;
|
|
228
|
-
var PyFloat = class {
|
|
229
|
-
constructor(value) {
|
|
230
|
-
this.value = value;
|
|
231
|
-
}
|
|
232
|
-
value;
|
|
233
|
-
};
|
|
234
|
-
function _isPlainObject(v) {
|
|
235
|
-
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof PyFloat);
|
|
236
|
-
}
|
|
237
|
-
function _pyJsonStr(s) {
|
|
238
|
-
let out = '"';
|
|
239
|
-
for (const ch of s) {
|
|
240
|
-
const code = ch.codePointAt(0);
|
|
241
|
-
switch (ch) {
|
|
242
|
-
case '"':
|
|
243
|
-
out += '\\"';
|
|
244
|
-
break;
|
|
245
|
-
case "\\":
|
|
246
|
-
out += "\\\\";
|
|
247
|
-
break;
|
|
248
|
-
case "\n":
|
|
249
|
-
out += "\\n";
|
|
250
|
-
break;
|
|
251
|
-
case "\r":
|
|
252
|
-
out += "\\r";
|
|
253
|
-
break;
|
|
254
|
-
case " ":
|
|
255
|
-
out += "\\t";
|
|
256
|
-
break;
|
|
257
|
-
case "\b":
|
|
258
|
-
out += "\\b";
|
|
259
|
-
break;
|
|
260
|
-
case "\f":
|
|
261
|
-
out += "\\f";
|
|
262
|
-
break;
|
|
263
|
-
default:
|
|
264
|
-
if (code < 32) {
|
|
265
|
-
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
266
|
-
} else {
|
|
267
|
-
out += ch;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return out + '"';
|
|
272
|
-
}
|
|
273
|
-
function _pyJsonNum(n) {
|
|
274
|
-
if (!Number.isFinite(n)) {
|
|
275
|
-
if (Number.isNaN(n)) {
|
|
276
|
-
return "NaN";
|
|
277
|
-
}
|
|
278
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
279
|
-
}
|
|
280
|
-
return String(n);
|
|
281
|
-
}
|
|
282
|
-
function _pyJsonFloat(n) {
|
|
283
|
-
if (!Number.isFinite(n)) {
|
|
284
|
-
if (Number.isNaN(n)) {
|
|
285
|
-
return "NaN";
|
|
286
|
-
}
|
|
287
|
-
return n > 0 ? "Infinity" : "-Infinity";
|
|
288
|
-
}
|
|
289
|
-
return Number.isInteger(n) ? `${n}.0` : String(n);
|
|
290
|
-
}
|
|
291
|
-
function _pyJsonScalar(value) {
|
|
292
|
-
if (value === null || value === void 0) {
|
|
293
|
-
return "null";
|
|
294
|
-
}
|
|
295
|
-
if (typeof value === "boolean") {
|
|
296
|
-
return value ? "true" : "false";
|
|
297
|
-
}
|
|
298
|
-
if (value instanceof PyFloat) {
|
|
299
|
-
return _pyJsonFloat(value.value);
|
|
300
|
-
}
|
|
301
|
-
if (typeof value === "number") {
|
|
302
|
-
return _pyJsonNum(value);
|
|
303
|
-
}
|
|
304
|
-
if (typeof value === "string") {
|
|
305
|
-
return _pyJsonStr(value);
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
function _pyDumps(value) {
|
|
310
|
-
const scalar = _pyJsonScalar(value);
|
|
311
|
-
if (scalar !== null) {
|
|
312
|
-
return scalar;
|
|
313
|
-
}
|
|
314
|
-
if (Array.isArray(value)) {
|
|
315
|
-
return `[${value.map((v) => _pyDumps(v)).join(", ")}]`;
|
|
316
|
-
}
|
|
317
|
-
if (_isPlainObject(value)) {
|
|
318
|
-
const keys = Object.keys(value);
|
|
319
|
-
const parts = keys.map((k) => `${_pyJsonStr(k)}: ${_pyDumps(value[k])}`);
|
|
320
|
-
return `{${parts.join(", ")}}`;
|
|
321
|
-
}
|
|
322
|
-
return _pyJsonStr(String(value));
|
|
323
|
-
}
|
|
324
|
-
function _pyDumpsIndent2(value, depth = 0) {
|
|
325
|
-
const scalar = _pyJsonScalar(value);
|
|
326
|
-
if (scalar !== null) {
|
|
327
|
-
return scalar;
|
|
328
|
-
}
|
|
329
|
-
const pad = " ".repeat(2 * (depth + 1));
|
|
330
|
-
const closePad = " ".repeat(2 * depth);
|
|
331
|
-
if (Array.isArray(value)) {
|
|
332
|
-
if (value.length === 0) {
|
|
333
|
-
return "[]";
|
|
334
|
-
}
|
|
335
|
-
const items = value.map((v) => pad + _pyDumpsIndent2(v, depth + 1));
|
|
336
|
-
return `[
|
|
337
|
-
${items.join(",\n")}
|
|
338
|
-
${closePad}]`;
|
|
339
|
-
}
|
|
340
|
-
if (_isPlainObject(value)) {
|
|
341
|
-
const keys = Object.keys(value);
|
|
342
|
-
if (keys.length === 0) {
|
|
343
|
-
return "{}";
|
|
344
|
-
}
|
|
345
|
-
const items = keys.map(
|
|
346
|
-
(k) => `${pad}${_pyJsonStr(k)}: ${_pyDumpsIndent2(value[k], depth + 1)}`
|
|
347
|
-
);
|
|
348
|
-
return `{
|
|
349
|
-
${items.join(",\n")}
|
|
350
|
-
${closePad}}`;
|
|
351
|
-
}
|
|
352
|
-
return _pyJsonStr(String(value));
|
|
353
|
-
}
|
|
354
|
-
function _strip2(s) {
|
|
355
|
-
return s.replace(/^\s+/u, "").replace(/\s+$/u, "");
|
|
356
|
-
}
|
|
357
|
-
function _pyLen2(s) {
|
|
358
|
-
let n = 0;
|
|
359
|
-
for (const _ of s) {
|
|
360
|
-
n += 1;
|
|
361
|
-
}
|
|
362
|
-
return n;
|
|
363
|
-
}
|
|
364
|
-
function _pySliceTo(s, n) {
|
|
365
|
-
if (n <= 0) {
|
|
366
|
-
return "";
|
|
367
|
-
}
|
|
368
|
-
let out = "";
|
|
369
|
-
let i = 0;
|
|
370
|
-
for (const ch of s) {
|
|
371
|
-
if (i >= n) {
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
out += ch;
|
|
375
|
-
i += 1;
|
|
376
|
-
}
|
|
377
|
-
return out;
|
|
378
|
-
}
|
|
379
|
-
function _ljust(s, width) {
|
|
380
|
-
const pad = width - _pyLen2(s);
|
|
381
|
-
return pad > 0 ? s + " ".repeat(pad) : s;
|
|
382
|
-
}
|
|
383
|
-
function _pyIntFromAny(value) {
|
|
384
|
-
if (typeof value === "number") {
|
|
385
|
-
return Math.trunc(value);
|
|
386
|
-
}
|
|
387
|
-
if (typeof value === "boolean") {
|
|
388
|
-
return value ? 1 : 0;
|
|
389
|
-
}
|
|
390
|
-
if (typeof value === "string") {
|
|
391
|
-
const parsed = Number.parseInt(value, 10);
|
|
392
|
-
return Number.isNaN(parsed) ? 0 : parsed;
|
|
393
|
-
}
|
|
394
|
-
return 0;
|
|
395
|
-
}
|
|
396
|
-
function _pyRound(value, ndigits = 0) {
|
|
397
|
-
if (!Number.isFinite(value)) {
|
|
398
|
-
return value;
|
|
399
|
-
}
|
|
400
|
-
const factor = 10 ** ndigits;
|
|
401
|
-
const scaled = value * factor;
|
|
402
|
-
const floor = Math.floor(scaled);
|
|
403
|
-
const diff = scaled - floor;
|
|
404
|
-
let rounded;
|
|
405
|
-
const EPS = 1e-9;
|
|
406
|
-
if (Math.abs(diff - 0.5) < EPS) {
|
|
407
|
-
rounded = floor % 2 === 0 ? floor : floor + 1;
|
|
408
|
-
} else {
|
|
409
|
-
rounded = Math.round(scaled);
|
|
410
|
-
}
|
|
411
|
-
return rounded / factor;
|
|
412
|
-
}
|
|
413
|
-
function file_path() {
|
|
414
|
-
return process.env.AGENT_CHAT_HISTORY_FILE || DEFAULT_FILE;
|
|
415
|
-
}
|
|
416
|
-
function _now() {
|
|
417
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
|
|
418
|
-
}
|
|
419
|
-
function fingerprint(value) {
|
|
420
|
-
const normalized = _strip2((value || "").replace(_WS_RE, " "));
|
|
421
|
-
return createHash("sha256").update(normalized, "utf-8").digest("hex");
|
|
422
|
-
}
|
|
423
|
-
function derive_session_tag(session_id) {
|
|
424
|
-
if (!session_id) {
|
|
425
|
-
return SESSION_ID_UNKNOWN;
|
|
426
|
-
}
|
|
427
|
-
return fingerprint(session_id).slice(0, SESSION_ID_LEN);
|
|
428
|
-
}
|
|
429
|
-
function _isFile(p) {
|
|
430
|
-
try {
|
|
431
|
-
return fs2.statSync(p).isFile();
|
|
432
|
-
} catch {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
function _statSize(p) {
|
|
437
|
-
try {
|
|
438
|
-
return fs2.statSync(p).size;
|
|
439
|
-
} catch {
|
|
440
|
-
return -1;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function _preview(msg, n = 80) {
|
|
444
|
-
const flat = _strip2((msg || "").replace(_WS_RE, " "));
|
|
445
|
-
return _pySliceTo(flat, n);
|
|
446
|
-
}
|
|
447
|
-
function _extract_text(obj) {
|
|
448
|
-
let text = obj.text;
|
|
449
|
-
if (typeof text !== "string" || !text) {
|
|
450
|
-
const payload = obj.payload;
|
|
451
|
-
if (_isPlainObject(payload)) {
|
|
452
|
-
text = payload.text;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return typeof text === "string" ? text : "";
|
|
456
|
-
}
|
|
457
|
-
function _summarize_session(head, tail, total, n = 60) {
|
|
458
|
-
const seen = /* @__PURE__ */ new Set();
|
|
459
|
-
const sample = [];
|
|
460
|
-
for (const e of [...head, ...tail]) {
|
|
461
|
-
if (seen.has(e)) {
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
seen.add(e);
|
|
465
|
-
sample.push(e);
|
|
466
|
-
}
|
|
467
|
-
const user_texts = sample.filter((e) => e.t === "user" && _extract_text(e)).map((e) => _extract_text(e));
|
|
468
|
-
if (user_texts.length > 0) {
|
|
469
|
-
const first = _preview(user_texts[0], n);
|
|
470
|
-
if (user_texts.length > 1 && user_texts[user_texts.length - 1] !== user_texts[0]) {
|
|
471
|
-
const last = _preview(user_texts[user_texts.length - 1], n);
|
|
472
|
-
return `${first} \u2192 ${last}`;
|
|
473
|
-
}
|
|
474
|
-
return first;
|
|
475
|
-
}
|
|
476
|
-
const counts = /* @__PURE__ */ new Map();
|
|
477
|
-
for (const e of sample) {
|
|
478
|
-
const k = typeof e.t === "string" ? e.t : "?";
|
|
479
|
-
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
480
|
-
}
|
|
481
|
-
const ordered = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
482
|
-
const mix = ordered.map(([k, v]) => `${k}\xD7${v}`).join(" ");
|
|
483
|
-
return `(${total} entries \u2014 no user prompts; t-mix: ${mix})`;
|
|
484
|
-
}
|
|
485
|
-
function _session_tag_enabled() {
|
|
486
|
-
return (process.env.AGENT_CHAT_HISTORY_SESSION_TAG ?? "true").trim().toLowerCase() !== "false";
|
|
487
|
-
}
|
|
488
|
-
function _readLines(p) {
|
|
489
|
-
const raw = fs2.readFileSync(p, "utf-8");
|
|
490
|
-
if (raw === "") {
|
|
491
|
-
return [];
|
|
492
|
-
}
|
|
493
|
-
const parts = raw.split("\n");
|
|
494
|
-
return parts;
|
|
495
|
-
}
|
|
496
|
-
function _last_body_session_id(p) {
|
|
497
|
-
const target = p ?? file_path();
|
|
498
|
-
if (!_isFile(target) || _statSize(target) === 0) {
|
|
499
|
-
return SESSION_ID_UNKNOWN;
|
|
500
|
-
}
|
|
501
|
-
let lines;
|
|
502
|
-
try {
|
|
503
|
-
lines = _readLines(target);
|
|
504
|
-
} catch {
|
|
505
|
-
return SESSION_ID_UNKNOWN;
|
|
506
|
-
}
|
|
507
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
508
|
-
const line = _strip2(lines[i] ?? "");
|
|
509
|
-
if (!line) {
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
let obj;
|
|
513
|
-
try {
|
|
514
|
-
obj = JSON.parse(line);
|
|
515
|
-
} catch {
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
if (!_isPlainObject(obj) || obj.t === "header") {
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
const sid = obj.s;
|
|
522
|
-
if (typeof sid === "string" && sid) {
|
|
523
|
-
return sid;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return SESSION_ID_UNKNOWN;
|
|
527
|
-
}
|
|
528
|
-
function read_header(p) {
|
|
529
|
-
const target = p ?? file_path();
|
|
530
|
-
if (!_isFile(target) || _statSize(target) === 0) {
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
let first;
|
|
534
|
-
try {
|
|
535
|
-
const raw = fs2.readFileSync(target, "utf-8");
|
|
536
|
-
first = _strip2(raw.split("\n", 1)[0] ?? "");
|
|
537
|
-
} catch {
|
|
538
|
-
return null;
|
|
539
|
-
}
|
|
540
|
-
if (!first) {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
try {
|
|
544
|
-
const obj = JSON.parse(first);
|
|
545
|
-
if (!(_isPlainObject(obj) && obj.t === "header")) {
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
return obj;
|
|
549
|
-
} catch {
|
|
550
|
-
return null;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function _build_header(freq) {
|
|
554
|
-
return { t: "header", v: SCHEMA_VERSION, started: _now(), freq };
|
|
555
|
-
}
|
|
556
|
-
function init(freq = "per_phase", options = {}) {
|
|
557
|
-
if (!VALID_FREQS.has(freq)) {
|
|
558
|
-
throw new ValueError(`freq must be one of ${_pyReprSortedList(VALID_FREQS)}`);
|
|
559
|
-
}
|
|
560
|
-
const target = options.path ?? file_path();
|
|
561
|
-
const header = _build_header(freq);
|
|
562
|
-
if (_is_replay_mode()) {
|
|
563
|
-
return header;
|
|
564
|
-
}
|
|
565
|
-
fs2.mkdirSync(path.dirname(target), { recursive: true });
|
|
566
|
-
fs2.writeFileSync(target, _pyDumps(header) + "\n", { encoding: "utf-8" });
|
|
567
|
-
return header;
|
|
568
|
-
}
|
|
569
|
-
function migrate_header(p, options = {}) {
|
|
570
|
-
const target = p ?? file_path();
|
|
571
|
-
const existing = read_header(target);
|
|
572
|
-
if (existing === null) {
|
|
573
|
-
return null;
|
|
574
|
-
}
|
|
575
|
-
let current_v;
|
|
576
|
-
try {
|
|
577
|
-
current_v = _pyIntFromAny(existing.v ?? 0);
|
|
578
|
-
} catch {
|
|
579
|
-
current_v = 0;
|
|
580
|
-
}
|
|
581
|
-
if (current_v >= SCHEMA_VERSION) {
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
let chosen_freq = options.freq || (typeof existing.freq === "string" ? existing.freq : "") || "per_phase";
|
|
585
|
-
if (!VALID_FREQS.has(chosen_freq)) {
|
|
586
|
-
chosen_freq = "per_phase";
|
|
587
|
-
}
|
|
588
|
-
const new_header = _build_header(chosen_freq);
|
|
589
|
-
if (typeof existing.started === "string") {
|
|
590
|
-
new_header.started = existing.started;
|
|
591
|
-
}
|
|
592
|
-
const raw = fs2.readFileSync(target, "utf-8");
|
|
593
|
-
const lines = _pySplitlines(raw);
|
|
594
|
-
if (lines.length === 0) {
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
lines[0] = _pyDumps(new_header);
|
|
598
|
-
_atomic_write_text(target, lines.join("\n") + "\n");
|
|
599
|
-
return new_header;
|
|
600
|
-
}
|
|
601
|
-
function _pySplitlines(text) {
|
|
602
|
-
if (text === "") {
|
|
603
|
-
return [];
|
|
604
|
-
}
|
|
605
|
-
const out = [];
|
|
606
|
-
let cur = "";
|
|
607
|
-
for (let i = 0; i < text.length; i++) {
|
|
608
|
-
const ch = text[i];
|
|
609
|
-
if (ch === "\n") {
|
|
610
|
-
out.push(cur);
|
|
611
|
-
cur = "";
|
|
612
|
-
} else if (ch === "\r") {
|
|
613
|
-
out.push(cur);
|
|
614
|
-
cur = "";
|
|
615
|
-
if (text[i + 1] === "\n") {
|
|
616
|
-
i += 1;
|
|
617
|
-
}
|
|
618
|
-
} else {
|
|
619
|
-
cur += ch;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
if (cur !== "") {
|
|
623
|
-
out.push(cur);
|
|
624
|
-
}
|
|
625
|
-
return out;
|
|
626
|
-
}
|
|
627
|
-
function append(entry, options = {}) {
|
|
628
|
-
if (!_isPlainObject(entry) || !entry.t) {
|
|
629
|
-
throw new ValueError("entry must be a dict with non-empty 't' key");
|
|
630
|
-
}
|
|
631
|
-
if (entry.t === "header") {
|
|
632
|
-
throw new ValueError("use init() to write the header, not append()");
|
|
633
|
-
}
|
|
634
|
-
const target = options.path ?? file_path();
|
|
635
|
-
if (!("ts" in entry)) {
|
|
636
|
-
entry.ts = _now();
|
|
637
|
-
}
|
|
638
|
-
const session = options.session;
|
|
639
|
-
if (session !== void 0 && session !== null) {
|
|
640
|
-
entry.s = session;
|
|
641
|
-
} else if (!("s" in entry) && _session_tag_enabled()) {
|
|
642
|
-
entry.s = _last_body_session_id(target);
|
|
643
|
-
}
|
|
644
|
-
if (_is_replay_mode()) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
fs2.appendFileSync(target, _pyDumps(entry) + "\n", { encoding: "utf-8" });
|
|
648
|
-
}
|
|
649
|
-
function _atomic_write_text(target, text) {
|
|
650
|
-
if (_is_replay_mode()) {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const suffix = path.extname(target);
|
|
654
|
-
const uniq = `${process.pid}.${createHash("sha256").update(`${process.pid}:${Date.now()}:${Math.random()}`).digest("hex").slice(0, 8)}`;
|
|
655
|
-
const tmp = target.slice(0, target.length - suffix.length) + `${suffix}.${uniq}.tmp`;
|
|
656
|
-
try {
|
|
657
|
-
fs2.writeFileSync(tmp, text, { encoding: "utf-8" });
|
|
658
|
-
fs2.renameSync(tmp, target);
|
|
659
|
-
} catch (exc) {
|
|
660
|
-
try {
|
|
661
|
-
fs2.unlinkSync(tmp);
|
|
662
|
-
} catch {
|
|
663
|
-
}
|
|
664
|
-
throw exc;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
function _normalize_entries(entries) {
|
|
668
|
-
const out = [];
|
|
669
|
-
for (const raw of entries || []) {
|
|
670
|
-
if (!_isPlainObject(raw) || !raw.t) {
|
|
671
|
-
throw new ValueError("each entry must be a dict with non-empty 't' key");
|
|
672
|
-
}
|
|
673
|
-
if (raw.t === "header") {
|
|
674
|
-
throw new ValueError("entries must not contain headers");
|
|
675
|
-
}
|
|
676
|
-
const e = { ...raw };
|
|
677
|
-
if (!("ts" in e)) {
|
|
678
|
-
e.ts = _now();
|
|
679
|
-
}
|
|
680
|
-
out.push(e);
|
|
681
|
-
}
|
|
682
|
-
return out;
|
|
683
|
-
}
|
|
684
|
-
function reset_with_entries(entries, freq = "per_phase", options = {}) {
|
|
685
|
-
if (!VALID_FREQS.has(freq)) {
|
|
686
|
-
throw new ValueError(`freq must be one of ${_pyReprSortedList(VALID_FREQS)}`);
|
|
687
|
-
}
|
|
688
|
-
const target = options.path ?? file_path();
|
|
689
|
-
const header = _build_header(freq);
|
|
690
|
-
const body = _normalize_entries(entries);
|
|
691
|
-
fs2.mkdirSync(path.dirname(target), { recursive: true });
|
|
692
|
-
const lines = [_pyDumps(header), ...body.map((e) => _pyDumps(e))];
|
|
693
|
-
_atomic_write_text(target, lines.join("\n") + "\n");
|
|
694
|
-
return header;
|
|
695
|
-
}
|
|
696
|
-
function prepend_entries(entries, options = {}) {
|
|
697
|
-
const target = options.path ?? file_path();
|
|
698
|
-
if (!_isFile(target)) {
|
|
699
|
-
throw new FileNotFoundError(`no file at ${target}`);
|
|
700
|
-
}
|
|
701
|
-
const existing = _readLinesKeepNewline(target);
|
|
702
|
-
if (existing.length === 0) {
|
|
703
|
-
throw new ValueError(`empty file at ${target}`);
|
|
704
|
-
}
|
|
705
|
-
const header_line = existing[0];
|
|
706
|
-
const body = existing.slice(1);
|
|
707
|
-
const new_lines = _normalize_entries(entries).map((e) => _pyDumps(e) + "\n");
|
|
708
|
-
_atomic_write_text(target, header_line + new_lines.join("") + body.join(""));
|
|
709
|
-
return new_lines.length;
|
|
710
|
-
}
|
|
711
|
-
function _readLinesKeepNewline(p) {
|
|
712
|
-
const raw = fs2.readFileSync(p, "utf-8");
|
|
713
|
-
if (raw === "") {
|
|
714
|
-
return [];
|
|
715
|
-
}
|
|
716
|
-
const out = [];
|
|
717
|
-
let start = 0;
|
|
718
|
-
for (let i = 0; i < raw.length; i++) {
|
|
719
|
-
if (raw[i] === "\n") {
|
|
720
|
-
out.push(raw.slice(start, i + 1));
|
|
721
|
-
start = i + 1;
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
if (start < raw.length) {
|
|
725
|
-
out.push(raw.slice(start));
|
|
726
|
-
}
|
|
727
|
-
return out;
|
|
728
|
-
}
|
|
729
|
-
function clear(options = {}) {
|
|
730
|
-
if (_is_replay_mode()) {
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const target = options.path ?? file_path();
|
|
734
|
-
if (fs2.existsSync(target)) {
|
|
735
|
-
fs2.unlinkSync(target);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
function read_entries(options = {}) {
|
|
739
|
-
return read_entries_with_refs(options).map((p) => p.entry);
|
|
740
|
-
}
|
|
741
|
-
function read_entries_with_refs(options = {}) {
|
|
742
|
-
const target = options.path ?? file_path();
|
|
743
|
-
const last = options.last ?? null;
|
|
744
|
-
const session = options.session ?? null;
|
|
745
|
-
const agent = options.agent ?? null;
|
|
746
|
-
if (!_isFile(target)) {
|
|
747
|
-
return [];
|
|
748
|
-
}
|
|
749
|
-
let pairs = [];
|
|
750
|
-
const lines = _readLines(target);
|
|
751
|
-
let ref = 0;
|
|
752
|
-
for (let i = 0; i < lines.length; i++) {
|
|
753
|
-
const line = _strip2(lines[i] ?? "");
|
|
754
|
-
if (!line) {
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
let obj;
|
|
758
|
-
try {
|
|
759
|
-
obj = JSON.parse(line);
|
|
760
|
-
} catch {
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
if (i === 0 && _isPlainObject(obj) && obj.t === "header") {
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (_isPlainObject(obj)) {
|
|
767
|
-
pairs.push({ entry: obj, ref });
|
|
768
|
-
ref += 1;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
if (session !== null) {
|
|
772
|
-
pairs = pairs.filter((p) => p.entry.s === session);
|
|
773
|
-
}
|
|
774
|
-
if (agent !== null) {
|
|
775
|
-
if (agent === AGENT_UNKNOWN) {
|
|
776
|
-
pairs = pairs.filter((p) => !p.entry.agent);
|
|
777
|
-
} else {
|
|
778
|
-
pairs = pairs.filter((p) => p.entry.agent === agent);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (last !== null && last >= 0) {
|
|
782
|
-
pairs = pairs.slice(pairs.length - last);
|
|
783
|
-
}
|
|
784
|
-
return pairs;
|
|
785
|
-
}
|
|
786
|
-
function read_entries_for_current(options = {}) {
|
|
787
|
-
const target = options.path ?? file_path();
|
|
788
|
-
const last = options.last ?? null;
|
|
789
|
-
const kill = (process.env.AGENT_CHAT_HISTORY_SESSION_FILTER ?? "true").trim().toLowerCase();
|
|
790
|
-
if (kill === "false") {
|
|
791
|
-
return read_entries({ last, path: target, session: null });
|
|
792
|
-
}
|
|
793
|
-
return read_entries({ last, path: target, session: _last_body_session_id(target) });
|
|
794
|
-
}
|
|
795
|
-
function list_sessions(options = {}) {
|
|
796
|
-
const target = options.path ?? file_path();
|
|
797
|
-
const summary = options.summary ?? false;
|
|
798
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
799
|
-
const _bucket = (sid) => {
|
|
800
|
-
let b = buckets.get(sid);
|
|
801
|
-
if (b === void 0) {
|
|
802
|
-
b = { id: sid, count: 0, first_ts: null, last_ts: null, preview: "", agents: [] };
|
|
803
|
-
if (summary) {
|
|
804
|
-
b._head = [];
|
|
805
|
-
b._tail = [];
|
|
806
|
-
}
|
|
807
|
-
buckets.set(sid, b);
|
|
808
|
-
}
|
|
809
|
-
return b;
|
|
810
|
-
};
|
|
811
|
-
if (_isFile(target)) {
|
|
812
|
-
const lines = _readLines(target);
|
|
813
|
-
for (let i = 0; i < lines.length; i++) {
|
|
814
|
-
const line = _strip2(lines[i] ?? "");
|
|
815
|
-
if (!line) {
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
|
-
let obj;
|
|
819
|
-
try {
|
|
820
|
-
obj = JSON.parse(line);
|
|
821
|
-
} catch {
|
|
822
|
-
continue;
|
|
823
|
-
}
|
|
824
|
-
if (!_isPlainObject(obj)) {
|
|
825
|
-
continue;
|
|
826
|
-
}
|
|
827
|
-
if (i === 0 && obj.t === "header") {
|
|
828
|
-
continue;
|
|
829
|
-
}
|
|
830
|
-
const rawSid = obj.s;
|
|
831
|
-
const sid = typeof rawSid === "string" && rawSid ? rawSid : SESSION_ID_LEGACY;
|
|
832
|
-
const b = _bucket(sid);
|
|
833
|
-
b.count += 1;
|
|
834
|
-
const agent = typeof obj.agent === "string" ? obj.agent : null;
|
|
835
|
-
const tag = agent ? agent : AGENT_UNKNOWN;
|
|
836
|
-
if (!b.agents.includes(tag)) {
|
|
837
|
-
b.agents.push(tag);
|
|
838
|
-
}
|
|
839
|
-
const ts = obj.ts;
|
|
840
|
-
if (typeof ts === "string" && ts) {
|
|
841
|
-
if (b.first_ts === null || ts < b.first_ts) {
|
|
842
|
-
b.first_ts = ts;
|
|
843
|
-
}
|
|
844
|
-
if (b.last_ts === null || ts > b.last_ts) {
|
|
845
|
-
b.last_ts = ts;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
if (summary) {
|
|
849
|
-
if (b._head.length < 5) {
|
|
850
|
-
b._head.push(obj);
|
|
851
|
-
}
|
|
852
|
-
const tail = b._tail;
|
|
853
|
-
tail.push(obj);
|
|
854
|
-
if (tail.length > 5) {
|
|
855
|
-
tail.shift();
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
if (!b.preview || b._preview_from !== "user") {
|
|
859
|
-
if (obj.t === "user") {
|
|
860
|
-
const payload = _isPlainObject(obj.payload) ? obj.payload : {};
|
|
861
|
-
const text = obj.text || payload.text || "";
|
|
862
|
-
if (typeof text === "string" && text) {
|
|
863
|
-
b.preview = _preview(text);
|
|
864
|
-
b._preview_from = "user";
|
|
865
|
-
}
|
|
866
|
-
} else if (!b.preview) {
|
|
867
|
-
const text = obj.text || "";
|
|
868
|
-
if (typeof text === "string" && text) {
|
|
869
|
-
b.preview = _preview(text);
|
|
870
|
-
b._preview_from = "any";
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
const out = [];
|
|
877
|
-
for (const b of buckets.values()) {
|
|
878
|
-
delete b._preview_from;
|
|
879
|
-
b.agents = [...b.agents].sort(_pyStrCompare);
|
|
880
|
-
if (summary) {
|
|
881
|
-
const head = b._head ?? [];
|
|
882
|
-
const tail = b._tail ?? [];
|
|
883
|
-
delete b._head;
|
|
884
|
-
delete b._tail;
|
|
885
|
-
b.summary = _summarize_session(head, tail, b.count);
|
|
886
|
-
}
|
|
887
|
-
out.push(b);
|
|
888
|
-
}
|
|
889
|
-
out.sort((a, b) => _pyStrCompare(b.last_ts || "", a.last_ts || ""));
|
|
890
|
-
return out;
|
|
891
|
-
}
|
|
892
|
-
function status(options = {}) {
|
|
893
|
-
const target = options.path ?? file_path();
|
|
894
|
-
if (!_isFile(target)) {
|
|
895
|
-
return { exists: false, path: target };
|
|
896
|
-
}
|
|
897
|
-
const header = read_header(target);
|
|
898
|
-
let entry_count = 0;
|
|
899
|
-
const per_agent = /* @__PURE__ */ new Map();
|
|
900
|
-
const lines = _readLines(target);
|
|
901
|
-
for (let i = 0; i < lines.length; i++) {
|
|
902
|
-
const line = _strip2(lines[i] ?? "");
|
|
903
|
-
if (!line) {
|
|
904
|
-
continue;
|
|
905
|
-
}
|
|
906
|
-
let obj;
|
|
907
|
-
try {
|
|
908
|
-
obj = JSON.parse(line);
|
|
909
|
-
} catch {
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
912
|
-
if (!_isPlainObject(obj)) {
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
if (i === 0 && obj.t === "header") {
|
|
916
|
-
continue;
|
|
917
|
-
}
|
|
918
|
-
entry_count += 1;
|
|
919
|
-
const agent = typeof obj.agent === "string" ? obj.agent : null;
|
|
920
|
-
const tag = agent ? agent : AGENT_UNKNOWN;
|
|
921
|
-
per_agent.set(tag, (per_agent.get(tag) ?? 0) + 1);
|
|
922
|
-
}
|
|
923
|
-
const size = _statSize(target);
|
|
924
|
-
const sortedAgents = [...per_agent.entries()].sort((a, b) => _pyStrCompare(a[0], b[0]));
|
|
925
|
-
const per_agent_obj = {};
|
|
926
|
-
for (const [k, v] of sortedAgents) {
|
|
927
|
-
per_agent_obj[k] = v;
|
|
928
|
-
}
|
|
929
|
-
const agents = { total: per_agent.size, per_agent: per_agent_obj };
|
|
930
|
-
return {
|
|
931
|
-
exists: true,
|
|
932
|
-
path: target,
|
|
933
|
-
size_bytes: size,
|
|
934
|
-
size_kb: new PyFloat(_pyRound(size / 1024, 1)),
|
|
935
|
-
entries: entry_count,
|
|
936
|
-
header,
|
|
937
|
-
agents
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
function _pyStrCompare(a, b) {
|
|
941
|
-
if (a < b) {
|
|
942
|
-
return -1;
|
|
943
|
-
}
|
|
944
|
-
if (a > b) {
|
|
945
|
-
return 1;
|
|
946
|
-
}
|
|
947
|
-
return 0;
|
|
948
|
-
}
|
|
949
|
-
function _load_chat_history_section(settings_path) {
|
|
950
|
-
const data = load_agent_settings({ project_path: settings_path });
|
|
951
|
-
const section = data.chat_history;
|
|
952
|
-
return _isPlainObject(section) ? section : null;
|
|
953
|
-
}
|
|
954
|
-
function _read_chat_history_enabled(settings_path) {
|
|
955
|
-
const section = _load_chat_history_section(settings_path);
|
|
956
|
-
if (section === null) {
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
return _pyBool(section.enabled ?? false);
|
|
960
|
-
}
|
|
961
|
-
function _pyBool(v) {
|
|
962
|
-
if (v === null || v === void 0 || v === false) {
|
|
963
|
-
return false;
|
|
964
|
-
}
|
|
965
|
-
if (v === true) {
|
|
966
|
-
return true;
|
|
967
|
-
}
|
|
968
|
-
if (typeof v === "number") {
|
|
969
|
-
return v !== 0;
|
|
970
|
-
}
|
|
971
|
-
if (typeof v === "string") {
|
|
972
|
-
return v.length > 0;
|
|
973
|
-
}
|
|
974
|
-
if (Array.isArray(v)) {
|
|
975
|
-
return v.length > 0;
|
|
976
|
-
}
|
|
977
|
-
if (_isPlainObject(v)) {
|
|
978
|
-
return Object.keys(v).length > 0;
|
|
979
|
-
}
|
|
980
|
-
return true;
|
|
981
|
-
}
|
|
982
|
-
var VALID_HOOK_EVENTS = [
|
|
983
|
-
"session_start",
|
|
984
|
-
"session_end",
|
|
985
|
-
"user_prompt",
|
|
986
|
-
"agent_response",
|
|
987
|
-
"tool_use",
|
|
988
|
-
"phase",
|
|
989
|
-
"stop"
|
|
990
|
-
];
|
|
991
|
-
var VALID_HOOK_EVENTS_SET = new Set(VALID_HOOK_EVENTS);
|
|
992
|
-
var HOOK_EVENT_ENTRY_TYPE = {
|
|
993
|
-
user_prompt: "user",
|
|
994
|
-
agent_response: "agent",
|
|
995
|
-
tool_use: "tool",
|
|
996
|
-
phase: "phase",
|
|
997
|
-
stop: "agent",
|
|
998
|
-
session_end: "phase"
|
|
999
|
-
};
|
|
1000
|
-
var CADENCE_EVENTS = {
|
|
1001
|
-
per_turn: /* @__PURE__ */ new Set(["stop", "agent_response", "user_prompt"]),
|
|
1002
|
-
per_phase: /* @__PURE__ */ new Set(["phase", "stop", "user_prompt"]),
|
|
1003
|
-
per_tool: /* @__PURE__ */ new Set(["tool_use"])
|
|
1004
|
-
};
|
|
1005
|
-
var PLATFORM_EVENT_MAP = {
|
|
1006
|
-
claude: {
|
|
1007
|
-
SessionStart: "session_start",
|
|
1008
|
-
UserPromptSubmit: "user_prompt",
|
|
1009
|
-
PostToolUse: "tool_use",
|
|
1010
|
-
Stop: "stop",
|
|
1011
|
-
SessionEnd: "session_end",
|
|
1012
|
-
PreCompact: "phase"
|
|
1013
|
-
},
|
|
1014
|
-
cowork: {
|
|
1015
|
-
SessionStart: "session_start",
|
|
1016
|
-
UserPromptSubmit: "user_prompt",
|
|
1017
|
-
PostToolUse: "tool_use",
|
|
1018
|
-
Stop: "stop",
|
|
1019
|
-
SessionEnd: "session_end",
|
|
1020
|
-
PreCompact: "phase"
|
|
1021
|
-
},
|
|
1022
|
-
augment: {
|
|
1023
|
-
SessionStart: "session_start",
|
|
1024
|
-
Stop: "stop",
|
|
1025
|
-
PostToolUse: "tool_use",
|
|
1026
|
-
SessionEnd: "session_end"
|
|
1027
|
-
},
|
|
1028
|
-
cursor: {
|
|
1029
|
-
sessionStart: "session_start",
|
|
1030
|
-
sessionEnd: "session_end",
|
|
1031
|
-
afterAgentResponse: "agent_response",
|
|
1032
|
-
stop: "stop",
|
|
1033
|
-
postToolUse: "tool_use",
|
|
1034
|
-
beforeSubmitPrompt: "user_prompt"
|
|
1035
|
-
},
|
|
1036
|
-
cline: {
|
|
1037
|
-
TaskStart: "session_start",
|
|
1038
|
-
TaskComplete: "session_end",
|
|
1039
|
-
UserPromptSubmit: "user_prompt",
|
|
1040
|
-
PostToolUse: "tool_use"
|
|
1041
|
-
},
|
|
1042
|
-
windsurf: {
|
|
1043
|
-
pre_user_prompt: "user_prompt",
|
|
1044
|
-
post_cascade_response: "agent_response",
|
|
1045
|
-
post_cascade_response_with_transcript: "agent_response",
|
|
1046
|
-
post_setup_worktree: "phase"
|
|
1047
|
-
},
|
|
1048
|
-
gemini: {
|
|
1049
|
-
SessionStart: "session_start",
|
|
1050
|
-
AfterAgent: "agent_response",
|
|
1051
|
-
AfterTool: "tool_use",
|
|
1052
|
-
SessionEnd: "session_end"
|
|
1053
|
-
},
|
|
1054
|
-
generic: Object.fromEntries(VALID_HOOK_EVENTS.map((ev) => [ev, ev]))
|
|
1055
|
-
};
|
|
1056
|
-
var VALID_PLATFORMS = Object.keys(PLATFORM_EVENT_MAP);
|
|
1057
|
-
function _read_chat_history_frequency(settings_path) {
|
|
1058
|
-
const section = _load_chat_history_section(settings_path);
|
|
1059
|
-
if (section === null) {
|
|
1060
|
-
return "per_phase";
|
|
1061
|
-
}
|
|
1062
|
-
const val = String(section.frequency ?? "per_phase").toLowerCase();
|
|
1063
|
-
return VALID_FREQS.has(val) ? val : "per_phase";
|
|
1064
|
-
}
|
|
1065
|
-
function _read_chat_history_max_sessions(settings_path) {
|
|
1066
|
-
const section = _load_chat_history_section(settings_path);
|
|
1067
|
-
if (section === null) {
|
|
1068
|
-
return DEFAULT_MAX_SESSIONS;
|
|
1069
|
-
}
|
|
1070
|
-
const rawN = section.max_sessions ?? DEFAULT_MAX_SESSIONS;
|
|
1071
|
-
let n;
|
|
1072
|
-
if (typeof rawN === "number" && Number.isFinite(rawN)) {
|
|
1073
|
-
n = Math.trunc(rawN);
|
|
1074
|
-
} else if (typeof rawN === "string") {
|
|
1075
|
-
const parsed = Number.parseInt(rawN, 10);
|
|
1076
|
-
if (Number.isNaN(parsed)) {
|
|
1077
|
-
return DEFAULT_MAX_SESSIONS;
|
|
1078
|
-
}
|
|
1079
|
-
n = parsed;
|
|
1080
|
-
} else {
|
|
1081
|
-
return DEFAULT_MAX_SESSIONS;
|
|
1082
|
-
}
|
|
1083
|
-
return Math.max(1, n);
|
|
1084
|
-
}
|
|
1085
|
-
function _read_text_limits(settings_path) {
|
|
1086
|
-
const out = { ...DEFAULT_TEXT_LIMITS };
|
|
1087
|
-
const section = _load_chat_history_section(settings_path);
|
|
1088
|
-
if (section === null) {
|
|
1089
|
-
return out;
|
|
1090
|
-
}
|
|
1091
|
-
const overrides = section.text_limits;
|
|
1092
|
-
if (!_isPlainObject(overrides)) {
|
|
1093
|
-
return out;
|
|
1094
|
-
}
|
|
1095
|
-
for (const [kind, val] of Object.entries(overrides)) {
|
|
1096
|
-
let n;
|
|
1097
|
-
if (typeof val === "number" && Number.isFinite(val)) {
|
|
1098
|
-
n = Math.trunc(val);
|
|
1099
|
-
} else if (typeof val === "string") {
|
|
1100
|
-
const parsed = Number.parseInt(val, 10);
|
|
1101
|
-
if (Number.isNaN(parsed)) {
|
|
1102
|
-
continue;
|
|
1103
|
-
}
|
|
1104
|
-
n = parsed;
|
|
1105
|
-
} else {
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
out[kind] = Math.max(0, n);
|
|
1109
|
-
}
|
|
1110
|
-
return out;
|
|
1111
|
-
}
|
|
1112
|
-
function _apply_text_limit(text, kind, limits) {
|
|
1113
|
-
if (!text) {
|
|
1114
|
-
return "";
|
|
1115
|
-
}
|
|
1116
|
-
const n = limits[kind] ?? DEFAULT_TEXT_LIMITS[kind] ?? 0;
|
|
1117
|
-
if (n <= 0) {
|
|
1118
|
-
return text;
|
|
1119
|
-
}
|
|
1120
|
-
const flat = _strip2(text.replace(_WS_RE, " "));
|
|
1121
|
-
if (_pyLen2(flat) <= n) {
|
|
1122
|
-
return flat;
|
|
1123
|
-
}
|
|
1124
|
-
return `${_pySliceTo(flat, n)} \u2026 [+${_pyLen2(flat) - n} chars]`;
|
|
1125
|
-
}
|
|
1126
|
-
function prune_sessions(max_sessions = DEFAULT_MAX_SESSIONS, options = {}) {
|
|
1127
|
-
if (max_sessions < 1) {
|
|
1128
|
-
max_sessions = 1;
|
|
1129
|
-
}
|
|
1130
|
-
const target = options.path ?? file_path();
|
|
1131
|
-
if (!_isFile(target)) {
|
|
1132
|
-
return { action: "noop", kept_sessions: 0, dropped_sessions: 0, dropped_entries: 0 };
|
|
1133
|
-
}
|
|
1134
|
-
const lines = _readLinesKeepNewline(target);
|
|
1135
|
-
if (lines.length <= 1) {
|
|
1136
|
-
return { action: "noop", kept_sessions: 0, dropped_sessions: 0, dropped_entries: 0 };
|
|
1137
|
-
}
|
|
1138
|
-
const header_line = lines[0];
|
|
1139
|
-
const body = lines.slice(1);
|
|
1140
|
-
const last_pos = /* @__PURE__ */ new Map();
|
|
1141
|
-
const parsed = [];
|
|
1142
|
-
for (let idx = 0; idx < body.length; idx++) {
|
|
1143
|
-
const line = body[idx];
|
|
1144
|
-
const stripped = _strip2(line);
|
|
1145
|
-
if (!stripped) {
|
|
1146
|
-
continue;
|
|
1147
|
-
}
|
|
1148
|
-
let obj;
|
|
1149
|
-
try {
|
|
1150
|
-
obj = JSON.parse(stripped);
|
|
1151
|
-
} catch {
|
|
1152
|
-
parsed.push([SESSION_ID_LEGACY, line]);
|
|
1153
|
-
last_pos.set(SESSION_ID_LEGACY, idx);
|
|
1154
|
-
continue;
|
|
1155
|
-
}
|
|
1156
|
-
if (!_isPlainObject(obj)) {
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
const sid = typeof obj.s === "string" ? obj.s : SESSION_ID_LEGACY;
|
|
1160
|
-
parsed.push([sid, line]);
|
|
1161
|
-
last_pos.set(sid, idx);
|
|
1162
|
-
}
|
|
1163
|
-
if (last_pos.size <= max_sessions) {
|
|
1164
|
-
return {
|
|
1165
|
-
action: "noop",
|
|
1166
|
-
kept_sessions: last_pos.size,
|
|
1167
|
-
dropped_sessions: 0,
|
|
1168
|
-
dropped_entries: 0
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
const ranked = [...last_pos.entries()].sort((a, b) => b[1] - a[1]);
|
|
1172
|
-
const keep_set = new Set(ranked.slice(0, max_sessions).map(([sid]) => sid));
|
|
1173
|
-
const drop_set = new Set(ranked.slice(max_sessions).map(([sid]) => sid));
|
|
1174
|
-
const kept_lines = parsed.filter(([sid]) => keep_set.has(sid)).map(([, line]) => line);
|
|
1175
|
-
const dropped_entries = parsed.length - kept_lines.length;
|
|
1176
|
-
_atomic_write_text(target, header_line + kept_lines.join(""));
|
|
1177
|
-
return {
|
|
1178
|
-
action: "pruned",
|
|
1179
|
-
kept_sessions: keep_set.size,
|
|
1180
|
-
dropped_sessions: drop_set.size,
|
|
1181
|
-
dropped_entries
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
|
-
function overflow_handle(max_kb, mode = "rotate", options = {}) {
|
|
1185
|
-
if (!VALID_OVERFLOW.has(mode)) {
|
|
1186
|
-
throw new ValueError(`mode must be one of ${_pyReprSortedList(VALID_OVERFLOW)}`);
|
|
1187
|
-
}
|
|
1188
|
-
const target = options.path ?? file_path();
|
|
1189
|
-
if (!_isFile(target) || _statSize(target) <= max_kb * 1024) {
|
|
1190
|
-
return { action: "noop", kept: null, dropped: 0 };
|
|
1191
|
-
}
|
|
1192
|
-
const lines = _readLinesKeepNewline(target);
|
|
1193
|
-
if (lines.length === 0) {
|
|
1194
|
-
return { action: "noop", kept: 0, dropped: 0 };
|
|
1195
|
-
}
|
|
1196
|
-
const header_line = lines[0];
|
|
1197
|
-
const entries = lines.slice(1);
|
|
1198
|
-
if (mode === "rotate") {
|
|
1199
|
-
const budget = max_kb * 1024 - Buffer.byteLength(header_line, "utf-8");
|
|
1200
|
-
const kept = [];
|
|
1201
|
-
let total = 0;
|
|
1202
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1203
|
-
const line = entries[i];
|
|
1204
|
-
const size = Buffer.byteLength(line, "utf-8");
|
|
1205
|
-
if (total + size > budget) {
|
|
1206
|
-
break;
|
|
1207
|
-
}
|
|
1208
|
-
kept.push(line);
|
|
1209
|
-
total += size;
|
|
1210
|
-
}
|
|
1211
|
-
kept.reverse();
|
|
1212
|
-
const dropped = entries.length - kept.length;
|
|
1213
|
-
_atomic_write_text(target, header_line + kept.join(""));
|
|
1214
|
-
return { action: "rotate", kept: kept.length, dropped };
|
|
1215
|
-
}
|
|
1216
|
-
const marker = {
|
|
1217
|
-
t: "needs_condense",
|
|
1218
|
-
ts: _now(),
|
|
1219
|
-
reason: `file exceeded ${max_kb} KB, condense-mode requested`
|
|
1220
|
-
};
|
|
1221
|
-
append(marker, { path: target });
|
|
1222
|
-
return { action: "condense_marked", kept: entries.length, dropped: 0 };
|
|
1223
|
-
}
|
|
1224
|
-
function hook_append(event, options = {}) {
|
|
1225
|
-
if (!VALID_HOOK_EVENTS_SET.has(event)) {
|
|
1226
|
-
throw new ValueError(`event must be one of ${_pyReprSortedListArr(VALID_HOOK_EVENTS)}`);
|
|
1227
|
-
}
|
|
1228
|
-
const dry_run = options.dry_run ?? false;
|
|
1229
|
-
const sp = options.settings_path ?? DEFAULT_SETTINGS_FILE;
|
|
1230
|
-
if (!_read_chat_history_enabled(sp)) {
|
|
1231
|
-
const out = { action: "disabled", event };
|
|
1232
|
-
if (dry_run) {
|
|
1233
|
-
out.dry_run = true;
|
|
1234
|
-
}
|
|
1235
|
-
return out;
|
|
1236
|
-
}
|
|
1237
|
-
const target = options.path ?? file_path();
|
|
1238
|
-
const payload = options.payload ?? {};
|
|
1239
|
-
const session_id = options.session_id ?? null;
|
|
1240
|
-
const s_tag = session_id ? derive_session_tag(session_id) : SESSION_ID_UNKNOWN;
|
|
1241
|
-
const prior_s = _isFile(target) ? _last_body_session_id(target) : SESSION_ID_UNKNOWN;
|
|
1242
|
-
const is_new_session = s_tag !== SESSION_ID_UNKNOWN && prior_s !== SESSION_ID_UNKNOWN && prior_s !== s_tag;
|
|
1243
|
-
if (is_new_session) {
|
|
1244
|
-
process.stderr.write(
|
|
1245
|
-
`chat-history session_rotation event=${event} prior_s=${prior_s} new_s=${s_tag}
|
|
1246
|
-
`
|
|
1247
|
-
);
|
|
1248
|
-
}
|
|
1249
|
-
if (!dry_run) {
|
|
1250
|
-
if (!_isFile(target) || read_header(target) === null) {
|
|
1251
|
-
const freq2 = _read_chat_history_frequency(sp);
|
|
1252
|
-
init(freq2, { path: target });
|
|
1253
|
-
} else {
|
|
1254
|
-
migrate_header(target, { freq: _read_chat_history_frequency(sp) });
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
const _maybe_prune = () => {
|
|
1258
|
-
if (dry_run || !is_new_session) {
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
const max_n = _read_chat_history_max_sessions(sp);
|
|
1262
|
-
try {
|
|
1263
|
-
prune_sessions(max_n, { path: target });
|
|
1264
|
-
} catch (exc) {
|
|
1265
|
-
process.stderr.write(`chat-history prune_failed: ${_excStr(exc)}
|
|
1266
|
-
`);
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
if (event === "session_start") {
|
|
1270
|
-
_maybe_prune();
|
|
1271
|
-
const action = "session_start_noop";
|
|
1272
|
-
const out = { action: dry_run ? "dry_run" : action, event, s: s_tag };
|
|
1273
|
-
if (dry_run) {
|
|
1274
|
-
out.would_action = action;
|
|
1275
|
-
out.dry_run = true;
|
|
1276
|
-
}
|
|
1277
|
-
return out;
|
|
1278
|
-
}
|
|
1279
|
-
if (event === "session_end") {
|
|
1280
|
-
_maybe_prune();
|
|
1281
|
-
const action = "session_end_noop";
|
|
1282
|
-
const out = { action: dry_run ? "dry_run" : action, event, s: s_tag };
|
|
1283
|
-
if (dry_run) {
|
|
1284
|
-
out.would_action = action;
|
|
1285
|
-
out.dry_run = true;
|
|
1286
|
-
}
|
|
1287
|
-
return out;
|
|
1288
|
-
}
|
|
1289
|
-
const freq = _read_chat_history_frequency(sp);
|
|
1290
|
-
if (!(CADENCE_EVENTS[freq] ?? /* @__PURE__ */ new Set()).has(event)) {
|
|
1291
|
-
const out = { action: "skipped_cadence", event, frequency: freq };
|
|
1292
|
-
if (dry_run) {
|
|
1293
|
-
out.dry_run = true;
|
|
1294
|
-
}
|
|
1295
|
-
return out;
|
|
1296
|
-
}
|
|
1297
|
-
const entry_type = HOOK_EVENT_ENTRY_TYPE[event] ?? "agent";
|
|
1298
|
-
const limits = _read_text_limits(sp);
|
|
1299
|
-
const entry = { t: entry_type };
|
|
1300
|
-
const text = String(payload.text ?? "");
|
|
1301
|
-
if (text) {
|
|
1302
|
-
const sliced = _apply_text_limit(text, entry_type, limits);
|
|
1303
|
-
if (sliced) {
|
|
1304
|
-
entry.text = sliced;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
if (event === "tool_use") {
|
|
1308
|
-
const tool = payload.tool;
|
|
1309
|
-
if (tool) {
|
|
1310
|
-
entry.tool = String(tool);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
for (const k of ["agent", "source", "phase", "decision"]) {
|
|
1314
|
-
const v = payload[k];
|
|
1315
|
-
if (v) {
|
|
1316
|
-
entry[k] = String(v);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
if (dry_run) {
|
|
1320
|
-
const preview = { ...entry };
|
|
1321
|
-
preview.s = s_tag;
|
|
1322
|
-
return {
|
|
1323
|
-
action: "dry_run",
|
|
1324
|
-
would_action: "appended",
|
|
1325
|
-
event,
|
|
1326
|
-
type: entry_type,
|
|
1327
|
-
s: s_tag,
|
|
1328
|
-
entry_preview: preview,
|
|
1329
|
-
dry_run: true
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
append(entry, { path: target, session: s_tag });
|
|
1333
|
-
_maybe_prune();
|
|
1334
|
-
return { action: "appended", event, type: entry_type, s: s_tag };
|
|
1335
|
-
}
|
|
1336
|
-
function _extract_augment_conversation(payload) {
|
|
1337
|
-
const conv = payload.conversation;
|
|
1338
|
-
if (!_isPlainObject(conv)) {
|
|
1339
|
-
return ["", ""];
|
|
1340
|
-
}
|
|
1341
|
-
const user = conv.userPrompt;
|
|
1342
|
-
const agent = conv.agentTextResponse;
|
|
1343
|
-
const user_s = typeof user === "string" ? _strip2(user) : "";
|
|
1344
|
-
const agent_s = typeof agent === "string" ? _strip2(agent) : "";
|
|
1345
|
-
return [user_s, agent_s];
|
|
1346
|
-
}
|
|
1347
|
-
function _extract_claude_transcript_response(transcript_path) {
|
|
1348
|
-
if (!transcript_path) {
|
|
1349
|
-
return "";
|
|
1350
|
-
}
|
|
1351
|
-
if (!_isFile(transcript_path)) {
|
|
1352
|
-
return "";
|
|
1353
|
-
}
|
|
1354
|
-
let last_text = "";
|
|
1355
|
-
let lines;
|
|
1356
|
-
try {
|
|
1357
|
-
lines = _readLines(transcript_path);
|
|
1358
|
-
} catch {
|
|
1359
|
-
return "";
|
|
1360
|
-
}
|
|
1361
|
-
for (const rawLine of lines) {
|
|
1362
|
-
const line = _strip2(rawLine);
|
|
1363
|
-
if (!line) {
|
|
1364
|
-
continue;
|
|
1365
|
-
}
|
|
1366
|
-
let obj;
|
|
1367
|
-
try {
|
|
1368
|
-
obj = JSON.parse(line);
|
|
1369
|
-
} catch {
|
|
1370
|
-
continue;
|
|
1371
|
-
}
|
|
1372
|
-
if (!_isPlainObject(obj)) {
|
|
1373
|
-
continue;
|
|
1374
|
-
}
|
|
1375
|
-
if (obj.type !== "assistant") {
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
const msg = obj.message;
|
|
1379
|
-
if (!_isPlainObject(msg)) {
|
|
1380
|
-
continue;
|
|
1381
|
-
}
|
|
1382
|
-
const content = msg.content;
|
|
1383
|
-
if (typeof content === "string") {
|
|
1384
|
-
last_text = content;
|
|
1385
|
-
} else if (Array.isArray(content)) {
|
|
1386
|
-
const parts = [];
|
|
1387
|
-
for (const blk of content) {
|
|
1388
|
-
if (_isPlainObject(blk) && blk.type === "text") {
|
|
1389
|
-
const t = blk.text ?? "";
|
|
1390
|
-
if (typeof t === "string") {
|
|
1391
|
-
parts.push(t);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
if (parts.length > 0) {
|
|
1396
|
-
last_text = parts.join("\n");
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
return _strip2(last_text);
|
|
1401
|
-
}
|
|
1402
|
-
function _extract_cursor_text(payload, event) {
|
|
1403
|
-
if (event === "stop" || event === "agent_response") {
|
|
1404
|
-
const tp = payload.transcript_path ?? payload.transcriptPath;
|
|
1405
|
-
if (typeof tp === "string") {
|
|
1406
|
-
const txt = _extract_claude_transcript_response(tp);
|
|
1407
|
-
if (txt) {
|
|
1408
|
-
return txt;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
return "";
|
|
1413
|
-
}
|
|
1414
|
-
function _extract_cline_text(payload, event) {
|
|
1415
|
-
if (event === "user_prompt") {
|
|
1416
|
-
const v = payload.prompt ?? payload.userPrompt;
|
|
1417
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1418
|
-
return _strip2(v);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
return "";
|
|
1422
|
-
}
|
|
1423
|
-
function _extract_gemini_text(payload, event) {
|
|
1424
|
-
if (event === "agent_response" || event === "stop") {
|
|
1425
|
-
const v = payload.prompt_response ?? payload.promptResponse;
|
|
1426
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1427
|
-
return _strip2(v);
|
|
1428
|
-
}
|
|
1429
|
-
const tp = payload.transcript_path ?? payload.transcriptPath;
|
|
1430
|
-
if (typeof tp === "string") {
|
|
1431
|
-
const txt = _extract_claude_transcript_response(tp);
|
|
1432
|
-
if (txt) {
|
|
1433
|
-
return txt;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
return "";
|
|
1438
|
-
}
|
|
1439
|
-
function _extract_windsurf_text(payload, event) {
|
|
1440
|
-
if (event === "agent_response" || event === "stop") {
|
|
1441
|
-
const info = payload.tool_info ?? payload.toolInfo;
|
|
1442
|
-
if (_isPlainObject(info)) {
|
|
1443
|
-
const v = info.response ?? info.text;
|
|
1444
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1445
|
-
return _strip2(v);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
const tp = payload.transcript_path ?? payload.transcriptPath;
|
|
1449
|
-
if (typeof tp === "string") {
|
|
1450
|
-
const txt = _extract_claude_transcript_response(tp);
|
|
1451
|
-
if (txt) {
|
|
1452
|
-
return txt;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
return "";
|
|
1457
|
-
}
|
|
1458
|
-
function _extract_hook_text(payload, options = {}) {
|
|
1459
|
-
const platform = options.platform ?? null;
|
|
1460
|
-
const event = options.event ?? null;
|
|
1461
|
-
if (platform === "augment") {
|
|
1462
|
-
const [user, agent] = _extract_augment_conversation(payload);
|
|
1463
|
-
if (event === "user_prompt" && user) {
|
|
1464
|
-
return user;
|
|
1465
|
-
}
|
|
1466
|
-
if ((event === "stop" || event === "agent_response") && agent) {
|
|
1467
|
-
return agent;
|
|
1468
|
-
}
|
|
1469
|
-
if (agent) {
|
|
1470
|
-
return agent;
|
|
1471
|
-
}
|
|
1472
|
-
if (user) {
|
|
1473
|
-
return user;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
if ((platform === "claude" || platform === "cowork") && (event === "stop" || event === "agent_response")) {
|
|
1477
|
-
const tp = payload.transcript_path ?? payload.transcriptPath;
|
|
1478
|
-
if (typeof tp === "string") {
|
|
1479
|
-
const txt = _extract_claude_transcript_response(tp);
|
|
1480
|
-
if (txt) {
|
|
1481
|
-
return txt;
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
if (platform === "cursor") {
|
|
1486
|
-
const txt = _extract_cursor_text(payload, event);
|
|
1487
|
-
if (txt) {
|
|
1488
|
-
return txt;
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
if (platform === "cline") {
|
|
1492
|
-
const txt = _extract_cline_text(payload, event);
|
|
1493
|
-
if (txt) {
|
|
1494
|
-
return txt;
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
if (platform === "gemini") {
|
|
1498
|
-
const txt = _extract_gemini_text(payload, event);
|
|
1499
|
-
if (txt) {
|
|
1500
|
-
return txt;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
if (platform === "windsurf") {
|
|
1504
|
-
const txt = _extract_windsurf_text(payload, event);
|
|
1505
|
-
if (txt) {
|
|
1506
|
-
return txt;
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
for (const key of [
|
|
1510
|
-
"prompt",
|
|
1511
|
-
"user_prompt",
|
|
1512
|
-
"first_user_msg",
|
|
1513
|
-
"firstUserMsg",
|
|
1514
|
-
"userMessage",
|
|
1515
|
-
"user_message",
|
|
1516
|
-
"text",
|
|
1517
|
-
"response",
|
|
1518
|
-
"message",
|
|
1519
|
-
"content"
|
|
1520
|
-
]) {
|
|
1521
|
-
const v = payload[key];
|
|
1522
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1523
|
-
return _strip2(v);
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
const tr = payload.tool_response ?? payload.toolResponse;
|
|
1527
|
-
if (_isPlainObject(tr)) {
|
|
1528
|
-
for (const key of ["output", "stdout", "result", "text"]) {
|
|
1529
|
-
const v = tr[key];
|
|
1530
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1531
|
-
return _strip2(v);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
return "";
|
|
1536
|
-
}
|
|
1537
|
-
function _extract_hook_tool(payload) {
|
|
1538
|
-
for (const key of ["tool_name", "toolName", "tool"]) {
|
|
1539
|
-
const v = payload[key];
|
|
1540
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1541
|
-
return _strip2(v);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
return "";
|
|
1545
|
-
}
|
|
1546
|
-
function _extract_hook_event(payload) {
|
|
1547
|
-
for (const key of ["hook_event_name", "event", "eventName", "event_name"]) {
|
|
1548
|
-
const v = payload[key];
|
|
1549
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1550
|
-
return _strip2(v);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
return "";
|
|
1554
|
-
}
|
|
1555
|
-
function _extract_session_id(payload) {
|
|
1556
|
-
for (const key of ["session_id", "sessionId", "task_id", "taskId", "conversation_id", "conversationId"]) {
|
|
1557
|
-
const v = payload[key];
|
|
1558
|
-
if (typeof v === "string" && _strip2(v)) {
|
|
1559
|
-
return _strip2(v);
|
|
1560
|
-
}
|
|
29
|
+
import * as path2 from "node:path";
|
|
30
|
+
import process3 from "node:process";
|
|
31
|
+
|
|
32
|
+
// src/scripts/ai_council/redact_low_impact_entry.ts
|
|
33
|
+
function _pyLen(s) {
|
|
34
|
+
let n = 0;
|
|
35
|
+
for (const _ of s) {
|
|
36
|
+
n += 1;
|
|
1561
37
|
}
|
|
1562
|
-
return
|
|
38
|
+
return n;
|
|
1563
39
|
}
|
|
1564
|
-
function
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
)
|
|
1569
|
-
|
|
1570
|
-
const event_override = options.event_override ?? null;
|
|
1571
|
-
const dry_run = options.dry_run ?? false;
|
|
1572
|
-
const raw = _strip2(raw_json || "");
|
|
1573
|
-
let payload;
|
|
1574
|
-
if (!raw) {
|
|
1575
|
-
payload = {};
|
|
1576
|
-
} else {
|
|
1577
|
-
let parsed;
|
|
1578
|
-
try {
|
|
1579
|
-
parsed = JSON.parse(raw);
|
|
1580
|
-
} catch (exc) {
|
|
1581
|
-
throw new ValueError(`invalid JSON on stdin: ${_excStr(exc)}`);
|
|
1582
|
-
}
|
|
1583
|
-
if (!_isPlainObject(parsed)) {
|
|
1584
|
-
throw new ValueError("stdin JSON must decode to an object");
|
|
40
|
+
function _pySliceHead(s, n) {
|
|
41
|
+
let out = "";
|
|
42
|
+
let i = 0;
|
|
43
|
+
for (const ch of s) {
|
|
44
|
+
if (i >= n) {
|
|
45
|
+
break;
|
|
1585
46
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
let envelope_event = "";
|
|
1589
|
-
if (["schema_version", "platform", "event", "payload"].every((k) => k in payload)) {
|
|
1590
|
-
envelope_event = _strip2(
|
|
1591
|
-
String(payload.native_event || payload.event || "")
|
|
1592
|
-
);
|
|
1593
|
-
const inner = payload.payload;
|
|
1594
|
-
payload = _isPlainObject(inner) ? inner : {};
|
|
1595
|
-
}
|
|
1596
|
-
const raw_event = _strip2(
|
|
1597
|
-
String(event_override || envelope_event || _extract_hook_event(payload) || "")
|
|
1598
|
-
);
|
|
1599
|
-
const event = PLATFORM_EVENT_MAP[platform][raw_event];
|
|
1600
|
-
if (!event) {
|
|
1601
|
-
return { action: "skipped_unmapped_event", platform, raw_event };
|
|
1602
|
-
}
|
|
1603
|
-
const text = _extract_hook_text(payload, { platform, event });
|
|
1604
|
-
const tool = _extract_hook_tool(payload);
|
|
1605
|
-
const session_id = _extract_session_id(payload);
|
|
1606
|
-
let augment_user_prompt = "";
|
|
1607
|
-
if (platform === "augment" && event === "stop") {
|
|
1608
|
-
const [u] = _extract_augment_conversation(payload);
|
|
1609
|
-
augment_user_prompt = u;
|
|
1610
|
-
}
|
|
1611
|
-
const hook_payload = {
|
|
1612
|
-
source: `hook:${platform}:${raw_event}`,
|
|
1613
|
-
agent: platform
|
|
1614
|
-
};
|
|
1615
|
-
if (text && event !== "session_start") {
|
|
1616
|
-
hook_payload.text = text;
|
|
1617
|
-
}
|
|
1618
|
-
if (tool) {
|
|
1619
|
-
hook_payload.tool = tool;
|
|
1620
|
-
}
|
|
1621
|
-
if (augment_user_prompt) {
|
|
1622
|
-
hook_append("user_prompt", {
|
|
1623
|
-
session_id,
|
|
1624
|
-
payload: {
|
|
1625
|
-
text: augment_user_prompt,
|
|
1626
|
-
source: `hook:${platform}:${raw_event}:user`,
|
|
1627
|
-
agent: platform
|
|
1628
|
-
},
|
|
1629
|
-
path: options.path,
|
|
1630
|
-
settings_path: options.settings_path,
|
|
1631
|
-
dry_run
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
return hook_append(event, {
|
|
1635
|
-
session_id,
|
|
1636
|
-
payload: hook_payload,
|
|
1637
|
-
path: options.path,
|
|
1638
|
-
settings_path: options.settings_path,
|
|
1639
|
-
dry_run
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
var ValueError = class extends Error {
|
|
1643
|
-
};
|
|
1644
|
-
var FileNotFoundError = class extends Error {
|
|
1645
|
-
};
|
|
1646
|
-
function _excStr(exc) {
|
|
1647
|
-
if (exc instanceof Error) {
|
|
1648
|
-
return exc.message;
|
|
1649
|
-
}
|
|
1650
|
-
return String(exc);
|
|
1651
|
-
}
|
|
1652
|
-
function _pyRepr2(s) {
|
|
1653
|
-
if (!s.includes("'") || s.includes('"')) {
|
|
1654
|
-
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
47
|
+
out += ch;
|
|
48
|
+
i += 1;
|
|
1655
49
|
}
|
|
1656
|
-
return
|
|
1657
|
-
}
|
|
1658
|
-
function _pyReprSortedList(set) {
|
|
1659
|
-
const items = [...set].sort(_pyStrCompare);
|
|
1660
|
-
return `[${items.map((i) => _pyRepr2(i)).join(", ")}]`;
|
|
1661
|
-
}
|
|
1662
|
-
function _pyReprSortedListArr(arr) {
|
|
1663
|
-
const items = [...arr].sort(_pyStrCompare);
|
|
1664
|
-
return `[${items.map((i) => _pyRepr2(i)).join(", ")}]`;
|
|
1665
|
-
}
|
|
1666
|
-
var PROG = "chat_history.py";
|
|
1667
|
-
var SUBCMDS = [
|
|
1668
|
-
"init",
|
|
1669
|
-
"append",
|
|
1670
|
-
"status",
|
|
1671
|
-
"reset",
|
|
1672
|
-
"prune-sessions",
|
|
1673
|
-
"prepend",
|
|
1674
|
-
"clear",
|
|
1675
|
-
"read",
|
|
1676
|
-
"sessions",
|
|
1677
|
-
"rotate",
|
|
1678
|
-
"hook-append",
|
|
1679
|
-
"hook-dispatch"
|
|
1680
|
-
];
|
|
1681
|
-
function _print(line) {
|
|
1682
|
-
process.stdout.write(line + "\n");
|
|
1683
|
-
}
|
|
1684
|
-
function _eprint(line) {
|
|
1685
|
-
process.stderr.write(line + "\n");
|
|
1686
|
-
}
|
|
1687
|
-
var _TOP_USAGE = `usage: ${PROG} [-h]
|
|
1688
|
-
{${SUBCMDS.join(",")}}
|
|
1689
|
-
...
|
|
1690
|
-
`;
|
|
1691
|
-
function _argError(usage, prog, message) {
|
|
1692
|
-
process.stderr.write(usage);
|
|
1693
|
-
process.stderr.write(`${prog}: error: ${message}
|
|
1694
|
-
`);
|
|
1695
|
-
return EXIT_BAD_ARGS;
|
|
50
|
+
return out;
|
|
1696
51
|
}
|
|
1697
|
-
function
|
|
1698
|
-
|
|
1699
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1700
|
-
const a = argv[i];
|
|
1701
|
-
if (a === "--freq") {
|
|
1702
|
-
freq = argv[i + 1] ?? freq;
|
|
1703
|
-
i += 1;
|
|
1704
|
-
} else if (a.startsWith("--freq=")) {
|
|
1705
|
-
freq = a.slice("--freq=".length);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
if (!VALID_FREQS.has(freq)) {
|
|
1709
|
-
return _argError(
|
|
1710
|
-
`usage: ${PROG} init [-h] [--freq {per_phase,per_tool,per_turn}]
|
|
1711
|
-
`,
|
|
1712
|
-
`${PROG} init`,
|
|
1713
|
-
`argument --freq: invalid choice: ${_pyRepr2(freq)} (choose from 'per_phase', 'per_tool', 'per_turn')`
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
const h = init(freq);
|
|
1717
|
-
_print(_pyDumps(h));
|
|
1718
|
-
return 0;
|
|
52
|
+
function _strip(s) {
|
|
53
|
+
return s.trim();
|
|
1719
54
|
}
|
|
1720
|
-
function
|
|
1721
|
-
|
|
1722
|
-
let session_id = null;
|
|
1723
|
-
let payloadRaw = null;
|
|
1724
|
-
let settings = null;
|
|
1725
|
-
let dry_run = false;
|
|
1726
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1727
|
-
const a = argv[i];
|
|
1728
|
-
if (a === "--event") {
|
|
1729
|
-
event = argv[++i] ?? null;
|
|
1730
|
-
} else if (a.startsWith("--event=")) {
|
|
1731
|
-
event = a.slice("--event=".length);
|
|
1732
|
-
} else if (a === "--session-id") {
|
|
1733
|
-
session_id = argv[++i] ?? null;
|
|
1734
|
-
} else if (a.startsWith("--session-id=")) {
|
|
1735
|
-
session_id = a.slice("--session-id=".length);
|
|
1736
|
-
} else if (a === "--payload") {
|
|
1737
|
-
payloadRaw = argv[++i] ?? null;
|
|
1738
|
-
} else if (a.startsWith("--payload=")) {
|
|
1739
|
-
payloadRaw = a.slice("--payload=".length);
|
|
1740
|
-
} else if (a === "--settings") {
|
|
1741
|
-
settings = argv[++i] ?? null;
|
|
1742
|
-
} else if (a.startsWith("--settings=")) {
|
|
1743
|
-
settings = a.slice("--settings=".length);
|
|
1744
|
-
} else if (a === "--dry-run") {
|
|
1745
|
-
dry_run = true;
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
const _hookAppendUsage = `usage: ${PROG} hook-append [-h] --event
|
|
1749
|
-
{agent_response,phase,session_end,session_start,stop,tool_use,user_prompt}
|
|
1750
|
-
[--session-id SESSION_ID]
|
|
1751
|
-
[--payload PAYLOAD] [--settings SETTINGS]
|
|
1752
|
-
[--dry-run]
|
|
1753
|
-
`;
|
|
1754
|
-
if (event === null) {
|
|
1755
|
-
return _argError(_hookAppendUsage, `${PROG} hook-append`, "the following arguments are required: --event");
|
|
1756
|
-
}
|
|
1757
|
-
if (!VALID_HOOK_EVENTS_SET.has(event)) {
|
|
1758
|
-
const choices = [...VALID_HOOK_EVENTS].sort(_pyStrCompare).map((c) => _pyRepr2(c)).join(", ");
|
|
1759
|
-
return _argError(
|
|
1760
|
-
_hookAppendUsage,
|
|
1761
|
-
`${PROG} hook-append`,
|
|
1762
|
-
`argument --event: invalid choice: ${_pyRepr2(event)} (choose from ${choices})`
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
let payload = {};
|
|
1766
|
-
if (payloadRaw) {
|
|
1767
|
-
let parsed;
|
|
1768
|
-
try {
|
|
1769
|
-
parsed = JSON.parse(payloadRaw);
|
|
1770
|
-
} catch (exc) {
|
|
1771
|
-
_eprint(`error: --payload must be valid JSON: ${_excStr(exc)}`);
|
|
1772
|
-
return EXIT_BAD_ARGS;
|
|
1773
|
-
}
|
|
1774
|
-
if (!_isPlainObject(parsed)) {
|
|
1775
|
-
_eprint("error: --payload must decode to a JSON object");
|
|
1776
|
-
return EXIT_BAD_ARGS;
|
|
1777
|
-
}
|
|
1778
|
-
payload = parsed;
|
|
1779
|
-
}
|
|
1780
|
-
try {
|
|
1781
|
-
const result = hook_append(event, {
|
|
1782
|
-
session_id,
|
|
1783
|
-
payload,
|
|
1784
|
-
settings_path: settings,
|
|
1785
|
-
dry_run
|
|
1786
|
-
});
|
|
1787
|
-
_print(_pyDumps(result));
|
|
1788
|
-
} catch (exc) {
|
|
1789
|
-
if (exc instanceof ValueError) {
|
|
1790
|
-
_eprint(`error: ${exc.message}`);
|
|
1791
|
-
return EXIT_BAD_ARGS;
|
|
1792
|
-
}
|
|
1793
|
-
throw exc;
|
|
1794
|
-
}
|
|
1795
|
-
return EXIT_OK;
|
|
55
|
+
function _reEscape(s) {
|
|
56
|
+
return s.replace(/[.*+?^${}()|[\]\\]/gu, (c) => "\\" + c);
|
|
1796
57
|
}
|
|
1797
|
-
function
|
|
1798
|
-
|
|
1799
|
-
let event = null;
|
|
1800
|
-
let settings = null;
|
|
1801
|
-
let dry_run = false;
|
|
1802
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1803
|
-
const a = argv[i];
|
|
1804
|
-
if (a === "--platform") {
|
|
1805
|
-
platform = argv[++i] ?? null;
|
|
1806
|
-
} else if (a.startsWith("--platform=")) {
|
|
1807
|
-
platform = a.slice("--platform=".length);
|
|
1808
|
-
} else if (a === "--event") {
|
|
1809
|
-
event = argv[++i] ?? null;
|
|
1810
|
-
} else if (a.startsWith("--event=")) {
|
|
1811
|
-
event = a.slice("--event=".length);
|
|
1812
|
-
} else if (a === "--settings") {
|
|
1813
|
-
settings = argv[++i] ?? null;
|
|
1814
|
-
} else if (a.startsWith("--settings=")) {
|
|
1815
|
-
settings = a.slice("--settings=".length);
|
|
1816
|
-
} else if (a === "--dry-run") {
|
|
1817
|
-
dry_run = true;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
|
-
const _hookDispatchUsage = `usage: ${PROG} hook-dispatch [-h] --platform
|
|
1821
|
-
{augment,claude,cline,cowork,cursor,gemini,generic,windsurf}
|
|
1822
|
-
[--event EVENT] [--settings SETTINGS]
|
|
1823
|
-
[--dry-run]
|
|
1824
|
-
`;
|
|
1825
|
-
if (platform === null) {
|
|
1826
|
-
return _argError(
|
|
1827
|
-
_hookDispatchUsage,
|
|
1828
|
-
`${PROG} hook-dispatch`,
|
|
1829
|
-
"the following arguments are required: --platform"
|
|
1830
|
-
);
|
|
1831
|
-
}
|
|
1832
|
-
if (!(platform in PLATFORM_EVENT_MAP)) {
|
|
1833
|
-
const choices = [...VALID_PLATFORMS].sort(_pyStrCompare).map((c) => _pyRepr2(c)).join(", ");
|
|
1834
|
-
return _argError(
|
|
1835
|
-
_hookDispatchUsage,
|
|
1836
|
-
`${PROG} hook-dispatch`,
|
|
1837
|
-
`argument --platform: invalid choice: ${_pyRepr2(platform)} (choose from ${choices})`
|
|
1838
|
-
);
|
|
1839
|
-
}
|
|
1840
|
-
const raw = _readStdin();
|
|
1841
|
-
try {
|
|
1842
|
-
const result = hook_dispatch(platform, raw, {
|
|
1843
|
-
event_override: event,
|
|
1844
|
-
settings_path: settings,
|
|
1845
|
-
dry_run
|
|
1846
|
-
});
|
|
1847
|
-
_print(_pyDumps(result));
|
|
1848
|
-
} catch (exc) {
|
|
1849
|
-
if (exc instanceof ValueError) {
|
|
1850
|
-
_eprint(`error: ${exc.message}`);
|
|
1851
|
-
return EXIT_BAD_ARGS;
|
|
1852
|
-
}
|
|
1853
|
-
throw exc;
|
|
1854
|
-
}
|
|
1855
|
-
return EXIT_OK;
|
|
58
|
+
function _violation(category, snippet, note = "") {
|
|
59
|
+
return { category, snippet, note };
|
|
1856
60
|
}
|
|
1857
|
-
function
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
} else if (a === "--json") {
|
|
1868
|
-
jsonRaw = argv[++i] ?? null;
|
|
1869
|
-
} else if (a.startsWith("--json=")) {
|
|
1870
|
-
jsonRaw = a.slice("--json=".length);
|
|
1871
|
-
} else if (a === "--session-id") {
|
|
1872
|
-
session_id = argv[++i] ?? null;
|
|
1873
|
-
} else if (a.startsWith("--session-id=")) {
|
|
1874
|
-
session_id = a.slice("--session-id=".length);
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
const entry = jsonRaw ? JSON.parse(jsonRaw) : {};
|
|
1878
|
-
if (!("t" in entry)) {
|
|
1879
|
-
entry.t = type_;
|
|
1880
|
-
}
|
|
1881
|
-
if (!entry.t) {
|
|
1882
|
-
_eprint("error: --type or a 't' key in --json is required");
|
|
1883
|
-
return EXIT_BAD_ARGS;
|
|
61
|
+
function _pyRepr(s) {
|
|
62
|
+
const hasSingle = s.includes("'");
|
|
63
|
+
const hasDouble = s.includes('"');
|
|
64
|
+
const quote = hasSingle && !hasDouble ? '"' : "'";
|
|
65
|
+
let body = s.replace(/\\/g, "\\\\");
|
|
66
|
+
body = body.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
67
|
+
if (quote === "'") {
|
|
68
|
+
body = body.replace(/'/g, "\\'");
|
|
69
|
+
} else {
|
|
70
|
+
body = body.replace(/"/g, '\\"');
|
|
1884
71
|
}
|
|
1885
|
-
|
|
1886
|
-
append(entry, { session });
|
|
1887
|
-
return EXIT_OK;
|
|
1888
|
-
}
|
|
1889
|
-
function _cmd_status() {
|
|
1890
|
-
_print(_pyDumpsIndent2(status()));
|
|
1891
|
-
return 0;
|
|
72
|
+
return `${quote}${body}${quote}`;
|
|
1892
73
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
throw new ValueError(`invalid JSON for entries: ${_excStr(exc)}`);
|
|
1900
|
-
}
|
|
1901
|
-
if (!Array.isArray(data)) {
|
|
1902
|
-
throw new ValueError("entries must be a JSON array");
|
|
74
|
+
var RedactionResult = class {
|
|
75
|
+
ok;
|
|
76
|
+
violations;
|
|
77
|
+
constructor(ok, violations = []) {
|
|
78
|
+
this.ok = ok;
|
|
79
|
+
this.violations = violations;
|
|
1903
80
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
let freq = "per_phase";
|
|
1908
|
-
let entriesJson = null;
|
|
1909
|
-
let entriesStdin = false;
|
|
1910
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1911
|
-
const a = argv[i];
|
|
1912
|
-
if (a === "--freq") {
|
|
1913
|
-
freq = argv[++i] ?? freq;
|
|
1914
|
-
} else if (a.startsWith("--freq=")) {
|
|
1915
|
-
freq = a.slice("--freq=".length);
|
|
1916
|
-
} else if (a === "--entries-json") {
|
|
1917
|
-
entriesJson = argv[++i] ?? null;
|
|
1918
|
-
} else if (a.startsWith("--entries-json=")) {
|
|
1919
|
-
entriesJson = a.slice("--entries-json=".length);
|
|
1920
|
-
} else if (a === "--entries-stdin") {
|
|
1921
|
-
entriesStdin = true;
|
|
81
|
+
summary() {
|
|
82
|
+
if (this.ok) {
|
|
83
|
+
return "redaction: clean";
|
|
1922
84
|
}
|
|
85
|
+
const parts = this.violations.map((v) => `${v.category}: ${_pyRepr(v.snippet)}`);
|
|
86
|
+
return "redaction REFUSED \u2014 " + parts.join("; ");
|
|
1923
87
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
`,
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
88
|
+
};
|
|
89
|
+
var _UWORD = "[\\p{L}\\p{N}_]";
|
|
90
|
+
var _UNB_BEFORE = `(?<!${_UWORD})`;
|
|
91
|
+
var _UNB_AFTER = `(?!${_UWORD})`;
|
|
92
|
+
var _EMAIL_RE = new RegExp(
|
|
93
|
+
`${_UNB_BEFORE}[\\p{L}\\p{N}_.+-]+@[\\p{L}\\p{N}_-]+\\.[\\p{L}\\p{N}_.-]+${_UNB_AFTER}`,
|
|
94
|
+
"gu"
|
|
95
|
+
);
|
|
96
|
+
var _PATH_RE = new RegExp(
|
|
97
|
+
`(?:^|[\\s"'(])(?:/Users/|/home/|/opt/|/private/|[A-Z]:\\\\)[\\p{L}\\p{N}_.\\-/\\\\]+`,
|
|
98
|
+
"gmu"
|
|
99
|
+
);
|
|
100
|
+
var _INTERNAL_HOST_RE = new RegExp(
|
|
101
|
+
`${_UNB_BEFORE}[a-zA-Z0-9][\\p{L}\\p{N}_.-]*\\.(?:internal|local)${_UNB_AFTER}`,
|
|
102
|
+
"giu"
|
|
103
|
+
);
|
|
104
|
+
var _UD = "[\\p{Nd}]";
|
|
105
|
+
var _MONEY_RE = new RegExp(
|
|
106
|
+
`(?:[\\$\u20AC\xA3\xA5]\\s?${_UD}{1,3}(?:[,.]${_UD}{3})*(?:\\.${_UD}+)?|${_UNB_BEFORE}(?:USD|EUR|GBP|JPY)\\s?${_UD}+(?:[,.]${_UD}+)?)`,
|
|
107
|
+
"gu"
|
|
108
|
+
);
|
|
109
|
+
var _API_KEY_RE = new RegExp(
|
|
110
|
+
`${_UNB_BEFORE}api[_-]?key${_UNB_AFTER}\\s*[:=]\\s*[A-Za-z0-9+/=_\\-]{12,}`,
|
|
111
|
+
"giu"
|
|
112
|
+
);
|
|
113
|
+
var _CODE_FENCE_RE = /`([^`]{41,})`/gu;
|
|
114
|
+
function _checkSecrets(text) {
|
|
115
|
+
const hits = [];
|
|
116
|
+
for (const prefix of _RAW_KEY_PREFIXES) {
|
|
117
|
+
const pat = new RegExp(_reEscape(prefix) + "[A-Za-z0-9_\\-]{6,}", "u");
|
|
118
|
+
const m2 = pat.exec(text);
|
|
119
|
+
if (m2) {
|
|
120
|
+
hits.push(
|
|
121
|
+
_violation(
|
|
122
|
+
"secret",
|
|
123
|
+
_pySliceHead(m2[0], 8) + "\u2026",
|
|
124
|
+
`raw-key prefix ${_pyRepr(prefix)}`
|
|
125
|
+
)
|
|
126
|
+
);
|
|
1960
127
|
}
|
|
1961
128
|
}
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const sp = settings ?? DEFAULT_SETTINGS_FILE;
|
|
1967
|
-
max_n = _read_chat_history_max_sessions(sp);
|
|
129
|
+
_API_KEY_RE.lastIndex = 0;
|
|
130
|
+
const m = _API_KEY_RE.exec(text);
|
|
131
|
+
if (m) {
|
|
132
|
+
hits.push(_violation("secret", _pySliceHead(m[0], 20) + "\u2026", "inline api_key"));
|
|
1968
133
|
}
|
|
1969
|
-
|
|
1970
|
-
_print(_pyDumps(result));
|
|
1971
|
-
return EXIT_OK;
|
|
134
|
+
return hits;
|
|
1972
135
|
}
|
|
1973
|
-
function
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const a = argv[i];
|
|
1978
|
-
if (a === "--entries-json") {
|
|
1979
|
-
entriesJson = argv[++i] ?? null;
|
|
1980
|
-
} else if (a.startsWith("--entries-json=")) {
|
|
1981
|
-
entriesJson = a.slice("--entries-json=".length);
|
|
1982
|
-
} else if (a === "--entries-stdin") {
|
|
1983
|
-
entriesStdin = true;
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
if (entriesJson === null && !entriesStdin) {
|
|
1987
|
-
return _argError(
|
|
1988
|
-
`usage: ${PROG} prepend [-h] (--entries-json ENTRIES_JSON | --entries-stdin)
|
|
1989
|
-
`,
|
|
1990
|
-
`${PROG} prepend`,
|
|
1991
|
-
"one of the arguments --entries-json --entries-stdin is required"
|
|
1992
|
-
);
|
|
136
|
+
function _checkPatterns(text, repoRoot, privateDomains, customerNames, sqlIdentifiers) {
|
|
137
|
+
const hits = [];
|
|
138
|
+
for (const m of text.matchAll(_EMAIL_RE)) {
|
|
139
|
+
hits.push(_violation("email", m[0]));
|
|
1993
140
|
}
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
_print(_pyDumps({ prepended: n }));
|
|
1997
|
-
return 0;
|
|
1998
|
-
}
|
|
1999
|
-
function _cmd_clear() {
|
|
2000
|
-
clear();
|
|
2001
|
-
return 0;
|
|
2002
|
-
}
|
|
2003
|
-
function _cmd_read(argv) {
|
|
2004
|
-
let last = 5;
|
|
2005
|
-
let lastSeen = false;
|
|
2006
|
-
let all = false;
|
|
2007
|
-
let session = null;
|
|
2008
|
-
let agent = null;
|
|
2009
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2010
|
-
const a = argv[i];
|
|
2011
|
-
if (a === "--last") {
|
|
2012
|
-
last = _pyIntFromAny(argv[++i] ?? "");
|
|
2013
|
-
lastSeen = true;
|
|
2014
|
-
} else if (a.startsWith("--last=")) {
|
|
2015
|
-
last = _pyIntFromAny(a.slice("--last=".length));
|
|
2016
|
-
lastSeen = true;
|
|
2017
|
-
} else if (a === "--all") {
|
|
2018
|
-
all = true;
|
|
2019
|
-
} else if (a === "--session") {
|
|
2020
|
-
session = argv[++i] ?? null;
|
|
2021
|
-
} else if (a.startsWith("--session=")) {
|
|
2022
|
-
session = a.slice("--session=".length);
|
|
2023
|
-
} else if (a === "--agent") {
|
|
2024
|
-
agent = argv[++i] ?? null;
|
|
2025
|
-
} else if (a.startsWith("--agent=")) {
|
|
2026
|
-
agent = a.slice("--agent=".length);
|
|
2027
|
-
}
|
|
141
|
+
for (const m of text.matchAll(_PATH_RE)) {
|
|
142
|
+
hits.push(_violation("project_path", _strip(m[0])));
|
|
2028
143
|
}
|
|
2029
|
-
if (
|
|
2030
|
-
|
|
2031
|
-
`usage: ${PROG} read [-h] [--last LAST | --all] [--session SESSION]
|
|
2032
|
-
[--agent AGENT]
|
|
2033
|
-
`,
|
|
2034
|
-
`${PROG} read`,
|
|
2035
|
-
"argument --all: not allowed with argument --last"
|
|
2036
|
-
);
|
|
144
|
+
if (repoRoot && text.includes(repoRoot)) {
|
|
145
|
+
hits.push(_violation("project_path", repoRoot, "configured repo root"));
|
|
2037
146
|
}
|
|
2038
|
-
const
|
|
2039
|
-
|
|
2040
|
-
if (all) {
|
|
2041
|
-
entries = read_entries({ last: lastArg, session: null, agent });
|
|
2042
|
-
} else if (session !== null) {
|
|
2043
|
-
entries = read_entries({ last: lastArg, session, agent });
|
|
2044
|
-
} else if (agent !== null) {
|
|
2045
|
-
const sid = _last_body_session_id(file_path());
|
|
2046
|
-
entries = read_entries({ last: lastArg, session: sid, agent });
|
|
2047
|
-
} else {
|
|
2048
|
-
entries = read_entries_for_current({ last: lastArg });
|
|
147
|
+
for (const m of text.matchAll(_INTERNAL_HOST_RE)) {
|
|
148
|
+
hits.push(_violation("internal_hostname", m[0]));
|
|
2049
149
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
function _cmd_sessions(argv) {
|
|
2054
|
-
let limit = 20;
|
|
2055
|
-
let includeEmpty = false;
|
|
2056
|
-
let json = false;
|
|
2057
|
-
let summary = false;
|
|
2058
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2059
|
-
const a = argv[i];
|
|
2060
|
-
if (a === "--limit") {
|
|
2061
|
-
limit = _pyIntFromAny(argv[++i] ?? "");
|
|
2062
|
-
} else if (a.startsWith("--limit=")) {
|
|
2063
|
-
limit = _pyIntFromAny(a.slice("--limit=".length));
|
|
2064
|
-
} else if (a === "--include-empty") {
|
|
2065
|
-
includeEmpty = true;
|
|
2066
|
-
} else if (a === "--json") {
|
|
2067
|
-
json = true;
|
|
2068
|
-
} else if (a === "--summary") {
|
|
2069
|
-
summary = true;
|
|
150
|
+
for (const dom of privateDomains) {
|
|
151
|
+
if (dom && text.includes(dom)) {
|
|
152
|
+
hits.push(_violation("internal_hostname", dom, "configured private domain"));
|
|
2070
153
|
}
|
|
2071
154
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
sessions = sessions.filter((s) => s.count > 0);
|
|
2075
|
-
}
|
|
2076
|
-
sessions = sessions.slice(0, limit);
|
|
2077
|
-
if (json) {
|
|
2078
|
-
_print(_pyDumpsIndent2(sessions));
|
|
2079
|
-
return 0;
|
|
2080
|
-
}
|
|
2081
|
-
if (sessions.length === 0) {
|
|
2082
|
-
_print("(no sessions)");
|
|
2083
|
-
return 0;
|
|
155
|
+
for (const m of text.matchAll(_MONEY_RE)) {
|
|
156
|
+
hits.push(_violation("monetary_amount", m[0]));
|
|
2084
157
|
}
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
const lastVal = summary ? s.summary : s.preview;
|
|
2089
|
-
const agentsVal = (s.agents || []).join(",") || "-";
|
|
2090
|
-
rows.push([s.id, String(s.count), agentsVal, s.last_ts || "-", lastVal || "-"]);
|
|
2091
|
-
}
|
|
2092
|
-
const widths = [];
|
|
2093
|
-
for (let i = 0; i < 5; i++) {
|
|
2094
|
-
widths.push(Math.max(...rows.map((r) => _pyLen2(r[i]))));
|
|
2095
|
-
}
|
|
2096
|
-
for (let i = 0; i < rows.length; i++) {
|
|
2097
|
-
const r = rows[i];
|
|
2098
|
-
const line = r.map((c, j) => _ljust(c, widths[j])).join(" ");
|
|
2099
|
-
_print(line);
|
|
2100
|
-
if (i === 0) {
|
|
2101
|
-
_print(widths.map((w) => "-".repeat(w)).join(" "));
|
|
158
|
+
for (const name of customerNames) {
|
|
159
|
+
if (name && new RegExp(`${_UNB_BEFORE}${_reEscape(name)}${_UNB_AFTER}`, "iu").test(text)) {
|
|
160
|
+
hits.push(_violation("customer_name", name));
|
|
2102
161
|
}
|
|
2103
162
|
}
|
|
2104
|
-
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
let maxKb = 256;
|
|
2108
|
-
let mode = "rotate";
|
|
2109
|
-
for (let i = 0; i < argv.length; i++) {
|
|
2110
|
-
const a = argv[i];
|
|
2111
|
-
if (a === "--max-kb") {
|
|
2112
|
-
maxKb = _pyIntFromAny(argv[++i] ?? "");
|
|
2113
|
-
} else if (a.startsWith("--max-kb=")) {
|
|
2114
|
-
maxKb = _pyIntFromAny(a.slice("--max-kb=".length));
|
|
2115
|
-
} else if (a === "--mode") {
|
|
2116
|
-
mode = argv[++i] ?? mode;
|
|
2117
|
-
} else if (a.startsWith("--mode=")) {
|
|
2118
|
-
mode = a.slice("--mode=".length);
|
|
163
|
+
for (const ident of sqlIdentifiers) {
|
|
164
|
+
if (ident && new RegExp(`${_UNB_BEFORE}${_reEscape(ident)}${_UNB_AFTER}`, "u").test(text)) {
|
|
165
|
+
hits.push(_violation("sql_identifier", ident));
|
|
2119
166
|
}
|
|
2120
167
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
`,
|
|
2126
|
-
`${PROG} rotate`,
|
|
2127
|
-
`argument --mode: invalid choice: ${_pyRepr2(mode)} (choose from 'condense', 'rotate')`
|
|
2128
|
-
);
|
|
2129
|
-
}
|
|
2130
|
-
const result = overflow_handle(maxKb, mode);
|
|
2131
|
-
_print(_pyDumps(result));
|
|
2132
|
-
return 0;
|
|
2133
|
-
}
|
|
2134
|
-
function _readStdin() {
|
|
2135
|
-
return readHookStdin();
|
|
2136
|
-
}
|
|
2137
|
-
function main(argv = process.argv.slice(2)) {
|
|
2138
|
-
const cmd = argv[0];
|
|
2139
|
-
if (cmd === void 0) {
|
|
2140
|
-
return _argError(_TOP_USAGE, PROG, "the following arguments are required: cmd");
|
|
2141
|
-
}
|
|
2142
|
-
if (!SUBCMDS.includes(cmd)) {
|
|
2143
|
-
const choices = SUBCMDS.map((c) => _pyRepr2(c)).join(", ");
|
|
2144
|
-
return _argError(
|
|
2145
|
-
_TOP_USAGE,
|
|
2146
|
-
PROG,
|
|
2147
|
-
`argument cmd: invalid choice: ${_pyRepr2(cmd)} (choose from ${choices})`
|
|
168
|
+
for (const m of text.matchAll(_CODE_FENCE_RE)) {
|
|
169
|
+
const inner = m[1];
|
|
170
|
+
hits.push(
|
|
171
|
+
_violation("long_code_excerpt", _pySliceHead(inner, 40) + "\u2026", `${_pyLen(inner)} chars`)
|
|
2148
172
|
);
|
|
2149
173
|
}
|
|
2150
|
-
|
|
2151
|
-
switch (cmd) {
|
|
2152
|
-
case "init":
|
|
2153
|
-
return _cmd_init(rest);
|
|
2154
|
-
case "append":
|
|
2155
|
-
return _cmd_append(rest);
|
|
2156
|
-
case "status":
|
|
2157
|
-
return _cmd_status();
|
|
2158
|
-
case "reset":
|
|
2159
|
-
return _cmd_reset(rest);
|
|
2160
|
-
case "prune-sessions":
|
|
2161
|
-
return _cmd_prune_sessions(rest);
|
|
2162
|
-
case "prepend":
|
|
2163
|
-
return _cmd_prepend(rest);
|
|
2164
|
-
case "clear":
|
|
2165
|
-
return _cmd_clear();
|
|
2166
|
-
case "read":
|
|
2167
|
-
return _cmd_read(rest);
|
|
2168
|
-
case "sessions":
|
|
2169
|
-
return _cmd_sessions(rest);
|
|
2170
|
-
case "rotate":
|
|
2171
|
-
return _cmd_rotate(rest);
|
|
2172
|
-
case "hook-append":
|
|
2173
|
-
return _cmd_hook_append(rest);
|
|
2174
|
-
case "hook-dispatch":
|
|
2175
|
-
return _cmd_hook_dispatch(rest);
|
|
2176
|
-
default:
|
|
2177
|
-
return EXIT_BAD_ARGS;
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
function _isCliEntry() {
|
|
2181
|
-
if (true) {
|
|
2182
|
-
return false;
|
|
2183
|
-
}
|
|
2184
|
-
if (process.argv[1] === void 0) {
|
|
2185
|
-
return false;
|
|
2186
|
-
}
|
|
2187
|
-
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
2188
|
-
if (import.meta.url === argvUrl) {
|
|
2189
|
-
return true;
|
|
2190
|
-
}
|
|
2191
|
-
try {
|
|
2192
|
-
const here = fs2.realpathSync(fileURLToPath(import.meta.url));
|
|
2193
|
-
const argv = fs2.realpathSync(path.resolve(process.argv[1]));
|
|
2194
|
-
return here === argv;
|
|
2195
|
-
} catch {
|
|
2196
|
-
return false;
|
|
2197
|
-
}
|
|
174
|
+
return hits;
|
|
2198
175
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
176
|
+
function redact_low_impact_entry(text, opts = {}) {
|
|
177
|
+
const repoRoot = opts.repoRoot ?? null;
|
|
178
|
+
const privateDomains = opts.privateDomains ?? [];
|
|
179
|
+
const customerNames = opts.customerNames ?? [];
|
|
180
|
+
const sqlIdentifiers = opts.sqlIdentifiers ?? [];
|
|
181
|
+
const violations = [];
|
|
182
|
+
violations.push(..._checkSecrets(text));
|
|
183
|
+
violations.push(
|
|
184
|
+
..._checkPatterns(text, repoRoot, privateDomains, customerNames, sqlIdentifiers)
|
|
185
|
+
);
|
|
186
|
+
return new RedactionResult(violations.length === 0, violations);
|
|
2212
187
|
}
|
|
2213
188
|
|
|
2214
189
|
// src/scripts/handoff_context_hook.ts
|
|
2215
|
-
import * as
|
|
2216
|
-
import * as
|
|
190
|
+
import * as fs from "node:fs";
|
|
191
|
+
import * as path from "node:path";
|
|
2217
192
|
import process2 from "node:process";
|
|
2218
|
-
import { fileURLToPath
|
|
2219
|
-
var HANDOFF_CONTEXT_REL =
|
|
193
|
+
import { fileURLToPath } from "node:url";
|
|
194
|
+
var HANDOFF_CONTEXT_REL = path.join("agents", "runtime", "state", "handoff-context.md");
|
|
2220
195
|
var MAX_AGE_HOURS = 48;
|
|
2221
|
-
var
|
|
2222
|
-
function
|
|
2223
|
-
return (process2.env[
|
|
196
|
+
var REPLAY_ENV_VAR = "AGENT_CONFIG_REPLAY";
|
|
197
|
+
function _is_replay_mode() {
|
|
198
|
+
return (process2.env[REPLAY_ENV_VAR] ?? "").trim() === "1";
|
|
2224
199
|
}
|
|
2225
200
|
function _handoff_context_path(root) {
|
|
2226
|
-
return process2.env.AGENT_HANDOFF_CONTEXT_FILE ||
|
|
201
|
+
return process2.env.AGENT_HANDOFF_CONTEXT_FILE || path.join(root, HANDOFF_CONTEXT_REL);
|
|
2227
202
|
}
|
|
2228
203
|
function consume_handoff_context(root, now = /* @__PURE__ */ new Date()) {
|
|
2229
204
|
const target = _handoff_context_path(root);
|
|
2230
205
|
let text;
|
|
2231
206
|
try {
|
|
2232
|
-
text =
|
|
207
|
+
text = fs.readFileSync(target, "utf-8");
|
|
2233
208
|
} catch {
|
|
2234
209
|
return { action: "absent", reason: "no handoff file" };
|
|
2235
210
|
}
|
|
2236
211
|
const remove = () => {
|
|
2237
212
|
try {
|
|
2238
|
-
|
|
213
|
+
fs.unlinkSync(target);
|
|
2239
214
|
} catch {
|
|
2240
215
|
}
|
|
2241
216
|
};
|
|
@@ -2264,7 +239,7 @@ function consume_handoff_context(root, now = /* @__PURE__ */ new Date()) {
|
|
|
2264
239
|
remove();
|
|
2265
240
|
return { action: "inject", reason: `handoff consumed (session=${sourceSession})`, context: block };
|
|
2266
241
|
}
|
|
2267
|
-
function
|
|
242
|
+
function main() {
|
|
2268
243
|
let envelope = {};
|
|
2269
244
|
try {
|
|
2270
245
|
const raw = readHookStdin().trim();
|
|
@@ -2279,7 +254,7 @@ function main2() {
|
|
|
2279
254
|
const event = String(envelope.event ?? "");
|
|
2280
255
|
const root = String(envelope.workspace_root ?? process2.cwd());
|
|
2281
256
|
try {
|
|
2282
|
-
if (
|
|
257
|
+
if (_is_replay_mode()) {
|
|
2283
258
|
return 0;
|
|
2284
259
|
}
|
|
2285
260
|
if (event === "session_start") {
|
|
@@ -2301,8 +276,8 @@ function main2() {
|
|
|
2301
276
|
return 0;
|
|
2302
277
|
}
|
|
2303
278
|
var _bundled = true;
|
|
2304
|
-
if (!_bundled &&
|
|
2305
|
-
process2.exit(
|
|
279
|
+
if (!_bundled && fileURLToPath(import.meta.url) === path.resolve(process2.argv[1] ?? "")) {
|
|
280
|
+
process2.exit(main());
|
|
2306
281
|
}
|
|
2307
282
|
|
|
2308
283
|
// src/scripts/_cli/handoff_generate.ts
|
|
@@ -2325,7 +300,7 @@ var LlmPolishNotImplementedError = class extends Error {
|
|
|
2325
300
|
};
|
|
2326
301
|
function _read_lines(file) {
|
|
2327
302
|
try {
|
|
2328
|
-
return
|
|
303
|
+
return fs2.readFileSync(file, "utf-8").split("\n");
|
|
2329
304
|
} catch {
|
|
2330
305
|
return [];
|
|
2331
306
|
}
|
|
@@ -2602,22 +577,22 @@ function build_handoff(session, options = {}) {
|
|
|
2602
577
|
return text;
|
|
2603
578
|
}
|
|
2604
579
|
function handoff_target_path(root) {
|
|
2605
|
-
return process3.env.AGENT_HANDOFF_CONTEXT_FILE ||
|
|
580
|
+
return process3.env.AGENT_HANDOFF_CONTEXT_FILE || path2.join(root, HANDOFF_CONTEXT_REL);
|
|
2606
581
|
}
|
|
2607
582
|
function write_handoff(text, options = {}) {
|
|
2608
583
|
const root = options.cwd ?? process3.cwd();
|
|
2609
584
|
const target = handoff_target_path(root);
|
|
2610
|
-
|
|
585
|
+
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
2611
586
|
const tmp = `${target}.tmp-${process3.pid}`;
|
|
2612
|
-
|
|
2613
|
-
|
|
587
|
+
fs2.writeFileSync(tmp, text, { encoding: "utf-8" });
|
|
588
|
+
fs2.renameSync(tmp, target);
|
|
2614
589
|
return target;
|
|
2615
590
|
}
|
|
2616
591
|
|
|
2617
592
|
// src/scripts/_cli/handoff_sessions.ts
|
|
2618
|
-
import * as
|
|
593
|
+
import * as fs3 from "node:fs";
|
|
2619
594
|
import * as os from "node:os";
|
|
2620
|
-
import * as
|
|
595
|
+
import * as path3 from "node:path";
|
|
2621
596
|
var DEFAULT_CAP = 15;
|
|
2622
597
|
var SUMMARY_SNIPPET_CHARS = 120;
|
|
2623
598
|
function encode_project_path(cwd) {
|
|
@@ -2635,7 +610,7 @@ function _summarize(first, last) {
|
|
|
2635
610
|
}
|
|
2636
611
|
function _read_lines2(file) {
|
|
2637
612
|
try {
|
|
2638
|
-
return
|
|
613
|
+
return fs3.readFileSync(file, "utf-8").split("\n");
|
|
2639
614
|
} catch {
|
|
2640
615
|
return [];
|
|
2641
616
|
}
|
|
@@ -2652,10 +627,10 @@ function _parse_json2(line) {
|
|
|
2652
627
|
}
|
|
2653
628
|
return null;
|
|
2654
629
|
}
|
|
2655
|
-
var CHAT_HISTORY_REL =
|
|
630
|
+
var CHAT_HISTORY_REL = path3.join("agents", "runtime", ".agent-chat-history");
|
|
2656
631
|
function _chat_history_path(opts) {
|
|
2657
632
|
if (opts.chatHistoryPath) return opts.chatHistoryPath;
|
|
2658
|
-
return process.env.AGENT_CHAT_HISTORY_FILE ||
|
|
633
|
+
return process.env.AGENT_CHAT_HISTORY_FILE || path3.join(opts.cwd ?? process.cwd(), CHAT_HISTORY_REL);
|
|
2659
634
|
}
|
|
2660
635
|
function _chat_history_summaries(histPath) {
|
|
2661
636
|
const bySession = /* @__PURE__ */ new Map();
|
|
@@ -2740,19 +715,19 @@ function _scan_claude_transcript(file) {
|
|
|
2740
715
|
return scan;
|
|
2741
716
|
}
|
|
2742
717
|
function _claude_transcript_sessions(opts, cap) {
|
|
2743
|
-
const root = opts.claudeProjectsRoot ??
|
|
718
|
+
const root = opts.claudeProjectsRoot ?? path3.join(os.homedir(), ".claude", "projects");
|
|
2744
719
|
const cwd = opts.cwd ?? process.cwd();
|
|
2745
|
-
const projectDir =
|
|
720
|
+
const projectDir = path3.join(root, encode_project_path(cwd));
|
|
2746
721
|
let names;
|
|
2747
722
|
try {
|
|
2748
|
-
names =
|
|
723
|
+
names = fs3.readdirSync(projectDir).filter((n) => n.endsWith(".jsonl"));
|
|
2749
724
|
} catch {
|
|
2750
725
|
return [];
|
|
2751
726
|
}
|
|
2752
727
|
const files = names.map((n) => {
|
|
2753
|
-
const full =
|
|
728
|
+
const full = path3.join(projectDir, n);
|
|
2754
729
|
try {
|
|
2755
|
-
return { full, id:
|
|
730
|
+
return { full, id: path3.basename(n, ".jsonl"), mtime: fs3.statSync(full).mtimeMs };
|
|
2756
731
|
} catch {
|
|
2757
732
|
return null;
|
|
2758
733
|
}
|
|
@@ -2777,13 +752,13 @@ function _claude_transcript_sessions(opts, cap) {
|
|
|
2777
752
|
function _codex_meta(file) {
|
|
2778
753
|
let firstLine = "";
|
|
2779
754
|
try {
|
|
2780
|
-
const fd =
|
|
755
|
+
const fd = fs3.openSync(file, "r");
|
|
2781
756
|
try {
|
|
2782
757
|
const buf = Buffer.alloc(8192);
|
|
2783
|
-
const n =
|
|
758
|
+
const n = fs3.readSync(fd, buf, 0, buf.length, 0);
|
|
2784
759
|
firstLine = buf.toString("utf-8", 0, n).split("\n")[0] ?? "";
|
|
2785
760
|
} finally {
|
|
2786
|
-
|
|
761
|
+
fs3.closeSync(fd);
|
|
2787
762
|
}
|
|
2788
763
|
} catch {
|
|
2789
764
|
return null;
|
|
@@ -2808,17 +783,17 @@ function _codex_files(root) {
|
|
|
2808
783
|
const dir = stack.pop();
|
|
2809
784
|
let entries;
|
|
2810
785
|
try {
|
|
2811
|
-
entries =
|
|
786
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
2812
787
|
} catch {
|
|
2813
788
|
continue;
|
|
2814
789
|
}
|
|
2815
790
|
for (const e of entries) {
|
|
2816
|
-
const full =
|
|
791
|
+
const full = path3.join(dir, e.name);
|
|
2817
792
|
if (e.isDirectory()) stack.push(full);
|
|
2818
793
|
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
|
|
2819
794
|
}
|
|
2820
795
|
}
|
|
2821
|
-
return out.sort((a, b) =>
|
|
796
|
+
return out.sort((a, b) => path3.basename(a) < path3.basename(b) ? 1 : -1);
|
|
2822
797
|
}
|
|
2823
798
|
function _codex_prompt_text(rec) {
|
|
2824
799
|
if (rec.type !== "event_msg") return null;
|
|
@@ -2846,13 +821,13 @@ function _scan_codex_session(file) {
|
|
|
2846
821
|
return scan;
|
|
2847
822
|
}
|
|
2848
823
|
function _matches_repo(sessionCwd, repoCwd) {
|
|
2849
|
-
const norm = (p) =>
|
|
824
|
+
const norm = (p) => path3.resolve(p).replace(/\/+$/, "");
|
|
2850
825
|
const a = norm(sessionCwd);
|
|
2851
826
|
const b = norm(repoCwd);
|
|
2852
|
-
return a === b || a.startsWith(b +
|
|
827
|
+
return a === b || a.startsWith(b + path3.sep);
|
|
2853
828
|
}
|
|
2854
829
|
function _codex_sessions(opts, cap) {
|
|
2855
|
-
const root = opts.codexSessionsRoot ??
|
|
830
|
+
const root = opts.codexSessionsRoot ?? path3.join(os.homedir(), ".codex", "sessions");
|
|
2856
831
|
const cwd = opts.cwd ?? process.cwd();
|
|
2857
832
|
const matching = [];
|
|
2858
833
|
for (const file of _codex_files(root)) {
|
|
@@ -2900,8 +875,8 @@ function list_handoff_sessions(opts = {}) {
|
|
|
2900
875
|
}
|
|
2901
876
|
|
|
2902
877
|
// src/scripts/_cli/cmd_handoff.ts
|
|
2903
|
-
var
|
|
2904
|
-
var USAGE = `usage: ${
|
|
878
|
+
var PROG = "agent-config handoff";
|
|
879
|
+
var USAGE = `usage: ${PROG} [--list] [--json] [--session <id>] [--print] [--launch <host>] [--llm] [--root <path>]`;
|
|
2905
880
|
var _HELP = `${USAGE}
|
|
2906
881
|
|
|
2907
882
|
Pick a recent session, generate a deterministic handoff, and seed a fresh
|
|
@@ -2952,7 +927,7 @@ var LAUNCH_ADAPTERS = {
|
|
|
2952
927
|
function _default_runner(cmd) {
|
|
2953
928
|
const r = spawnSync2(cmd[0], cmd.slice(1), { stdio: "inherit" });
|
|
2954
929
|
if (r.error) {
|
|
2955
|
-
process4.stderr.write(`${
|
|
930
|
+
process4.stderr.write(`${PROG}: cannot run ${cmd[0]}: ${String(r.error)}
|
|
2956
931
|
`);
|
|
2957
932
|
return 127;
|
|
2958
933
|
}
|
|
@@ -2961,14 +936,14 @@ function _default_runner(cmd) {
|
|
|
2961
936
|
function _tty_read_line(prompt) {
|
|
2962
937
|
try {
|
|
2963
938
|
process4.stdout.write(prompt);
|
|
2964
|
-
const fd =
|
|
939
|
+
const fd = fs4.openSync("/dev/tty", "rs");
|
|
2965
940
|
try {
|
|
2966
941
|
const buf = Buffer.alloc(1);
|
|
2967
942
|
let line = "";
|
|
2968
943
|
for (; ; ) {
|
|
2969
944
|
let n = 0;
|
|
2970
945
|
try {
|
|
2971
|
-
n =
|
|
946
|
+
n = fs4.readSync(fd, buf, 0, 1, null);
|
|
2972
947
|
} catch (exc) {
|
|
2973
948
|
const code = exc.code;
|
|
2974
949
|
if (code === "EAGAIN" || code === "EINTR") continue;
|
|
@@ -2981,15 +956,15 @@ function _tty_read_line(prompt) {
|
|
|
2981
956
|
}
|
|
2982
957
|
return line;
|
|
2983
958
|
} finally {
|
|
2984
|
-
|
|
959
|
+
fs4.closeSync(fd);
|
|
2985
960
|
}
|
|
2986
961
|
} catch {
|
|
2987
962
|
return null;
|
|
2988
963
|
}
|
|
2989
964
|
}
|
|
2990
|
-
function
|
|
965
|
+
function _argError(err, message) {
|
|
2991
966
|
err(`${USAGE}
|
|
2992
|
-
${
|
|
967
|
+
${PROG}: error: ${message}
|
|
2993
968
|
`);
|
|
2994
969
|
throw new ArgparseExit(2);
|
|
2995
970
|
}
|
|
@@ -3010,7 +985,7 @@ function _parse(argv, err) {
|
|
|
3010
985
|
if (eq >= 0) return arg.slice(eq + 1);
|
|
3011
986
|
i += 1;
|
|
3012
987
|
const value = argv[i];
|
|
3013
|
-
if (value === void 0)
|
|
988
|
+
if (value === void 0) _argError(err, `argument ${flag}: expected one argument`);
|
|
3014
989
|
return value;
|
|
3015
990
|
};
|
|
3016
991
|
while (i < argv.length) {
|
|
@@ -3043,7 +1018,7 @@ function _parse(argv, err) {
|
|
|
3043
1018
|
opts.root = takeValue("--root");
|
|
3044
1019
|
break;
|
|
3045
1020
|
default:
|
|
3046
|
-
|
|
1021
|
+
_argError(err, `unrecognized arguments: ${arg}`);
|
|
3047
1022
|
}
|
|
3048
1023
|
i += 1;
|
|
3049
1024
|
}
|
|
@@ -3061,7 +1036,7 @@ function _render_list(sessions, json) {
|
|
|
3061
1036
|
if (json) return JSON.stringify(sessions, null, 2) + "\n";
|
|
3062
1037
|
return sessions.map((s, i) => render_session_line(s, i)).join("\n") + "\n";
|
|
3063
1038
|
}
|
|
3064
|
-
function
|
|
1039
|
+
function main2(argv = null, options = {}) {
|
|
3065
1040
|
const out = options.out ?? ((t) => void process4.stdout.write(t));
|
|
3066
1041
|
const err = options.err ?? ((t) => void process4.stderr.write(t));
|
|
3067
1042
|
const isTTY = options.isTTY ?? Boolean(process4.stdin.isTTY && process4.stdout.isTTY);
|
|
@@ -3075,7 +1050,7 @@ function main3(argv = null, options = {}) {
|
|
|
3075
1050
|
throw exc;
|
|
3076
1051
|
}
|
|
3077
1052
|
const envRoot = process4.env.AGENT_CONFIG_ROOT_OVERRIDE === "1" ? process4.env.AGENT_CONFIG_PROJECT_ROOT || null : null;
|
|
3078
|
-
const root =
|
|
1053
|
+
const root = path4.resolve(opts.root ?? envRoot ?? process4.cwd());
|
|
3079
1054
|
const sessions = list_handoff_sessions({ cwd: root });
|
|
3080
1055
|
if (opts.list) {
|
|
3081
1056
|
if (sessions.length === 0) {
|
|
@@ -3086,19 +1061,19 @@ function main3(argv = null, options = {}) {
|
|
|
3086
1061
|
return 0;
|
|
3087
1062
|
}
|
|
3088
1063
|
if (sessions.length === 0) {
|
|
3089
|
-
err(`${
|
|
1064
|
+
err(`${PROG}: no recent sessions found for this repo
|
|
3090
1065
|
`);
|
|
3091
1066
|
return 1;
|
|
3092
1067
|
}
|
|
3093
1068
|
if (opts.launch && !(opts.launch in LAUNCH_ADAPTERS)) {
|
|
3094
1069
|
err(`${USAGE}
|
|
3095
|
-
${
|
|
1070
|
+
${PROG}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
3096
1071
|
`);
|
|
3097
1072
|
return 2;
|
|
3098
1073
|
}
|
|
3099
1074
|
const adapter = opts.launch ? LAUNCH_ADAPTERS[opts.launch] : null;
|
|
3100
1075
|
if (opts.launch && adapter && !adapter.supports_launch) {
|
|
3101
|
-
err(`${
|
|
1076
|
+
err(`${PROG}: --launch ${opts.launch} is not supported yet (hook injection already works there)
|
|
3102
1077
|
`);
|
|
3103
1078
|
return 2;
|
|
3104
1079
|
}
|
|
@@ -3106,12 +1081,12 @@ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
3106
1081
|
if (opts.session) {
|
|
3107
1082
|
const matches = sessions.filter((s) => s.id === opts.session || s.id.startsWith(opts.session));
|
|
3108
1083
|
if (matches.length === 0) {
|
|
3109
|
-
err(`${
|
|
1084
|
+
err(`${PROG}: session '${opts.session}' not found \u2014 run --list
|
|
3110
1085
|
`);
|
|
3111
1086
|
return 1;
|
|
3112
1087
|
}
|
|
3113
1088
|
if (matches.length > 1) {
|
|
3114
|
-
err(`${
|
|
1089
|
+
err(`${PROG}: session prefix '${opts.session}' is ambiguous (${matches.length} matches)
|
|
3115
1090
|
`);
|
|
3116
1091
|
return 2;
|
|
3117
1092
|
}
|
|
@@ -3122,14 +1097,14 @@ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
3122
1097
|
const raw = readLine(`Pick a session [1-${sessions.length}]: `);
|
|
3123
1098
|
const n = Number((raw ?? "").trim());
|
|
3124
1099
|
if (!Number.isInteger(n) || n < 1 || n > sessions.length) {
|
|
3125
|
-
err(`${
|
|
1100
|
+
err(`${PROG}: invalid selection
|
|
3126
1101
|
`);
|
|
3127
1102
|
return 2;
|
|
3128
1103
|
}
|
|
3129
1104
|
picked = sessions[n - 1];
|
|
3130
1105
|
} else {
|
|
3131
1106
|
out(_render_list(sessions, false));
|
|
3132
|
-
err(`${
|
|
1107
|
+
err(`${PROG}: non-interactive invocation needs --session <id> (list above)
|
|
3133
1108
|
`);
|
|
3134
1109
|
return 2;
|
|
3135
1110
|
}
|
|
@@ -3139,7 +1114,7 @@ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
3139
1114
|
text = build_handoff(picked, { cwd: root, llm: opts.llm });
|
|
3140
1115
|
} catch (exc) {
|
|
3141
1116
|
if (exc instanceof LlmPolishNotImplementedError) {
|
|
3142
|
-
err(`${
|
|
1117
|
+
err(`${PROG}: ${exc.message}
|
|
3143
1118
|
`);
|
|
3144
1119
|
return 2;
|
|
3145
1120
|
}
|
|
@@ -3154,7 +1129,7 @@ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
3154
1129
|
`);
|
|
3155
1130
|
return runner(adapter.build_argv(text));
|
|
3156
1131
|
}
|
|
3157
|
-
const existed =
|
|
1132
|
+
const existed = fs4.existsSync(handoff_target_path(root));
|
|
3158
1133
|
const target = write_handoff(text, { cwd: root });
|
|
3159
1134
|
if (existed) out("note: replaced an unconsumed handoff \u2014 only the most recent one persists.\n");
|
|
3160
1135
|
out(`handoff written: ${target}
|
|
@@ -3167,28 +1142,28 @@ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
|
|
|
3167
1142
|
}
|
|
3168
1143
|
return 0;
|
|
3169
1144
|
}
|
|
3170
|
-
function
|
|
1145
|
+
function _isCliEntry() {
|
|
3171
1146
|
if (process4.argv[1] === void 0) {
|
|
3172
1147
|
return false;
|
|
3173
1148
|
}
|
|
3174
|
-
const argvUrl =
|
|
1149
|
+
const argvUrl = pathToFileURL(path4.resolve(process4.argv[1])).href;
|
|
3175
1150
|
if (import.meta.url === argvUrl) {
|
|
3176
1151
|
return true;
|
|
3177
1152
|
}
|
|
3178
1153
|
try {
|
|
3179
|
-
const here =
|
|
3180
|
-
const argv =
|
|
1154
|
+
const here = fs4.realpathSync(fileURLToPath2(import.meta.url));
|
|
1155
|
+
const argv = fs4.realpathSync(path4.resolve(process4.argv[1]));
|
|
3181
1156
|
return here === argv;
|
|
3182
1157
|
} catch {
|
|
3183
1158
|
return false;
|
|
3184
1159
|
}
|
|
3185
1160
|
}
|
|
3186
|
-
if (
|
|
3187
|
-
process4.exitCode =
|
|
1161
|
+
if (_isCliEntry()) {
|
|
1162
|
+
process4.exitCode = main2();
|
|
3188
1163
|
}
|
|
3189
1164
|
export {
|
|
3190
1165
|
LAUNCH_ADAPTERS,
|
|
3191
1166
|
_parse,
|
|
3192
|
-
|
|
1167
|
+
main2 as main,
|
|
3193
1168
|
render_session_line
|
|
3194
1169
|
};
|