@event4u/agent-config 9.16.0 → 9.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +53 -0
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/agent-handoff.md +100 -48
  6. package/dist/agent-src/commands/commit.md +1 -1
  7. package/dist/agent-src/commands/condense.md +19 -64
  8. package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
  9. package/dist/agent-src/commands/memory/promote.md +14 -1
  10. package/dist/agent-src/commands/mode.md +9 -5
  11. package/dist/agent-src/commands/optimize/deep.md +72 -11
  12. package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
  13. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  14. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  15. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
  16. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
  17. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
  18. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
  19. package/dist/agent-src/profiles/agency.yml +3 -3
  20. package/dist/agent-src/profiles/content_creator.yml +2 -2
  21. package/dist/agent-src/profiles/developer.yml +2 -2
  22. package/dist/agent-src/profiles/finance.yml +1 -1
  23. package/dist/agent-src/profiles/founder.yml +2 -2
  24. package/dist/agent-src/profiles/ops.yml +2 -2
  25. package/dist/agent-src/rules/architecture.md +2 -2
  26. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  27. package/dist/agent-src/rules/context-hygiene.md +3 -2
  28. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  29. package/dist/agent-src/rules/output-discipline.md +9 -2
  30. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  31. package/dist/agent-src/rules/user-interaction.md +3 -4
  32. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
  33. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
  34. package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
  35. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
  36. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  37. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  38. package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
  39. package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
  40. package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
  41. package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
  42. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  43. package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
  44. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
  45. package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
  46. package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
  47. package/dist/agent-src/templates/agent-settings.md +13 -15
  48. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
  49. package/dist/agent-src/templates/persona.md +2 -2
  50. package/dist/agent-src/templates/roadmaps.md +1 -1
  51. package/dist/agent-src/templates/rule.md +1 -1
  52. package/dist/agent-src/templates/skill.md +1 -1
  53. package/dist/cli/registry.js +1 -0
  54. package/dist/cli/registry.js.map +1 -1
  55. package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
  56. package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-IMSXPRVH.js} +24 -1
  57. package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
  58. package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
  59. package/dist/cli-delegate/cmd_conformance.js +4 -3
  60. package/dist/cli-delegate/cmd_converge.js +3 -3
  61. package/dist/cli-delegate/cmd_doctor.js +3 -2
  62. package/dist/cli-delegate/cmd_export.js +4 -4
  63. package/dist/cli-delegate/cmd_handoff.js +3194 -0
  64. package/dist/cli-delegate/cmd_memory_get.js +35 -3754
  65. package/dist/cli-delegate/cmd_migrate.js +2 -2
  66. package/dist/cli-delegate/cmd_prune.js +4 -4
  67. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  68. package/dist/cli-delegate/cmd_sync.js +4 -4
  69. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  70. package/dist/cli-delegate/cmd_update.js +3 -3
  71. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  72. package/dist/cli-delegate/cmd_validate.js +4 -4
  73. package/dist/cli-delegate/cmd_versions.js +3 -3
  74. package/dist/discovery/deprecation-report.md +1 -1
  75. package/dist/discovery/discovery-manifest.json +31 -31
  76. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  77. package/dist/discovery/discovery-manifest.summary.md +1 -1
  78. package/dist/discovery/orphan-report.md +1 -1
  79. package/dist/discovery/packs.json +2 -2
  80. package/dist/discovery/trust-report.md +1 -1
  81. package/dist/discovery/workspaces.json +2 -2
  82. package/dist/hooks/dispatch.js +602 -507
  83. package/dist/install/install.mjs +23 -0
  84. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  85. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  86. package/dist/mcp/registry-manifest.json +4 -4
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +25 -3
  89. package/dist/scripts/_lib/agent_src.js +39 -6
  90. package/dist/scripts/_lib/agent_src.js.map +1 -1
  91. package/dist/server/schemas/settings.js +13 -0
  92. package/dist/server/schemas/settings.js.map +1 -1
  93. package/docs/guidelines/abstraction-thresholds.md +53 -0
  94. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
  95. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
  96. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
  97. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
  98. package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
  99. package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
  100. package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
  101. package/llms.txt +1 -0
  102. package/package.json +1 -1
  103. package/src/config/agent-settings.template.yml +22 -0
  104. package/src/config/evaluator-budgets.json +8 -11
  105. package/src/config/gate-coverage.yml +30 -0
  106. package/src/scripts/_cli/cmd_handoff.ts +405 -0
  107. package/src/scripts/_cli/handoff_generate.ts +453 -0
  108. package/src/scripts/_cli/handoff_sessions.ts +435 -0
  109. package/src/scripts/_dispatch.bash +9 -0
  110. package/src/scripts/_lib/agent_src.ts +36 -6
  111. package/src/scripts/_lib/release_material.ts +156 -0
  112. package/src/scripts/check_condensation.ts +2 -1
  113. package/src/scripts/check_finding_dispositions.ts +278 -0
  114. package/src/scripts/check_pack_size.ts +4 -3
  115. package/src/scripts/check_references.ts +1 -0
  116. package/src/scripts/check_release_highlights.ts +300 -0
  117. package/src/scripts/check_release_pr_shape.ts +4 -0
  118. package/src/scripts/check_release_surface_equality.ts +132 -0
  119. package/src/scripts/check_reply_consistency.ts +187 -138
  120. package/src/scripts/handoff_context_hook.ts +148 -0
  121. package/src/scripts/hook_manifest.yaml +17 -7
  122. package/src/scripts/hooks/concern_registry.ts +2 -0
  123. package/src/scripts/lint_abstraction_thresholds.ts +325 -0
  124. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  125. package/src/scripts/lint_profile_personas.ts +153 -0
  126. package/src/scripts/lint_token_budget_discipline.ts +148 -0
  127. package/src/scripts/release.ts +156 -32
  128. package/src/scripts/release_drill.ts +491 -0
  129. package/src/scripts/schemas/command.schema.json +33 -0
  130. package/src/scripts/self_review_gate.ts +41 -3
  131. package/src/scripts/skill_linter.ts +3 -1
  132. package/src/scripts/smoke/router.sh +1 -1
  133. package/src/scripts/update_counts.ts +60 -12
  134. package/src/server/schemas/settings.ts +23 -0
  135. package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
@@ -0,0 +1,3194 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
3
+ import "./chunk-XGA2NTU4.js";
4
+ import {
5
+ _RAW_KEY_PREFIXES,
6
+ hardenedSpawnEnv
7
+ } from "./chunk-VSYT7OHO.js";
8
+ import {
9
+ load_agent_settings
10
+ } from "./chunk-HJ6DAJHR.js";
11
+ import "./chunk-NDJLNC6Q.js";
12
+ import "./chunk-OY53J6II.js";
13
+ import "./chunk-J55WI3RR.js";
14
+
15
+ // src/scripts/_cli/cmd_handoff.ts
16
+ import { spawnSync as spawnSync2 } from "node:child_process";
17
+ import * as fs6 from "node:fs";
18
+ import process4 from "node:process";
19
+ import * as path5 from "node:path";
20
+ import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "node:url";
21
+
22
+ // src/scripts/_cli/handoff_generate.ts
23
+ 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
+ import * as fs2 from "node:fs";
188
+ import * as path from "node:path";
189
+ import { fileURLToPath, pathToFileURL } from "node:url";
190
+
191
+ // src/scripts/hooks/hook_stdin.ts
192
+ import * as fs from "node:fs";
193
+ var _override = null;
194
+ function readHookStdin() {
195
+ if (_override !== null) return _override;
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
+ }
1561
+ }
1562
+ return "";
1563
+ }
1564
+ function hook_dispatch(platform, raw_json, options = {}) {
1565
+ if (!(platform in PLATFORM_EVENT_MAP)) {
1566
+ throw new ValueError(
1567
+ `unknown platform: ${_pyRepr2(platform)}; expected one of ${_pyReprSortedListArr(VALID_PLATFORMS)}`
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");
1585
+ }
1586
+ payload = parsed;
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, "\\'")}'`;
1655
+ }
1656
+ return `'${s}'`;
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;
1696
+ }
1697
+ function _cmd_init(argv) {
1698
+ let freq = "per_phase";
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;
1719
+ }
1720
+ function _cmd_hook_append(argv) {
1721
+ let event = null;
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;
1796
+ }
1797
+ function _cmd_hook_dispatch(argv) {
1798
+ let platform = null;
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;
1856
+ }
1857
+ function _cmd_append(argv) {
1858
+ let type_ = null;
1859
+ let jsonRaw = null;
1860
+ let session_id = null;
1861
+ for (let i = 0; i < argv.length; i++) {
1862
+ const a = argv[i];
1863
+ if (a === "--type") {
1864
+ type_ = argv[++i] ?? null;
1865
+ } else if (a.startsWith("--type=")) {
1866
+ type_ = a.slice("--type=".length);
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;
1884
+ }
1885
+ const session = session_id ? derive_session_tag(session_id) : null;
1886
+ append(entry, { session });
1887
+ return EXIT_OK;
1888
+ }
1889
+ function _cmd_status() {
1890
+ _print(_pyDumpsIndent2(status()));
1891
+ return 0;
1892
+ }
1893
+ function _loadEntriesArg(entriesJson, entriesStdin) {
1894
+ const raw = entriesStdin ? _readStdin() : entriesJson || "[]";
1895
+ let data;
1896
+ try {
1897
+ data = JSON.parse(raw);
1898
+ } catch (exc) {
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");
1903
+ }
1904
+ return data;
1905
+ }
1906
+ function _cmd_reset(argv) {
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;
1922
+ }
1923
+ }
1924
+ if (!VALID_FREQS.has(freq)) {
1925
+ return _argError(
1926
+ `usage: ${PROG} reset [-h] [--freq {per_phase,per_tool,per_turn}]
1927
+ (--entries-json ENTRIES_JSON | --entries-stdin)
1928
+ `,
1929
+ `${PROG} reset`,
1930
+ `argument --freq: invalid choice: ${_pyRepr2(freq)} (choose from 'per_phase', 'per_tool', 'per_turn')`
1931
+ );
1932
+ }
1933
+ if (entriesJson === null && !entriesStdin) {
1934
+ return _argError(
1935
+ `usage: ${PROG} reset [-h] [--freq {per_phase,per_tool,per_turn}]
1936
+ (--entries-json ENTRIES_JSON | --entries-stdin)
1937
+ `,
1938
+ `${PROG} reset`,
1939
+ "one of the arguments --entries-json --entries-stdin is required"
1940
+ );
1941
+ }
1942
+ const entries = _loadEntriesArg(entriesJson, entriesStdin);
1943
+ const h = reset_with_entries(entries, freq);
1944
+ _print(_pyDumps(h));
1945
+ return 0;
1946
+ }
1947
+ function _cmd_prune_sessions(argv) {
1948
+ let maxSessions = null;
1949
+ let settings = null;
1950
+ for (let i = 0; i < argv.length; i++) {
1951
+ const a = argv[i];
1952
+ if (a === "--max-sessions") {
1953
+ maxSessions = _pyIntFromAny(argv[++i] ?? "");
1954
+ } else if (a.startsWith("--max-sessions=")) {
1955
+ maxSessions = _pyIntFromAny(a.slice("--max-sessions=".length));
1956
+ } else if (a === "--settings") {
1957
+ settings = argv[++i] ?? null;
1958
+ } else if (a.startsWith("--settings=")) {
1959
+ settings = a.slice("--settings=".length);
1960
+ }
1961
+ }
1962
+ let max_n;
1963
+ if (maxSessions !== null) {
1964
+ max_n = Math.max(1, maxSessions);
1965
+ } else {
1966
+ const sp = settings ?? DEFAULT_SETTINGS_FILE;
1967
+ max_n = _read_chat_history_max_sessions(sp);
1968
+ }
1969
+ const result = prune_sessions(max_n);
1970
+ _print(_pyDumps(result));
1971
+ return EXIT_OK;
1972
+ }
1973
+ function _cmd_prepend(argv) {
1974
+ let entriesJson = null;
1975
+ let entriesStdin = false;
1976
+ for (let i = 0; i < argv.length; i++) {
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
+ );
1993
+ }
1994
+ const entries = _loadEntriesArg(entriesJson, entriesStdin);
1995
+ const n = prepend_entries(entries);
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
+ }
2028
+ }
2029
+ if (lastSeen && all) {
2030
+ return _argError(
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
+ );
2037
+ }
2038
+ const lastArg = all ? null : last;
2039
+ let entries;
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 });
2049
+ }
2050
+ _print(_pyDumpsIndent2(entries));
2051
+ return 0;
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;
2070
+ }
2071
+ }
2072
+ let sessions = list_sessions({ summary });
2073
+ if (!includeEmpty) {
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;
2084
+ }
2085
+ const lastCol = summary ? "SUMMARY" : "PREVIEW";
2086
+ const rows = [["ID", "COUNT", "AGENTS", "LAST_TS", lastCol]];
2087
+ for (const s of sessions) {
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(" "));
2102
+ }
2103
+ }
2104
+ return 0;
2105
+ }
2106
+ function _cmd_rotate(argv) {
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);
2119
+ }
2120
+ }
2121
+ if (!VALID_OVERFLOW.has(mode)) {
2122
+ return _argError(
2123
+ `usage: ${PROG} rotate [-h] [--max-kb MAX_KB]
2124
+ [--mode {condense,rotate}]
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})`
2148
+ );
2149
+ }
2150
+ const rest = argv.slice(1);
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
+ }
2198
+ }
2199
+ var _invokedDirectly = _isCliEntry();
2200
+ if (_invokedDirectly) {
2201
+ try {
2202
+ process.exitCode = main();
2203
+ } catch (exc) {
2204
+ if (exc instanceof ValueError || exc instanceof FileNotFoundError) {
2205
+ process.stderr.write(`${exc.constructor.name}: ${exc.message}
2206
+ `);
2207
+ process.exitCode = 1;
2208
+ } else {
2209
+ throw exc;
2210
+ }
2211
+ }
2212
+ }
2213
+
2214
+ // src/scripts/handoff_context_hook.ts
2215
+ import * as fs3 from "node:fs";
2216
+ import * as path2 from "node:path";
2217
+ import process2 from "node:process";
2218
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2219
+ var HANDOFF_CONTEXT_REL = path2.join("agents", "runtime", "state", "handoff-context.md");
2220
+ var MAX_AGE_HOURS = 48;
2221
+ var REPLAY_ENV_VAR2 = "AGENT_CONFIG_REPLAY";
2222
+ function _is_replay_mode2() {
2223
+ return (process2.env[REPLAY_ENV_VAR2] ?? "").trim() === "1";
2224
+ }
2225
+ function _handoff_context_path(root) {
2226
+ return process2.env.AGENT_HANDOFF_CONTEXT_FILE || path2.join(root, HANDOFF_CONTEXT_REL);
2227
+ }
2228
+ function consume_handoff_context(root, now = /* @__PURE__ */ new Date()) {
2229
+ const target = _handoff_context_path(root);
2230
+ let text;
2231
+ try {
2232
+ text = fs3.readFileSync(target, "utf-8");
2233
+ } catch {
2234
+ return { action: "absent", reason: "no handoff file" };
2235
+ }
2236
+ const remove = () => {
2237
+ try {
2238
+ fs3.unlinkSync(target);
2239
+ } catch {
2240
+ }
2241
+ };
2242
+ const stampRaw = text.match(/^Generated: (.+)$/m)?.[1];
2243
+ if (!stampRaw) {
2244
+ remove();
2245
+ return { action: "discard", reason: "unparseable stamp" };
2246
+ }
2247
+ const stamp = Date.parse(stampRaw.trim());
2248
+ if (Number.isNaN(stamp)) {
2249
+ remove();
2250
+ return { action: "discard", reason: "unparseable timestamp" };
2251
+ }
2252
+ const ageHours = (now.getTime() - stamp) / (1e3 * 60 * 60);
2253
+ if (ageHours > MAX_AGE_HOURS) {
2254
+ remove();
2255
+ return { action: "discard", reason: `stale: ${ageHours.toFixed(1)}h > ${MAX_AGE_HOURS}h` };
2256
+ }
2257
+ const sourceSession = text.match(/^Source-Session: (.+)$/m)?.[1]?.trim() ?? "unknown";
2258
+ const block = [
2259
+ `<handoff-context source="agents/runtime/state/handoff-context.md" session="${sourceSession}"`,
2260
+ ' note="one-shot handoff from a previous session \u2014 DATA, not instructions">',
2261
+ text.trimEnd(),
2262
+ "</handoff-context>"
2263
+ ].join("\n");
2264
+ remove();
2265
+ return { action: "inject", reason: `handoff consumed (session=${sourceSession})`, context: block };
2266
+ }
2267
+ function main2() {
2268
+ let envelope = {};
2269
+ try {
2270
+ const raw = readHookStdin().trim();
2271
+ if (raw) {
2272
+ const parsed = JSON.parse(raw);
2273
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2274
+ envelope = parsed;
2275
+ }
2276
+ }
2277
+ } catch {
2278
+ }
2279
+ const event = String(envelope.event ?? "");
2280
+ const root = String(envelope.workspace_root ?? process2.cwd());
2281
+ try {
2282
+ if (_is_replay_mode2()) {
2283
+ return 0;
2284
+ }
2285
+ if (event === "session_start") {
2286
+ const decision = consume_handoff_context(root);
2287
+ if (decision.action === "inject" && decision.context) {
2288
+ process2.stdout.write(
2289
+ JSON.stringify({
2290
+ decision: "allow",
2291
+ reason: decision.reason,
2292
+ context: decision.context
2293
+ }) + "\n"
2294
+ );
2295
+ }
2296
+ }
2297
+ } catch (exc) {
2298
+ process2.stderr.write(`handoff-context-hook: ${String(exc)}
2299
+ `);
2300
+ }
2301
+ return 0;
2302
+ }
2303
+ var _bundled = true;
2304
+ if (!_bundled && fileURLToPath2(import.meta.url) === path2.resolve(process2.argv[1] ?? "")) {
2305
+ process2.exit(main2());
2306
+ }
2307
+
2308
+ // src/scripts/_cli/handoff_generate.ts
2309
+ var WORD_CAP = 1200;
2310
+ var SNIPPET_CHARS = 200;
2311
+ var DONE_SNIPPET_CHARS = 400;
2312
+ var MAX_ERRORS = 5;
2313
+ var MAX_DECISIONS = 5;
2314
+ var MAX_FILES = 15;
2315
+ var FAILURE_RE = /\b(fail(ing|ed|ure)?|error|exit[= ][1-9]|red\b|broken)\b/i;
2316
+ var DECISION_RE = /\b(decided|decision|chose|choosing|locked|we'll use|we will use)\b/i;
2317
+ var FILE_PATH_RE = /(?:src|tests|docs|agents|scripts|config)\/[\w./-]+\.\w{1,8}/g;
2318
+ var ROADMAP_RE = /agents\/roadmaps\/[\w.-]+\.md/g;
2319
+ var LlmPolishNotImplementedError = class extends Error {
2320
+ constructor() {
2321
+ super(
2322
+ "--llm polish is a v1 seam and not implemented yet \u2014 the deterministic handoff (default) is the supported path."
2323
+ );
2324
+ }
2325
+ };
2326
+ function _read_lines(file) {
2327
+ try {
2328
+ return fs4.readFileSync(file, "utf-8").split("\n");
2329
+ } catch {
2330
+ return [];
2331
+ }
2332
+ }
2333
+ function _parse_json(line) {
2334
+ const trimmed = line.trim();
2335
+ if (!trimmed) return null;
2336
+ try {
2337
+ const parsed = JSON.parse(trimmed);
2338
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2339
+ return parsed;
2340
+ }
2341
+ } catch {
2342
+ }
2343
+ return null;
2344
+ }
2345
+ function _from_chat_history(sessionId, chatHistoryPath) {
2346
+ let entries;
2347
+ try {
2348
+ entries = read_entries({ session: sessionId, path: chatHistoryPath, last: null });
2349
+ } catch {
2350
+ entries = [];
2351
+ }
2352
+ const turns = [];
2353
+ for (const e of entries) {
2354
+ const text = typeof e.text === "string" ? e.text : "";
2355
+ if (!text) continue;
2356
+ const t = String(e.t ?? "");
2357
+ const kind = t === "user" ? "user" : t === "tool" ? "tool" : "assistant";
2358
+ turns.push({
2359
+ kind,
2360
+ text,
2361
+ ts: typeof e.ts === "string" ? e.ts : null,
2362
+ ...typeof e.tool === "string" && e.tool ? { tool: e.tool } : {}
2363
+ });
2364
+ }
2365
+ return { turns, branch: null };
2366
+ }
2367
+ function _cc_text_from_content(content) {
2368
+ if (typeof content === "string") return content;
2369
+ if (Array.isArray(content)) {
2370
+ return content.map((block) => {
2371
+ if (block && typeof block === "object" && !Array.isArray(block)) {
2372
+ const b = block;
2373
+ if (b.type === "text" && typeof b.text === "string") return b.text;
2374
+ }
2375
+ return "";
2376
+ }).filter((t) => t).join("\n");
2377
+ }
2378
+ return "";
2379
+ }
2380
+ function _from_claude_transcript(file) {
2381
+ const turns = [];
2382
+ let branch = null;
2383
+ for (const line of _read_lines(file)) {
2384
+ const rec = _parse_json(line);
2385
+ if (!rec || rec.isSidechain === true) continue;
2386
+ if (!branch && typeof rec.gitBranch === "string" && rec.gitBranch) branch = rec.gitBranch;
2387
+ const ts = typeof rec.timestamp === "string" ? rec.timestamp : null;
2388
+ const msg = rec.message && typeof rec.message === "object" && !Array.isArray(rec.message) ? rec.message : null;
2389
+ if (!msg) continue;
2390
+ if (rec.type === "user") {
2391
+ if (typeof msg.content === "string") {
2392
+ const text = msg.content.trim();
2393
+ if (text && !text.startsWith("<") && !text.startsWith("Caveat:")) {
2394
+ turns.push({ kind: "user", text, ts });
2395
+ }
2396
+ } else {
2397
+ const text = _cc_text_from_content(msg.content).trim();
2398
+ if (text) turns.push({ kind: "tool", text, ts });
2399
+ }
2400
+ } else if (rec.type === "assistant") {
2401
+ const text = _cc_text_from_content(msg.content).trim();
2402
+ if (text) turns.push({ kind: "assistant", text, ts });
2403
+ }
2404
+ }
2405
+ return { turns, branch };
2406
+ }
2407
+ function _from_codex_session(file) {
2408
+ const turns = [];
2409
+ let branch = null;
2410
+ for (const line of _read_lines(file)) {
2411
+ const rec = _parse_json(line);
2412
+ if (!rec) continue;
2413
+ const ts = typeof rec.timestamp === "string" ? rec.timestamp : null;
2414
+ const p = rec.payload && typeof rec.payload === "object" && !Array.isArray(rec.payload) ? rec.payload : null;
2415
+ if (!p) continue;
2416
+ if (rec.type === "session_meta") {
2417
+ const git = p.git && typeof p.git === "object" && !Array.isArray(p.git) ? p.git : {};
2418
+ if (typeof git.branch === "string" && git.branch) branch = git.branch;
2419
+ } else if (rec.type === "event_msg" && p.type === "user_message") {
2420
+ const text = typeof p.message === "string" ? p.message.trim() : "";
2421
+ if (text && !text.startsWith("<")) turns.push({ kind: "user", text, ts });
2422
+ } else if (rec.type === "event_msg" && p.type === "agent_message") {
2423
+ const text = typeof p.message === "string" ? p.message.trim() : "";
2424
+ if (text) turns.push({ kind: "assistant", text, ts });
2425
+ }
2426
+ }
2427
+ return { turns, branch };
2428
+ }
2429
+ function load_transcript(session, options = {}) {
2430
+ if (session.source === "chat-history") {
2431
+ return _from_chat_history(session.id, options.chatHistoryPath ?? null);
2432
+ }
2433
+ if (!session.transcriptPath) return { turns: [], branch: null };
2434
+ return session.source === "claude-transcript" ? _from_claude_transcript(session.transcriptPath) : _from_codex_session(session.transcriptPath);
2435
+ }
2436
+ function _snippet(text, max) {
2437
+ const oneLine = text.replace(/\s+/g, " ").trim();
2438
+ return oneLine.length <= max ? oneLine : oneLine.slice(0, max - 1) + "\u2026";
2439
+ }
2440
+ function _word_count(text) {
2441
+ return text.split(/\s+/).filter((w) => w.length > 0).length;
2442
+ }
2443
+ function _redact_items(items) {
2444
+ const kept = [];
2445
+ let dropped = 0;
2446
+ for (const item of items) {
2447
+ try {
2448
+ if (redact_low_impact_entry(item).ok) kept.push(item);
2449
+ else dropped += 1;
2450
+ } catch {
2451
+ dropped += 1;
2452
+ }
2453
+ }
2454
+ return { kept, dropped };
2455
+ }
2456
+ function _redact_verbatim(prompts) {
2457
+ const kept = [];
2458
+ let withheldLines = 0;
2459
+ for (const prompt of prompts) {
2460
+ const lines = prompt.split("\n");
2461
+ let failing = 0;
2462
+ for (const line of lines) {
2463
+ try {
2464
+ if (!redact_low_impact_entry(line).ok) failing += 1;
2465
+ } catch {
2466
+ failing += 1;
2467
+ }
2468
+ }
2469
+ if (failing === 0) kept.push(prompt);
2470
+ else withheldLines += failing;
2471
+ }
2472
+ return { kept, withheldLines };
2473
+ }
2474
+ function _current_head(root) {
2475
+ try {
2476
+ const proc = spawnSync("git", ["log", "--oneline", "-1"], {
2477
+ cwd: root,
2478
+ encoding: "utf-8",
2479
+ timeout: 5e3,
2480
+ env: hardenedSpawnEnv()
2481
+ });
2482
+ if (proc.status === 0) return (proc.stdout || "").trim() || null;
2483
+ } catch {
2484
+ }
2485
+ return null;
2486
+ }
2487
+ function _current_branch(root) {
2488
+ try {
2489
+ const proc = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
2490
+ cwd: root,
2491
+ encoding: "utf-8",
2492
+ timeout: 5e3,
2493
+ env: hardenedSpawnEnv()
2494
+ });
2495
+ if (proc.status === 0) return (proc.stdout || "").trim() || "unknown";
2496
+ } catch {
2497
+ }
2498
+ return "unknown";
2499
+ }
2500
+ function build_handoff(session, options = {}) {
2501
+ if (options.llm) throw new LlmPolishNotImplementedError();
2502
+ const root = options.cwd ?? process3.cwd();
2503
+ const now = options.now ?? /* @__PURE__ */ new Date();
2504
+ const wordCap = options.wordCap ?? WORD_CAP;
2505
+ const transcript = load_transcript(session, { chatHistoryPath: options.chatHistoryPath ?? null });
2506
+ const users = transcript.turns.filter((t) => t.kind === "user");
2507
+ const assistants = transcript.turns.filter((t) => t.kind === "assistant");
2508
+ const toolish = transcript.turns.filter((t) => t.kind !== "user");
2509
+ const verbatim = _redact_verbatim(users.map((t) => t.text));
2510
+ const lastAssistant = assistants[assistants.length - 1]?.text ?? "";
2511
+ const done = _redact_items(lastAssistant ? [_snippet(lastAssistant, DONE_SNIPPET_CHARS)] : []);
2512
+ const lastUser = users[users.length - 1]?.text ?? "";
2513
+ const open = _redact_items(lastUser ? [`in-flight ask: ${_snippet(lastUser, SNIPPET_CHARS)}`] : []);
2514
+ const resume = _redact_items(
2515
+ lastUser ? [`continue from the last user ask: ${_snippet(lastUser, SNIPPET_CHARS)}`] : []
2516
+ );
2517
+ const errors = _redact_items(
2518
+ toolish.filter((t) => FAILURE_RE.test(t.text)).slice(-MAX_ERRORS).map((t) => _snippet(`${t.tool ? `${t.tool}: ` : ""}${t.text}`, SNIPPET_CHARS))
2519
+ );
2520
+ const decisions = _redact_items(
2521
+ assistants.filter((t) => DECISION_RE.test(t.text)).slice(-MAX_DECISIONS).map((t) => _snippet(t.text, SNIPPET_CHARS))
2522
+ );
2523
+ const fileSet = /* @__PURE__ */ new Set();
2524
+ for (const t of transcript.turns) {
2525
+ for (const m of t.text.match(FILE_PATH_RE) ?? []) fileSet.add(m);
2526
+ }
2527
+ const files = _redact_items([...fileSet].slice(-MAX_FILES));
2528
+ let roadmap = null;
2529
+ for (const t of transcript.turns) {
2530
+ for (const m of t.text.match(ROADMAP_RE) ?? []) roadmap = m;
2531
+ }
2532
+ const branch = session.branch ?? transcript.branch ?? "unknown";
2533
+ const droppedTotal = done.dropped + open.dropped + resume.dropped + errors.dropped + decisions.dropped + files.dropped;
2534
+ const sections = [
2535
+ { title: "User instructions (VERBATIM)", items: verbatim.kept, verbatim: true },
2536
+ { title: "Done", items: done.kept },
2537
+ { title: "Open", items: open.kept },
2538
+ { title: "Resume pointer", items: resume.kept },
2539
+ { title: "Errors + fixes", items: errors.kept },
2540
+ { title: "Key decisions", items: decisions.kept },
2541
+ { title: "Relevant files", items: files.kept }
2542
+ ];
2543
+ const trimOrder = [
2544
+ "Relevant files",
2545
+ "Key decisions",
2546
+ "Errors + fixes",
2547
+ "Done",
2548
+ "Open",
2549
+ "Resume pointer",
2550
+ "User instructions (VERBATIM)"
2551
+ ];
2552
+ const head = branch !== "unknown" && branch === _current_branch(root) ? _current_head(root) : null;
2553
+ const render = () => {
2554
+ const lines = [
2555
+ "# Handoff",
2556
+ "",
2557
+ `Generated: ${now.toISOString()}`,
2558
+ `Source-Session: ${session.id}`,
2559
+ `Branch: ${branch}`
2560
+ ];
2561
+ if (head) lines.push(`Last commit: ${head}`);
2562
+ if (roadmap) lines.push(`Roadmap: ${roadmap}`);
2563
+ if (verbatim.withheldLines > 0 || droppedTotal > 0) {
2564
+ lines.push(
2565
+ `Privacy floor: [${verbatim.withheldLines + droppedTotal} line(s) withheld by privacy floor]`
2566
+ );
2567
+ }
2568
+ for (const s of sections) {
2569
+ lines.push("", `## ${s.title}`, "");
2570
+ if (s.items.length === 0) {
2571
+ if (s.verbatim && verbatim.withheldLines > 0) {
2572
+ lines.push(`- [${verbatim.withheldLines} line(s) withheld by privacy floor]`);
2573
+ } else {
2574
+ lines.push("- (none extracted)");
2575
+ }
2576
+ continue;
2577
+ }
2578
+ for (const item of s.items) {
2579
+ if (s.verbatim) {
2580
+ const [first, ...rest] = item.split("\n");
2581
+ lines.push(`- ${first ?? ""}`);
2582
+ for (const cont of rest) lines.push(` ${cont}`);
2583
+ } else {
2584
+ lines.push(`- ${item}`);
2585
+ }
2586
+ }
2587
+ }
2588
+ lines.push("");
2589
+ return lines.join("\n");
2590
+ };
2591
+ let text = render();
2592
+ for (const title of trimOrder) {
2593
+ if (_word_count(text) <= wordCap) break;
2594
+ const section = sections.find((s) => s.title === title);
2595
+ if (!section) continue;
2596
+ while (section.items.length > 0 && _word_count(text) > wordCap) {
2597
+ if (section.verbatim) section.items.shift();
2598
+ else section.items.pop();
2599
+ text = render();
2600
+ }
2601
+ }
2602
+ return text;
2603
+ }
2604
+ function handoff_target_path(root) {
2605
+ return process3.env.AGENT_HANDOFF_CONTEXT_FILE || path3.join(root, HANDOFF_CONTEXT_REL);
2606
+ }
2607
+ function write_handoff(text, options = {}) {
2608
+ const root = options.cwd ?? process3.cwd();
2609
+ const target = handoff_target_path(root);
2610
+ fs4.mkdirSync(path3.dirname(target), { recursive: true });
2611
+ const tmp = `${target}.tmp-${process3.pid}`;
2612
+ fs4.writeFileSync(tmp, text, { encoding: "utf-8" });
2613
+ fs4.renameSync(tmp, target);
2614
+ return target;
2615
+ }
2616
+
2617
+ // src/scripts/_cli/handoff_sessions.ts
2618
+ import * as fs5 from "node:fs";
2619
+ import * as os from "node:os";
2620
+ import * as path4 from "node:path";
2621
+ var DEFAULT_CAP = 15;
2622
+ var SUMMARY_SNIPPET_CHARS = 120;
2623
+ function encode_project_path(cwd) {
2624
+ return cwd.replace(/[/.]/g, "-");
2625
+ }
2626
+ function _snippet2(text, max = SUMMARY_SNIPPET_CHARS) {
2627
+ const oneLine = text.replace(/\s+/g, " ").trim();
2628
+ return oneLine.length <= max ? oneLine : oneLine.slice(0, max - 1) + "\u2026";
2629
+ }
2630
+ function _summarize(first, last) {
2631
+ const a = first ? _snippet2(first) : "";
2632
+ const b = last ? _snippet2(last) : "";
2633
+ if (a && b && a !== b) return `${a} \u2192 ${b}`;
2634
+ return a || b || "(no user prompts)";
2635
+ }
2636
+ function _read_lines2(file) {
2637
+ try {
2638
+ return fs5.readFileSync(file, "utf-8").split("\n");
2639
+ } catch {
2640
+ return [];
2641
+ }
2642
+ }
2643
+ function _parse_json2(line) {
2644
+ const trimmed = line.trim();
2645
+ if (!trimmed) return null;
2646
+ try {
2647
+ const parsed = JSON.parse(trimmed);
2648
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2649
+ return parsed;
2650
+ }
2651
+ } catch {
2652
+ }
2653
+ return null;
2654
+ }
2655
+ var CHAT_HISTORY_REL = path4.join("agents", "runtime", ".agent-chat-history");
2656
+ function _chat_history_path(opts) {
2657
+ if (opts.chatHistoryPath) return opts.chatHistoryPath;
2658
+ return process.env.AGENT_CHAT_HISTORY_FILE || path4.join(opts.cwd ?? process.cwd(), CHAT_HISTORY_REL);
2659
+ }
2660
+ function _chat_history_summaries(histPath) {
2661
+ const bySession = /* @__PURE__ */ new Map();
2662
+ let entries;
2663
+ try {
2664
+ entries = read_entries({ path: histPath, last: null });
2665
+ } catch {
2666
+ return bySession;
2667
+ }
2668
+ for (const e of entries) {
2669
+ if (e.t !== "user" || typeof e.text !== "string") continue;
2670
+ const text = e.text.trim();
2671
+ if (!text || text.startsWith("<") || text.startsWith("Caveat:")) continue;
2672
+ const sid = String(e.s ?? "<legacy>");
2673
+ const slot = bySession.get(sid) ?? { first: null, last: null };
2674
+ if (!slot.first) slot.first = text;
2675
+ slot.last = text;
2676
+ bySession.set(sid, slot);
2677
+ }
2678
+ return bySession;
2679
+ }
2680
+ function _chat_history_sessions(opts) {
2681
+ const histPath = _chat_history_path(opts);
2682
+ let buckets;
2683
+ try {
2684
+ buckets = list_sessions({ path: histPath, summary: true });
2685
+ } catch {
2686
+ return [];
2687
+ }
2688
+ const summaries = _chat_history_summaries(histPath);
2689
+ return buckets.filter((b) => Number(b.count ?? 0) > 0).map((b) => {
2690
+ const id = String(b.id ?? "");
2691
+ const prompts = summaries.get(id);
2692
+ const summary = prompts ? _summarize(prompts.first, prompts.last) : "(no user prompts)";
2693
+ return {
2694
+ id,
2695
+ source: "chat-history",
2696
+ startedAt: b.first_ts ? String(b.first_ts) : null,
2697
+ endedAt: b.last_ts ? String(b.last_ts) : null,
2698
+ entryCount: Number(b.count ?? 0),
2699
+ summary
2700
+ };
2701
+ });
2702
+ }
2703
+ function _cc_prompt_text(rec) {
2704
+ if (rec.type !== "user" || rec.isSidechain === true) return null;
2705
+ const msg = rec.message;
2706
+ if (!msg || typeof msg !== "object" || Array.isArray(msg)) return null;
2707
+ const content = msg.content;
2708
+ if (typeof content !== "string") return null;
2709
+ const trimmed = content.trim();
2710
+ if (!trimmed || trimmed.startsWith("<") || trimmed.startsWith("Caveat:")) return null;
2711
+ return trimmed;
2712
+ }
2713
+ function _scan_claude_transcript(file) {
2714
+ const scan = {
2715
+ entryCount: 0,
2716
+ firstTs: null,
2717
+ lastTs: null,
2718
+ branch: null,
2719
+ firstPrompt: null,
2720
+ lastPrompt: null
2721
+ };
2722
+ for (const line of _read_lines2(file)) {
2723
+ const rec = _parse_json2(line);
2724
+ if (!rec) continue;
2725
+ scan.entryCount += 1;
2726
+ const ts = typeof rec.timestamp === "string" ? rec.timestamp : null;
2727
+ if (ts) {
2728
+ if (!scan.firstTs) scan.firstTs = ts;
2729
+ scan.lastTs = ts;
2730
+ }
2731
+ if (!scan.branch && typeof rec.gitBranch === "string" && rec.gitBranch) {
2732
+ scan.branch = rec.gitBranch;
2733
+ }
2734
+ const prompt = _cc_prompt_text(rec);
2735
+ if (prompt) {
2736
+ if (!scan.firstPrompt) scan.firstPrompt = prompt;
2737
+ scan.lastPrompt = prompt;
2738
+ }
2739
+ }
2740
+ return scan;
2741
+ }
2742
+ function _claude_transcript_sessions(opts, cap) {
2743
+ const root = opts.claudeProjectsRoot ?? path4.join(os.homedir(), ".claude", "projects");
2744
+ const cwd = opts.cwd ?? process.cwd();
2745
+ const projectDir = path4.join(root, encode_project_path(cwd));
2746
+ let names;
2747
+ try {
2748
+ names = fs5.readdirSync(projectDir).filter((n) => n.endsWith(".jsonl"));
2749
+ } catch {
2750
+ return [];
2751
+ }
2752
+ const files = names.map((n) => {
2753
+ const full = path4.join(projectDir, n);
2754
+ try {
2755
+ return { full, id: path4.basename(n, ".jsonl"), mtime: fs5.statSync(full).mtimeMs };
2756
+ } catch {
2757
+ return null;
2758
+ }
2759
+ }).filter((f) => f !== null).sort((a, b) => b.mtime - a.mtime).slice(0, cap);
2760
+ const sessions = [];
2761
+ for (const f of files) {
2762
+ const scan = _scan_claude_transcript(f.full);
2763
+ if (scan.entryCount === 0) continue;
2764
+ sessions.push({
2765
+ id: f.id,
2766
+ source: "claude-transcript",
2767
+ startedAt: scan.firstTs,
2768
+ endedAt: scan.lastTs,
2769
+ ...scan.branch ? { branch: scan.branch } : {},
2770
+ entryCount: scan.entryCount,
2771
+ summary: _summarize(scan.firstPrompt, scan.lastPrompt),
2772
+ transcriptPath: f.full
2773
+ });
2774
+ }
2775
+ return sessions;
2776
+ }
2777
+ function _codex_meta(file) {
2778
+ let firstLine = "";
2779
+ try {
2780
+ const fd = fs5.openSync(file, "r");
2781
+ try {
2782
+ const buf = Buffer.alloc(8192);
2783
+ const n = fs5.readSync(fd, buf, 0, buf.length, 0);
2784
+ firstLine = buf.toString("utf-8", 0, n).split("\n")[0] ?? "";
2785
+ } finally {
2786
+ fs5.closeSync(fd);
2787
+ }
2788
+ } catch {
2789
+ return null;
2790
+ }
2791
+ const rec = _parse_json2(firstLine);
2792
+ if (!rec || rec.type !== "session_meta") return null;
2793
+ const payload = rec.payload;
2794
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
2795
+ const p = payload;
2796
+ const git = p.git && typeof p.git === "object" && !Array.isArray(p.git) ? p.git : {};
2797
+ return {
2798
+ id: String(p.id ?? ""),
2799
+ timestamp: typeof p.timestamp === "string" ? p.timestamp : null,
2800
+ cwd: String(p.cwd ?? ""),
2801
+ branch: typeof git.branch === "string" && git.branch ? git.branch : null
2802
+ };
2803
+ }
2804
+ function _codex_files(root) {
2805
+ const out = [];
2806
+ const stack = [root];
2807
+ while (stack.length > 0) {
2808
+ const dir = stack.pop();
2809
+ let entries;
2810
+ try {
2811
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
2812
+ } catch {
2813
+ continue;
2814
+ }
2815
+ for (const e of entries) {
2816
+ const full = path4.join(dir, e.name);
2817
+ if (e.isDirectory()) stack.push(full);
2818
+ else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
2819
+ }
2820
+ }
2821
+ return out.sort((a, b) => path4.basename(a) < path4.basename(b) ? 1 : -1);
2822
+ }
2823
+ function _codex_prompt_text(rec) {
2824
+ if (rec.type !== "event_msg") return null;
2825
+ const p = rec.payload;
2826
+ if (!p || typeof p !== "object" || Array.isArray(p)) return null;
2827
+ const payload = p;
2828
+ if (payload.type !== "user_message") return null;
2829
+ const msg = typeof payload.message === "string" ? payload.message.trim() : "";
2830
+ if (!msg || msg.startsWith("<")) return null;
2831
+ return msg;
2832
+ }
2833
+ function _scan_codex_session(file) {
2834
+ const scan = { entryCount: 0, lastTs: null, firstPrompt: null, lastPrompt: null };
2835
+ for (const line of _read_lines2(file)) {
2836
+ const rec = _parse_json2(line);
2837
+ if (!rec) continue;
2838
+ scan.entryCount += 1;
2839
+ if (typeof rec.timestamp === "string") scan.lastTs = rec.timestamp;
2840
+ const prompt = _codex_prompt_text(rec);
2841
+ if (prompt) {
2842
+ if (!scan.firstPrompt) scan.firstPrompt = prompt;
2843
+ scan.lastPrompt = prompt;
2844
+ }
2845
+ }
2846
+ return scan;
2847
+ }
2848
+ function _matches_repo(sessionCwd, repoCwd) {
2849
+ const norm = (p) => path4.resolve(p).replace(/\/+$/, "");
2850
+ const a = norm(sessionCwd);
2851
+ const b = norm(repoCwd);
2852
+ return a === b || a.startsWith(b + path4.sep);
2853
+ }
2854
+ function _codex_sessions(opts, cap) {
2855
+ const root = opts.codexSessionsRoot ?? path4.join(os.homedir(), ".codex", "sessions");
2856
+ const cwd = opts.cwd ?? process.cwd();
2857
+ const matching = [];
2858
+ for (const file of _codex_files(root)) {
2859
+ if (matching.length >= cap) break;
2860
+ const meta = _codex_meta(file);
2861
+ if (!meta || !meta.id) continue;
2862
+ if (!_matches_repo(meta.cwd, cwd)) continue;
2863
+ matching.push({ file, meta });
2864
+ }
2865
+ const sessions = [];
2866
+ for (const { file, meta } of matching) {
2867
+ const scan = _scan_codex_session(file);
2868
+ if (scan.entryCount === 0) continue;
2869
+ sessions.push({
2870
+ id: meta.id,
2871
+ source: "codex-session",
2872
+ startedAt: meta.timestamp,
2873
+ endedAt: scan.lastTs ?? meta.timestamp,
2874
+ ...meta.branch ? { branch: meta.branch } : {},
2875
+ entryCount: scan.entryCount,
2876
+ summary: _summarize(scan.firstPrompt, scan.lastPrompt),
2877
+ transcriptPath: file
2878
+ });
2879
+ }
2880
+ return sessions;
2881
+ }
2882
+ function _sort_key(s) {
2883
+ return s.endedAt ?? s.startedAt ?? "";
2884
+ }
2885
+ function list_handoff_sessions(opts = {}) {
2886
+ const cap = opts.cap ?? DEFAULT_CAP;
2887
+ const primary = _chat_history_sessions(opts);
2888
+ const knownTags = new Set(primary.map((s) => s.id));
2889
+ const native = [
2890
+ ..._claude_transcript_sessions(opts, cap),
2891
+ ..._codex_sessions(opts, cap)
2892
+ ].filter((s) => {
2893
+ try {
2894
+ return !knownTags.has(derive_session_tag(s.id));
2895
+ } catch {
2896
+ return true;
2897
+ }
2898
+ });
2899
+ return [...primary, ...native].sort((a, b) => _sort_key(a) < _sort_key(b) ? 1 : _sort_key(a) > _sort_key(b) ? -1 : 0).slice(0, cap);
2900
+ }
2901
+
2902
+ // src/scripts/_cli/cmd_handoff.ts
2903
+ var PROG2 = "agent-config handoff";
2904
+ var USAGE = `usage: ${PROG2} [--list] [--json] [--session <id>] [--print] [--launch <host>] [--llm] [--root <path>]`;
2905
+ var _HELP = `${USAGE}
2906
+
2907
+ Pick a recent session, generate a deterministic handoff, and seed a fresh
2908
+ session with it. Without flags (in a TTY): numbered picker, then the handoff
2909
+ lands in agents/runtime/state/handoff-context.md and auto-injects into the
2910
+ next session on hook-capable hosts.
2911
+
2912
+ options:
2913
+ --list print the recent sessions and exit
2914
+ --json with --list: JSON output
2915
+ --session <id> generate for this session id (exact or unique prefix)
2916
+ --print print the handoff to stdout (no state file)
2917
+ --launch <host> start a fresh interactive session (claude | codex)
2918
+ --llm optional LLM polish \u2014 not implemented in v1
2919
+ --root <path> repo root (default: current directory)
2920
+ -h, --help show this help
2921
+
2922
+ exit codes:
2923
+ 0 handoff generated (or list printed)
2924
+ 1 no sessions found / session id not found
2925
+ 2 invocation error (bad flag, non-TTY without --session, unsupported host)
2926
+ `;
2927
+ var ArgparseExit = class extends Error {
2928
+ constructor(code) {
2929
+ super(`exit ${code}`);
2930
+ this.code = code;
2931
+ }
2932
+ code;
2933
+ };
2934
+ var LAUNCH_ADAPTERS = {
2935
+ claude: {
2936
+ supports_launch: true,
2937
+ seeding: "hook",
2938
+ build_argv: () => ["claude"]
2939
+ },
2940
+ codex: {
2941
+ // Codex is a bundle host without hooks — seed via the initial prompt.
2942
+ supports_launch: true,
2943
+ seeding: "initial-prompt",
2944
+ build_argv: (handoffText) => ["codex", handoffText]
2945
+ },
2946
+ gemini: {
2947
+ supports_launch: false,
2948
+ seeding: "hook",
2949
+ build_argv: () => ["gemini"]
2950
+ }
2951
+ };
2952
+ function _default_runner(cmd) {
2953
+ const r = spawnSync2(cmd[0], cmd.slice(1), { stdio: "inherit" });
2954
+ if (r.error) {
2955
+ process4.stderr.write(`${PROG2}: cannot run ${cmd[0]}: ${String(r.error)}
2956
+ `);
2957
+ return 127;
2958
+ }
2959
+ return r.status ?? 1;
2960
+ }
2961
+ function _tty_read_line(prompt) {
2962
+ try {
2963
+ process4.stdout.write(prompt);
2964
+ const fd = fs6.openSync("/dev/tty", "rs");
2965
+ try {
2966
+ const buf = Buffer.alloc(1);
2967
+ let line = "";
2968
+ for (; ; ) {
2969
+ let n = 0;
2970
+ try {
2971
+ n = fs6.readSync(fd, buf, 0, 1, null);
2972
+ } catch (exc) {
2973
+ const code = exc.code;
2974
+ if (code === "EAGAIN" || code === "EINTR") continue;
2975
+ return null;
2976
+ }
2977
+ if (n === 0) break;
2978
+ const ch = buf.toString("utf-8");
2979
+ if (ch === "\n") break;
2980
+ line += ch;
2981
+ }
2982
+ return line;
2983
+ } finally {
2984
+ fs6.closeSync(fd);
2985
+ }
2986
+ } catch {
2987
+ return null;
2988
+ }
2989
+ }
2990
+ function _argError2(err, message) {
2991
+ err(`${USAGE}
2992
+ ${PROG2}: error: ${message}
2993
+ `);
2994
+ throw new ArgparseExit(2);
2995
+ }
2996
+ function _parse(argv, err) {
2997
+ const opts = {
2998
+ list: false,
2999
+ json: false,
3000
+ session: null,
3001
+ print: false,
3002
+ launch: null,
3003
+ llm: false,
3004
+ root: null
3005
+ };
3006
+ let i = 0;
3007
+ const takeValue = (flag) => {
3008
+ const arg = argv[i];
3009
+ const eq = arg.indexOf("=");
3010
+ if (eq >= 0) return arg.slice(eq + 1);
3011
+ i += 1;
3012
+ const value = argv[i];
3013
+ if (value === void 0) _argError2(err, `argument ${flag}: expected one argument`);
3014
+ return value;
3015
+ };
3016
+ while (i < argv.length) {
3017
+ const arg = argv[i];
3018
+ const flag = arg.includes("=") ? arg.slice(0, arg.indexOf("=")) : arg;
3019
+ switch (flag) {
3020
+ case "-h":
3021
+ case "--help":
3022
+ process4.stdout.write(_HELP);
3023
+ throw new ArgparseExit(0);
3024
+ case "--list":
3025
+ opts.list = true;
3026
+ break;
3027
+ case "--json":
3028
+ opts.json = true;
3029
+ break;
3030
+ case "--print":
3031
+ opts.print = true;
3032
+ break;
3033
+ case "--llm":
3034
+ opts.llm = true;
3035
+ break;
3036
+ case "--session":
3037
+ opts.session = takeValue("--session");
3038
+ break;
3039
+ case "--launch":
3040
+ opts.launch = takeValue("--launch");
3041
+ break;
3042
+ case "--root":
3043
+ opts.root = takeValue("--root");
3044
+ break;
3045
+ default:
3046
+ _argError2(err, `unrecognized arguments: ${arg}`);
3047
+ }
3048
+ i += 1;
3049
+ }
3050
+ return opts;
3051
+ }
3052
+ function _fmt_date(iso) {
3053
+ if (!iso) return "unknown-date";
3054
+ return iso.slice(0, 16).replace("T", " ");
3055
+ }
3056
+ function render_session_line(s, index) {
3057
+ const branch = s.branch ? ` \xB7 ${s.branch}` : "";
3058
+ return `${String(index + 1).padStart(2)}. ${_fmt_date(s.endedAt ?? s.startedAt)} \xB7 ${s.source}${branch} \xB7 ${s.summary}`;
3059
+ }
3060
+ function _render_list(sessions, json) {
3061
+ if (json) return JSON.stringify(sessions, null, 2) + "\n";
3062
+ return sessions.map((s, i) => render_session_line(s, i)).join("\n") + "\n";
3063
+ }
3064
+ function main3(argv = null, options = {}) {
3065
+ const out = options.out ?? ((t) => void process4.stdout.write(t));
3066
+ const err = options.err ?? ((t) => void process4.stderr.write(t));
3067
+ const isTTY = options.isTTY ?? Boolean(process4.stdin.isTTY && process4.stdout.isTTY);
3068
+ const readLine = options.readLine ?? _tty_read_line;
3069
+ const runner = options.runner ?? _default_runner;
3070
+ let opts;
3071
+ try {
3072
+ opts = _parse(argv ?? process4.argv.slice(2), err);
3073
+ } catch (exc) {
3074
+ if (exc instanceof ArgparseExit) return exc.code;
3075
+ throw exc;
3076
+ }
3077
+ const envRoot = process4.env.AGENT_CONFIG_ROOT_OVERRIDE === "1" ? process4.env.AGENT_CONFIG_PROJECT_ROOT || null : null;
3078
+ const root = path5.resolve(opts.root ?? envRoot ?? process4.cwd());
3079
+ const sessions = list_handoff_sessions({ cwd: root });
3080
+ if (opts.list) {
3081
+ if (sessions.length === 0) {
3082
+ out("no recent sessions found for this repo\n");
3083
+ return 1;
3084
+ }
3085
+ out(_render_list(sessions, opts.json));
3086
+ return 0;
3087
+ }
3088
+ if (sessions.length === 0) {
3089
+ err(`${PROG2}: no recent sessions found for this repo
3090
+ `);
3091
+ return 1;
3092
+ }
3093
+ if (opts.launch && !(opts.launch in LAUNCH_ADAPTERS)) {
3094
+ err(`${USAGE}
3095
+ ${PROG2}: error: unknown --launch host '${opts.launch}' (claude | codex)
3096
+ `);
3097
+ return 2;
3098
+ }
3099
+ const adapter = opts.launch ? LAUNCH_ADAPTERS[opts.launch] : null;
3100
+ if (opts.launch && adapter && !adapter.supports_launch) {
3101
+ err(`${PROG2}: --launch ${opts.launch} is not supported yet (hook injection already works there)
3102
+ `);
3103
+ return 2;
3104
+ }
3105
+ let picked;
3106
+ if (opts.session) {
3107
+ const matches = sessions.filter((s) => s.id === opts.session || s.id.startsWith(opts.session));
3108
+ if (matches.length === 0) {
3109
+ err(`${PROG2}: session '${opts.session}' not found \u2014 run --list
3110
+ `);
3111
+ return 1;
3112
+ }
3113
+ if (matches.length > 1) {
3114
+ err(`${PROG2}: session prefix '${opts.session}' is ambiguous (${matches.length} matches)
3115
+ `);
3116
+ return 2;
3117
+ }
3118
+ picked = matches[0];
3119
+ } else if (isTTY) {
3120
+ out("Recent sessions:\n");
3121
+ out(_render_list(sessions, false));
3122
+ const raw = readLine(`Pick a session [1-${sessions.length}]: `);
3123
+ const n = Number((raw ?? "").trim());
3124
+ if (!Number.isInteger(n) || n < 1 || n > sessions.length) {
3125
+ err(`${PROG2}: invalid selection
3126
+ `);
3127
+ return 2;
3128
+ }
3129
+ picked = sessions[n - 1];
3130
+ } else {
3131
+ out(_render_list(sessions, false));
3132
+ err(`${PROG2}: non-interactive invocation needs --session <id> (list above)
3133
+ `);
3134
+ return 2;
3135
+ }
3136
+ if (!picked) return 2;
3137
+ let text;
3138
+ try {
3139
+ text = build_handoff(picked, { cwd: root, llm: opts.llm });
3140
+ } catch (exc) {
3141
+ if (exc instanceof LlmPolishNotImplementedError) {
3142
+ err(`${PROG2}: ${exc.message}
3143
+ `);
3144
+ return 2;
3145
+ }
3146
+ throw exc;
3147
+ }
3148
+ if (opts.print) {
3149
+ out(text);
3150
+ return 0;
3151
+ }
3152
+ if (adapter && adapter.seeding === "initial-prompt") {
3153
+ out(`launching ${opts.launch} with the handoff as initial prompt\u2026
3154
+ `);
3155
+ return runner(adapter.build_argv(text));
3156
+ }
3157
+ const existed = fs6.existsSync(handoff_target_path(root));
3158
+ const target = write_handoff(text, { cwd: root });
3159
+ if (existed) out("note: replaced an unconsumed handoff \u2014 only the most recent one persists.\n");
3160
+ out(`handoff written: ${target}
3161
+ `);
3162
+ out("start a new session \u2014 the handoff will be injected automatically (hook-capable hosts).\n");
3163
+ if (adapter) {
3164
+ out(`launching ${opts.launch}\u2026
3165
+ `);
3166
+ return runner(adapter.build_argv(text));
3167
+ }
3168
+ return 0;
3169
+ }
3170
+ function _isCliEntry2() {
3171
+ if (process4.argv[1] === void 0) {
3172
+ return false;
3173
+ }
3174
+ const argvUrl = pathToFileURL2(path5.resolve(process4.argv[1])).href;
3175
+ if (import.meta.url === argvUrl) {
3176
+ return true;
3177
+ }
3178
+ try {
3179
+ const here = fs6.realpathSync(fileURLToPath3(import.meta.url));
3180
+ const argv = fs6.realpathSync(path5.resolve(process4.argv[1]));
3181
+ return here === argv;
3182
+ } catch {
3183
+ return false;
3184
+ }
3185
+ }
3186
+ if (_isCliEntry2()) {
3187
+ process4.exitCode = main3();
3188
+ }
3189
+ export {
3190
+ LAUNCH_ADAPTERS,
3191
+ _parse,
3192
+ main3 as main,
3193
+ render_session_line
3194
+ };