token-goat 2.9.18 → 2.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/token-goat-chunk-2CLWWRFR.mjs +26 -0
- package/dist/{token-goat-chunk-LTYWLKAG.mjs → token-goat-chunk-2Q3N5KUQ.mjs} +1283 -1012
- package/dist/{token-goat-chunk-6LGVCCMY.mjs → token-goat-chunk-3OGZOFLS.mjs} +26 -16
- package/dist/{token-goat-chunk-QML7ITHJ.mjs → token-goat-chunk-3VTJC7KU.mjs} +693 -438
- package/dist/{token-goat-chunk-QIDLJGXZ.mjs → token-goat-chunk-6LJNM4ZZ.mjs} +29 -5
- package/dist/{token-goat-chunk-CA6V36KW.mjs → token-goat-chunk-74ADIZZX.mjs} +68 -50
- package/dist/{token-goat-chunk-52IMXJ2L.mjs → token-goat-chunk-ASTBLE2R.mjs} +4 -4
- package/dist/token-goat-chunk-AU2K2QHR.mjs +20 -0
- package/dist/token-goat-chunk-BQIC3AVL.mjs +292 -0
- package/dist/token-goat-chunk-CBXLE54Q.mjs +30 -0
- package/dist/{token-goat-chunk-LMFO66YD.mjs → token-goat-chunk-DA43OE7L.mjs} +1 -1
- package/dist/{token-goat-chunk-ARNRFR6M.mjs → token-goat-chunk-DNAF26L4.mjs} +1 -1
- package/dist/{token-goat-chunk-BFEWOMA3.mjs → token-goat-chunk-DPHJU3P7.mjs} +1186 -4573
- package/dist/{token-goat-chunk-OWSJJY5R.mjs → token-goat-chunk-DW4VKU7D.mjs} +5 -5
- package/dist/token-goat-chunk-DYUBZZNP.mjs +198 -0
- package/dist/{token-goat-chunk-W3E65QOW.mjs → token-goat-chunk-ERGHDBC6.mjs} +310 -47
- package/dist/token-goat-chunk-EUZKG7ZO.mjs +304 -0
- package/dist/{token-goat-chunk-5MMJAJF3.mjs → token-goat-chunk-FWYZVS3E.mjs} +324 -31
- package/dist/token-goat-chunk-G6SYQQO7.mjs +41 -0
- package/dist/{token-goat-chunk-YQZE5NS6.mjs → token-goat-chunk-GIPMOGOI.mjs} +6 -6
- package/dist/{token-goat-chunk-BWCRC23L.mjs → token-goat-chunk-H4VC5TR6.mjs} +29 -20
- package/dist/{token-goat-chunk-V3ZWBEHM.mjs → token-goat-chunk-I5PQRHFE.mjs} +103 -24
- package/dist/token-goat-chunk-ITWYDOKP.mjs +341 -0
- package/dist/{token-goat-chunk-I67CPTDD.mjs → token-goat-chunk-K3ZNBN4U.mjs} +19 -14
- package/dist/{token-goat-chunk-HPOFR6QX.mjs → token-goat-chunk-K5XPIUVB.mjs} +222 -61
- package/dist/token-goat-chunk-KT75DZK4.mjs +1989 -0
- package/dist/{token-goat-chunk-H3FONFBO.mjs → token-goat-chunk-MNJWG2ZO.mjs} +3 -3
- package/dist/{token-goat-chunk-GRI6HWX3.mjs → token-goat-chunk-MUJV7BR3.mjs} +1 -1
- package/dist/{token-goat-chunk-ERTXEKB6.mjs → token-goat-chunk-NMTKNYGF.mjs} +2 -0
- package/dist/{token-goat-chunk-LA3O4JGD.mjs → token-goat-chunk-ODF7H7WS.mjs} +3 -3
- package/dist/{token-goat-chunk-K4DG6EEF.mjs → token-goat-chunk-ONMU7BON.mjs} +7 -8
- package/dist/{token-goat-chunk-4EXFN2AW.mjs → token-goat-chunk-OZRSREKR.mjs} +2 -2
- package/dist/{token-goat-chunk-EPXNOKIV.mjs → token-goat-chunk-P4XBHZXU.mjs} +2 -2
- package/dist/{token-goat-chunk-CTOYJ232.mjs → token-goat-chunk-PAVWB4L4.mjs} +173 -1840
- package/dist/{token-goat-chunk-UENEL6KX.mjs → token-goat-chunk-PTW22MRC.mjs} +2 -2
- package/dist/token-goat-chunk-SL454EN5.mjs +1816 -0
- package/dist/token-goat-chunk-SP5QR2HN.mjs +1379 -0
- package/dist/{token-goat-chunk-7VLWNQDQ.mjs → token-goat-chunk-T46UKRFM.mjs} +1 -1
- package/dist/{token-goat-chunk-L7VCX33I.mjs → token-goat-chunk-T63VHOL3.mjs} +40 -10
- package/dist/token-goat-chunk-VSJGPEBB.mjs +248 -0
- package/dist/{token-goat-chunk-HIZXQVNU.mjs → token-goat-chunk-VYXGHGUY.mjs} +4 -4
- package/dist/token-goat-chunk-W6QYCWAT.mjs +128 -0
- package/dist/{token-goat-chunk-WFX2V3OF.mjs → token-goat-chunk-Z4ALQADF.mjs} +579 -422
- package/dist/token-goat-chunk-ZHFM4DHJ.mjs +324 -0
- package/dist/token-goat-hook.mjs +18 -12
- package/dist/token-goat.core.mjs +29 -20
- package/docs/C4_RUNTIME_ARCHITECTURE.md +1 -1
- package/docs/cli.md +15 -13
- package/docs/install.md +1 -1
- package/docs/security.md +2 -2
- package/package.json +3 -2
- package/dist/token-goat-chunk-2KTZ6J7T.mjs +0 -173
- package/dist/token-goat-chunk-34OUJ2IE.mjs +0 -35
- package/dist/token-goat-chunk-RHHFEXKF.mjs +0 -93
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
foldCase,
|
|
16
16
|
foldPath,
|
|
17
17
|
sleepSync
|
|
18
|
-
} from "./token-goat-chunk-
|
|
18
|
+
} from "./token-goat-chunk-6LJNM4ZZ.mjs";
|
|
19
19
|
import {
|
|
20
20
|
safeJoin
|
|
21
|
-
} from "./token-goat-chunk-
|
|
21
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
22
22
|
import {
|
|
23
23
|
init_define_import_meta_env
|
|
24
24
|
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -1666,6 +1666,10 @@ var LANGUAGE_SPECS = [
|
|
|
1666
1666
|
// Oracle PL/SQL sources: package spec and body, standalone procedure/function, trigger, and object type spec and body.
|
|
1667
1667
|
{ id: "sql", extraction: "regex", extensions: [".sql", ".pks", ".pkb", ".pls", ".plsql", ".pck", ".prc", ".fnc", ".trg", ".tps", ".tpb"], label: "SQL", ...DATA, symbolBearing: true, diffable: true, fence: "sql" },
|
|
1668
1668
|
{ id: "ini", extraction: "regex", extensions: [".ini", ".cfg", ".conf"], label: "INI", ...DATA, fence: "ini" },
|
|
1669
|
+
{ id: "nginx", extraction: "regex", extensions: [".nginx"], basenames: ["nginx.conf"], label: "Nginx", ...DATA, symbolBearing: true, diffable: true, fence: "nginx" },
|
|
1670
|
+
// `basenames` is matched against a lower-cased basename, so `Caddyfile` is spelled in lower case here; the display spelling is the label's job.
|
|
1671
|
+
{ id: "caddy", extraction: "regex", extensions: [".caddy"], basenames: ["caddyfile", "caddy.conf"], label: "Caddyfile", ...DATA, symbolBearing: true, diffable: true, fence: "caddy" },
|
|
1672
|
+
{ id: "apache", extraction: "regex", extensions: [".apache", ".apache2"], basenames: ["httpd.conf", "apache2.conf", ".htaccess"], label: "Apache Config", ...DATA, symbolBearing: true, diffable: true, fence: "apache" },
|
|
1669
1673
|
// `.mk` fragments (config.mk, rules.mk) share a bare Makefile's syntax.
|
|
1670
1674
|
{ id: "makefile", extraction: "regex", extensions: [".mk"], basenames: ["makefile", "gnumakefile", "bsdmakefile"], ...DATA, fence: "makefile", basenameImportsExtension: ".mk" },
|
|
1671
1675
|
{ id: "proto", extraction: "regex", extensions: [".proto"], label: "Protocol Buffers", ...DATA, symbolBearing: true, fence: "protobuf" },
|
|
@@ -2044,12 +2048,29 @@ CREATE INDEX IF NOT EXISTS idx_cache_recall_type ON cache_recall(cache_type);
|
|
|
2044
2048
|
-- emission event (a hook returning a 'context' HookOutput classified as a discretionary
|
|
2045
2049
|
-- efficiency nudge, as opposed to a mandatory informational injection -- see hint_stats.ts's
|
|
2046
2050
|
-- doc comment for the exact category list and what is deliberately excluded). correlator is a
|
|
2047
|
-
--
|
|
2051
|
+
-- file-path/output-id pointer the hint carries, used to check
|
|
2048
2052
|
-- whether a later Bash tool call in the same session actually followed the hint's specific
|
|
2049
|
-
-- pointer (see resolvePendingHintsForEvent)
|
|
2050
|
-
--
|
|
2051
|
-
--
|
|
2052
|
-
--
|
|
2053
|
+
-- pointer (see resolvePendingHintsForEvent). Supplied by the hint builder itself via
|
|
2054
|
+
-- HookOutput's correlators field wherever one exists, and only fallen back to a regex scrape
|
|
2055
|
+
-- of the rendered hint text (hint_stats.ts's extractPathCorrelator, whose doc comment records
|
|
2056
|
+
-- what that scrape actually measured) where it does not. A row can name SEVERAL files -- the
|
|
2057
|
+
-- sed/awk line-range hint joins one per file into one emission -- stored newline-separated and
|
|
2058
|
+
-- matched on any member; see joinCorrelators. NULL when no pointer could be supplied or extracted,
|
|
2059
|
+
-- in which case the row is inserted already resolved and observable=0: there is nothing a later
|
|
2060
|
+
-- command could do that this row would recognize, so no verdict about it was ever observed.
|
|
2061
|
+
-- observable=1 is the normal case. Efficacy counts only observable rows, numerator and denominator
|
|
2062
|
+
-- both, because an unobservable row's acted_on is a fabrication in whichever direction the code
|
|
2063
|
+
-- picked and averaging fabrications into a measured rate is how 174 of bash_redirect's 698 rows
|
|
2064
|
+
-- came to hold its efficacy down to 0.86% when the observed figure was 1.15%. calls_remaining is
|
|
2065
|
+
-- the countdown of subsequent tool-use events still eligible to resolve this row before it is
|
|
2066
|
+
-- considered timed out. displayed=0 marks a detection that never reached the agent -- auto-
|
|
2067
|
+
-- suppressed, or declined by a hint's own net-benefit gate -- recorded so the ledger can answer
|
|
2068
|
+
-- how often a hint fired at all, which it previously could not: a suppressed detection left no
|
|
2069
|
+
-- trace, so a fully-muted category was indistinguishable from one that had stopped triggering.
|
|
2070
|
+
-- Such a row is always observable=0 and bytes_emitted=0: nothing was shown, so nothing was spent
|
|
2071
|
+
-- and no verdict was available. It shares hint_emissions' 180-day retention (pruneHintEmissions
|
|
2072
|
+
-- in stats.ts deletes on emitted_at alone), deliberately unlike hint_suppression_probes, which is
|
|
2073
|
+
-- one upserted row per key rather than a growing log and is therefore never pruned.
|
|
2053
2074
|
CREATE TABLE IF NOT EXISTS hint_emissions (
|
|
2054
2075
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2055
2076
|
category TEXT NOT NULL,
|
|
@@ -2060,7 +2081,9 @@ CREATE TABLE IF NOT EXISTS hint_emissions (
|
|
|
2060
2081
|
resolved INTEGER NOT NULL DEFAULT 0,
|
|
2061
2082
|
acted_on INTEGER NOT NULL DEFAULT 0,
|
|
2062
2083
|
calls_remaining INTEGER NOT NULL DEFAULT 0,
|
|
2063
|
-
bytes_emitted INTEGER
|
|
2084
|
+
bytes_emitted INTEGER,
|
|
2085
|
+
observable INTEGER NOT NULL DEFAULT 1,
|
|
2086
|
+
displayed INTEGER NOT NULL DEFAULT 1
|
|
2064
2087
|
);
|
|
2065
2088
|
CREATE INDEX IF NOT EXISTS idx_hint_emissions_category ON hint_emissions(category);
|
|
2066
2089
|
CREATE INDEX IF NOT EXISTS idx_hint_emissions_session_pending ON hint_emissions(session_id, resolved);
|
|
@@ -2206,7 +2229,7 @@ CREATE TRIGGER IF NOT EXISTS cache_recall_au AFTER UPDATE ON cache_recall BEGIN
|
|
|
2206
2229
|
VALUES (new.row_id, new.label, new.content);
|
|
2207
2230
|
END;
|
|
2208
2231
|
`;
|
|
2209
|
-
var SCHEMA_VERSION =
|
|
2232
|
+
var SCHEMA_VERSION = 16;
|
|
2210
2233
|
function alterTableIdempotent(conn, sql) {
|
|
2211
2234
|
try {
|
|
2212
2235
|
conn.exec(sql);
|
|
@@ -2262,7 +2285,14 @@ var MIGRATIONS = {
|
|
|
2262
2285
|
// v12 -> v13: adds files.parser_sha, the digest of the extraction logic that produced this file's rows, tracked separately from files.sha for the same reason embed_sha is -- content freshness and parse freshness are different questions, and the content sha alone could only ever answer the first. A pre-existing v12 database's `files` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v1 -> v2 / v2 -> v3 / v8 -> v9 / v9 -> v10 above. Deliberately left NULL for every existing row rather than backfilled with the current fingerprint: NULL is the truthful answer (nobody recorded which parser wrote those rows), and it is also the answer that makes the freshness gates reparse them once, which is exactly what a database indexed by an older parser needs. v13 -> v14: changes both FTS5 tables' tokenizer to `unicode61 remove_diacritics 2`, so a search for `Noi` or `Viet` finds `Hà Nội` and `Việt Nam` -- combining marks that `remove_diacritics 1`, FTS5's default, leaves in place. This is the first schema change that `CREATE VIRTUAL TABLE IF NOT EXISTS` cannot express at all rather than merely cannot express on a populated table: against an existing virtual table that statement is a silent no-op, so without MIGRATIONS[13] the new tokenizer would reach only databases created after this release. The step drops both tables, re-runs FTS_SQL to re-create them at the current declaration, and rebuilds each from its content table.
|
|
2263
2286
|
12: (conn) => alterTableIdempotent(conn, "ALTER TABLE files ADD COLUMN parser_sha TEXT"),
|
|
2264
2287
|
// v13 -> v14: re-creates both FTS5 tables at the tokenizer FTS_SQL currently declares (see the SCHEMA_VERSION comment above for why no `IF NOT EXISTS` form can do this).
|
|
2265
|
-
13: rebuildFtsAtCurrentTokenizer
|
|
2288
|
+
13: rebuildFtsAtCurrentTokenizer,
|
|
2289
|
+
// v14 -> v15: adds hint_emissions.observable and backfills it from the column that always implied it. A pre-existing v14 database's `hint_emissions` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v8 -> v9 / v9 -> v10 / v12 -> v13 above. The backfill is exact rather than a guess: until this version a NULL correlator was the only way a row could be born with nothing to wait for, so `correlator IS NULL` identifies precisely the population the new column names. It runs unconditionally after the ALTER because alterTableIdempotent swallows the duplicate-column case, and re-running the UPDATE on an already-migrated database sets the same rows to the same values.
|
|
2290
|
+
14: (conn) => {
|
|
2291
|
+
alterTableIdempotent(conn, "ALTER TABLE hint_emissions ADD COLUMN observable INTEGER NOT NULL DEFAULT 1");
|
|
2292
|
+
conn.prepare("UPDATE hint_emissions SET observable = 0 WHERE correlator IS NULL").run();
|
|
2293
|
+
},
|
|
2294
|
+
// v15 -> v16: adds hint_emissions.displayed (see the SCHEMA_VERSION comment above for why). A pre-existing v15 database's `hint_emissions` table predates the column, so it needs an explicit ALTER TABLE here; a brand-new database already has it from SCHEMA_SQL's CREATE TABLE above, so the ALTER TABLE would fail with "duplicate column name" there -- swallow exactly that error and rethrow anything else, same pattern as v8 -> v9 / v9 -> v10 / v12 -> v13 / v14 -> v15 above. No backfill, unlike v14 -> v15: until this version a row was only ever written when a hint was actually shown, so the column's own DEFAULT 1 is already the truth for every existing row, and an UPDATE would be a no-op dressed up as a migration step.
|
|
2295
|
+
15: (conn) => alterTableIdempotent(conn, "ALTER TABLE hint_emissions ADD COLUMN displayed INTEGER NOT NULL DEFAULT 1")
|
|
2266
2296
|
};
|
|
2267
2297
|
function runMigrations(conn, fromVersion, toVersion) {
|
|
2268
2298
|
for (let v = fromVersion; v < toVersion; v++) {
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
WEB_FETCH_KEY_SEP,
|
|
5
|
+
computeAdaptiveBudget,
|
|
6
|
+
getContextPressure,
|
|
7
|
+
getSessionBashOutputs,
|
|
8
|
+
getSessionBashReruns,
|
|
9
|
+
getSessionFiles,
|
|
10
|
+
getSessionWebFetches,
|
|
11
|
+
isNoisePath,
|
|
12
|
+
listSiblingSessionStates,
|
|
13
|
+
loadSessionCache
|
|
14
|
+
} from "./token-goat-chunk-KT75DZK4.mjs";
|
|
15
|
+
import {
|
|
16
|
+
getBashOutput
|
|
17
|
+
} from "./token-goat-chunk-SL454EN5.mjs";
|
|
18
|
+
import {
|
|
19
|
+
loadConfig
|
|
20
|
+
} from "./token-goat-chunk-74ADIZZX.mjs";
|
|
21
|
+
import {
|
|
22
|
+
foldPath,
|
|
23
|
+
runGit,
|
|
24
|
+
toKB
|
|
25
|
+
} from "./token-goat-chunk-6LJNM4ZZ.mjs";
|
|
26
|
+
import {
|
|
27
|
+
displaySafePath,
|
|
28
|
+
displaySafeText,
|
|
29
|
+
neutralizeSpokenMarkers
|
|
30
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
31
|
+
import {
|
|
32
|
+
init_define_import_meta_env
|
|
33
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
34
|
+
|
|
35
|
+
// src/manifest.ts
|
|
36
|
+
init_define_import_meta_env();
|
|
37
|
+
import { spawnSync } from "node:child_process";
|
|
38
|
+
var MEM_EPOCH_TIMEOUT_MS = 800;
|
|
39
|
+
var MAX_ROWS = 40;
|
|
40
|
+
function appendCappedSection(lines, header, rows, cap) {
|
|
41
|
+
if (rows.length === 0) return;
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push(header);
|
|
44
|
+
for (const row of rows.slice(0, cap)) lines.push(row);
|
|
45
|
+
if (rows.length > cap) lines.push(`- ...and ${rows.length - cap} more`);
|
|
46
|
+
}
|
|
47
|
+
function renderReadRow(entry) {
|
|
48
|
+
const kb = Math.max(1, toKB(entry.sizeBytes));
|
|
49
|
+
const plural = entry.readCount === 1 ? "read" : "reads";
|
|
50
|
+
const edited = entry.wasEdited ? ", edited" : "";
|
|
51
|
+
return `- ${displaySafePath(entry.path)} (${kb}kb, ${entry.readCount} ${plural}${edited})`;
|
|
52
|
+
}
|
|
53
|
+
function renderSymbolReadRow(entry) {
|
|
54
|
+
return `- ${displaySafePath(entry.path)} (symbols: ${(entry.symbols_read ?? []).map(displaySafeText).join(", ")})`;
|
|
55
|
+
}
|
|
56
|
+
function mergeManifestFiles(parent, siblingFiles) {
|
|
57
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const f of parent) byPath.set(foldPath(f.path), f);
|
|
59
|
+
for (const f of siblingFiles) {
|
|
60
|
+
const key = foldPath(f.path);
|
|
61
|
+
const prev = byPath.get(key);
|
|
62
|
+
if (prev === void 0) {
|
|
63
|
+
byPath.set(key, f);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const mergedSymbols = [.../* @__PURE__ */ new Set([...prev.symbols_read ?? [], ...f.symbols_read ?? []])];
|
|
67
|
+
byPath.set(key, {
|
|
68
|
+
path: prev.path,
|
|
69
|
+
readCount: Math.max(prev.readCount, f.readCount),
|
|
70
|
+
lastReadAt: Math.max(prev.lastReadAt, f.lastReadAt),
|
|
71
|
+
wasEdited: prev.wasEdited || f.wasEdited,
|
|
72
|
+
sizeBytes: f.lastReadAt >= prev.lastReadAt ? f.sizeBytes : prev.sizeBytes,
|
|
73
|
+
...prev.wasTruncated || f.wasTruncated ? { wasTruncated: true } : {},
|
|
74
|
+
...mergedSymbols.length > 0 ? { symbols_read: mergedSymbols } : {}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return Array.from(byPath.values());
|
|
78
|
+
}
|
|
79
|
+
function renderWebFetchRow(key, cacheId) {
|
|
80
|
+
const [url = key, prompt = ""] = key.split(WEB_FETCH_KEY_SEP);
|
|
81
|
+
const promptSuffix = prompt ? `, prompt: ${neutralizeSpokenMarkers(JSON.stringify(prompt))}` : "";
|
|
82
|
+
return `- ${displaySafeText(url)} (cacheId: ${cacheId}${promptSuffix})`;
|
|
83
|
+
}
|
|
84
|
+
function selectManifestFiles(siblings) {
|
|
85
|
+
const ownFiles = [...getSessionFiles().values()];
|
|
86
|
+
const siblingFiles = siblings.flatMap((s) => s.files);
|
|
87
|
+
const files = siblingFiles.length > 0 ? mergeManifestFiles(ownFiles, siblingFiles) : ownFiles;
|
|
88
|
+
const editedFiles = files.filter((f) => f.wasEdited);
|
|
89
|
+
const readFiles = files.filter((f) => f.readCount > 0 && !f.wasEdited && !isNoisePath(f.path));
|
|
90
|
+
const symbolOnlyFiles = files.filter((f) => f.readCount === 0 && !f.wasEdited && (f.symbols_read?.length ?? 0) > 0);
|
|
91
|
+
return { files, readFiles, editedFiles, symbolOnlyFiles };
|
|
92
|
+
}
|
|
93
|
+
function selectManifestWebFetches(siblings) {
|
|
94
|
+
const merged = /* @__PURE__ */ new Map();
|
|
95
|
+
for (const sibling of siblings) for (const [key, cacheId] of sibling.webFetches) merged.set(key, cacheId);
|
|
96
|
+
for (const [key, cacheId] of getSessionWebFetches()) merged.set(key, cacheId);
|
|
97
|
+
return [...merged.entries()];
|
|
98
|
+
}
|
|
99
|
+
function manifestPrintedPaths(sessionId, limit, cwd, transcriptPath) {
|
|
100
|
+
const { printed } = buildManifestParts(sessionId, cwd, transcriptPath);
|
|
101
|
+
return printed.slice(0, limit);
|
|
102
|
+
}
|
|
103
|
+
function buildManifestParts(sessionId, cwd, transcriptPath) {
|
|
104
|
+
const siblings = sessionId !== void 0 ? listSiblingSessionStates(sessionId) : [];
|
|
105
|
+
const { files, readFiles, editedFiles, symbolOnlyFiles } = selectManifestFiles(siblings);
|
|
106
|
+
const webFetches = selectManifestWebFetches(siblings);
|
|
107
|
+
const lines = [];
|
|
108
|
+
lines.push("## Session context");
|
|
109
|
+
lines.push(`Files read: ${readFiles.length}`);
|
|
110
|
+
lines.push(`Files edited: ${editedFiles.length}`);
|
|
111
|
+
const fileRows = [];
|
|
112
|
+
const appendFileSection = (header, entries, render) => {
|
|
113
|
+
const rows = entries.map(render);
|
|
114
|
+
appendCappedSection(lines, header, rows, MAX_ROWS);
|
|
115
|
+
for (let i = 0; i < Math.min(rows.length, MAX_ROWS); i++) fileRows.push({ path: displaySafePath(entries[i].path), row: rows[i] });
|
|
116
|
+
};
|
|
117
|
+
appendFileSection("### Edited files", editedFiles, (entry) => `- ${displaySafePath(entry.path)}`);
|
|
118
|
+
appendFileSection("### Surgically read files (symbol/section reads, never read whole)", symbolOnlyFiles, renderSymbolReadRow);
|
|
119
|
+
appendFileSection("### Read files", readFiles, renderReadRow);
|
|
120
|
+
appendCappedSection(lines, "### Web URLs fetched", webFetches.map(([key, cacheId]) => renderWebFetchRow(key, cacheId)), MAX_ROWS);
|
|
121
|
+
lines.push(...buildSafeToDiscardSection(files));
|
|
122
|
+
lines.push(...buildMemEpochSection());
|
|
123
|
+
const text = capManifestChars(lines.join("\n"), sessionId, cwd, transcriptPath);
|
|
124
|
+
const seen = /* @__PURE__ */ new Set();
|
|
125
|
+
const printed = [];
|
|
126
|
+
for (const { path, row } of fileRows) {
|
|
127
|
+
if (seen.has(path) || !(text.includes(row + "\n") || text.endsWith(row))) continue;
|
|
128
|
+
seen.add(path);
|
|
129
|
+
printed.push(path);
|
|
130
|
+
}
|
|
131
|
+
return { text, printed };
|
|
132
|
+
}
|
|
133
|
+
function buildManifest(sessionId, cwd, transcriptPath) {
|
|
134
|
+
return buildManifestParts(sessionId, cwd, transcriptPath).text;
|
|
135
|
+
}
|
|
136
|
+
function gitDirtySignals(cwd) {
|
|
137
|
+
const timeoutMs = loadConfig().hints.git_hint_max_ms;
|
|
138
|
+
let hasPendingDiff = false;
|
|
139
|
+
try {
|
|
140
|
+
const diffResult = runGit(["diff", "--no-color", "--stat", "HEAD"], { cwd, timeoutMs });
|
|
141
|
+
hasPendingDiff = diffResult.exitCode === 0 && diffResult.stdout.trim() !== "";
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
let hasUncommittedChanges = false;
|
|
145
|
+
try {
|
|
146
|
+
const statusResult = runGit(["status", "--porcelain"], { cwd, timeoutMs });
|
|
147
|
+
hasUncommittedChanges = statusResult.exitCode === 0 && statusResult.stdout.trim() !== "";
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
return { hasPendingDiff, hasUncommittedChanges };
|
|
151
|
+
}
|
|
152
|
+
function adaptiveCharBonus(sessionId, cwd, transcriptPath) {
|
|
153
|
+
if (!cwd) return 0;
|
|
154
|
+
const cache = loadSessionCache(sessionId ?? "") ?? {};
|
|
155
|
+
const ageSecs = cache.created_ts !== void 0 ? Math.max(0, Date.now() / 1e3 - cache.created_ts) : 0;
|
|
156
|
+
const contextPressure = getContextPressure(cache, transcriptPath);
|
|
157
|
+
const { hasPendingDiff, hasUncommittedChanges } = gitDirtySignals(cwd);
|
|
158
|
+
if (!hasPendingDiff && !hasUncommittedChanges) return 0;
|
|
159
|
+
const baseline = computeAdaptiveBudget(cache, ageSecs, { contextPressure });
|
|
160
|
+
const withGitSignal = computeAdaptiveBudget(cache, ageSecs, { hasPendingDiff, hasUncommittedChanges, contextPressure });
|
|
161
|
+
const deltaTokens = Math.max(0, withGitSignal - baseline);
|
|
162
|
+
return deltaTokens * 3;
|
|
163
|
+
}
|
|
164
|
+
function capManifestChars(manifest, sessionId, cwd, transcriptPath) {
|
|
165
|
+
const cap = loadConfig().compact_assist.max_manifest_chars;
|
|
166
|
+
if (cap <= 0) return manifest;
|
|
167
|
+
if (manifest.length <= cap) return manifest;
|
|
168
|
+
const effectiveCap = cap + adaptiveCharBonus(sessionId, cwd, transcriptPath);
|
|
169
|
+
if (manifest.length <= effectiveCap) return manifest;
|
|
170
|
+
const omitted = manifest.length - effectiveCap;
|
|
171
|
+
return manifest.slice(0, effectiveCap) + `
|
|
172
|
+
...(manifest truncated at ${effectiveCap} chars; ${omitted} chars omitted)`;
|
|
173
|
+
}
|
|
174
|
+
function buildSafeToDiscardSection(files) {
|
|
175
|
+
const rerunHashes = getSessionBashReruns();
|
|
176
|
+
const bashOutputs = getSessionBashOutputs();
|
|
177
|
+
const rerunHashSet = new Set(rerunHashes);
|
|
178
|
+
const rerunRows = [];
|
|
179
|
+
for (const hash of rerunHashes) {
|
|
180
|
+
const id = bashOutputs.find(([h]) => h === hash)?.[1];
|
|
181
|
+
if (id === void 0) continue;
|
|
182
|
+
const entry = getBashOutput(id);
|
|
183
|
+
if (entry === null) continue;
|
|
184
|
+
const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
|
|
185
|
+
rerunRows.push("- `" + flatCommand + "` \u2014 an older run of this exact command was superseded; recall the surviving copy with `bash-output " + id + " --full`");
|
|
186
|
+
}
|
|
187
|
+
const supersededReadRows = [];
|
|
188
|
+
for (const f of files) {
|
|
189
|
+
if (f.readCount > 1 || f.wasEdited) {
|
|
190
|
+
const reason = f.wasEdited ? "edited after being read" : "re-read " + f.readCount + "x";
|
|
191
|
+
supersededReadRows.push("- " + displaySafePath(f.path) + " (" + reason + " \u2014 only the latest content already in context is current)");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const cachedOutputRows = [];
|
|
195
|
+
for (const [hash, id] of bashOutputs) {
|
|
196
|
+
if (rerunHashSet.has(hash)) continue;
|
|
197
|
+
const entry = getBashOutput(id);
|
|
198
|
+
if (entry === null) continue;
|
|
199
|
+
const flatCommand = neutralizeSpokenMarkers(entry.command.replace(/[\t\r\n]+/g, " "));
|
|
200
|
+
cachedOutputRows.push("- `" + flatCommand + "` \u2014 recallable via `bash-output " + id + " --full`");
|
|
201
|
+
}
|
|
202
|
+
const total = rerunRows.length + supersededReadRows.length + cachedOutputRows.length;
|
|
203
|
+
if (total === 0) return [];
|
|
204
|
+
const lines = [];
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("### SAFE_TO_DISCARD (" + total + " items \u2014 provably inert; each is recallable, not gone)");
|
|
207
|
+
appendCappedSection(lines, "Superseded reruns (" + rerunRows.length + "):", rerunRows, MAX_ROWS);
|
|
208
|
+
appendCappedSection(lines, "Superseded file reads (" + supersededReadRows.length + "):", supersededReadRows, MAX_ROWS);
|
|
209
|
+
appendCappedSection(lines, "Other cached bash outputs (" + cachedOutputRows.length + "):", cachedOutputRows, MAX_ROWS);
|
|
210
|
+
return lines;
|
|
211
|
+
}
|
|
212
|
+
function buildMemEpochSection() {
|
|
213
|
+
let result;
|
|
214
|
+
try {
|
|
215
|
+
result = spawnSync("mem", ["epoch"], {
|
|
216
|
+
encoding: "utf-8",
|
|
217
|
+
timeout: MEM_EPOCH_TIMEOUT_MS,
|
|
218
|
+
windowsHide: true
|
|
219
|
+
});
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
if (result.error !== void 0 || result.status !== 0) return [];
|
|
224
|
+
const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
225
|
+
if (!/^\d+$/.test(stdout)) return [];
|
|
226
|
+
return [
|
|
227
|
+
"",
|
|
228
|
+
"### mem epoch",
|
|
229
|
+
`mem epoch: ${stdout} (no live TGMEM block is tracked in this session -- only the epoch counter is folded in; see buildMemEpochSection's doc comment)`
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
var MANIFEST_PREAMBLE = "When summarizing this session, keep the file paths and symbol names below exactly as written -- they are the handles the next turn needs to resume work. Do not paraphrase them into prose.";
|
|
233
|
+
var MANIFEST_RECOVERY_PREAMBLE = "The conversation was just compacted. Below is what this session touched, recovered from token-goat's own ledger rather than from the summary -- use it to re-establish paths and symbol names the summary may have dropped.";
|
|
234
|
+
var BUDGET_ESCALATION_MARKER = "TG-BUDGET-ESCALATION:";
|
|
235
|
+
function summaryBudgetDirective(budgetChars) {
|
|
236
|
+
if (budgetChars <= 0) return "";
|
|
237
|
+
return ` Aim for at most ${budgetChars} characters. Prefer a shorter summary that keeps every path, symbol, command and decision over a longer one that reproduces tool output verbatim: cite the recall id (\`token-goat bash-output <id> --full\`) rather than quoting the output again. If this session genuinely cannot be summarized within that target without losing state the next turn needs, exceed it and open the summary with a single line reading \`${BUDGET_ESCALATION_MARKER} <one-sentence reason>\`.`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
renderWebFetchRow,
|
|
242
|
+
manifestPrintedPaths,
|
|
243
|
+
buildManifest,
|
|
244
|
+
MANIFEST_PREAMBLE,
|
|
245
|
+
MANIFEST_RECOVERY_PREAMBLE,
|
|
246
|
+
BUDGET_ESCALATION_MARKER,
|
|
247
|
+
summaryBudgetDirective
|
|
248
|
+
};
|
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
docxTables,
|
|
6
6
|
docxText,
|
|
7
7
|
formatDocxTables
|
|
8
|
-
} from "./token-goat-chunk-
|
|
9
|
-
import "./token-goat-chunk-
|
|
8
|
+
} from "./token-goat-chunk-PTW22MRC.mjs";
|
|
9
|
+
import "./token-goat-chunk-T46UKRFM.mjs";
|
|
10
10
|
import "./token-goat-chunk-XTQAOTSO.mjs";
|
|
11
11
|
import "./token-goat-chunk-AH6QILZM.mjs";
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import "./token-goat-chunk-
|
|
12
|
+
import "./token-goat-chunk-6LJNM4ZZ.mjs";
|
|
13
|
+
import "./token-goat-chunk-NMTKNYGF.mjs";
|
|
14
14
|
import "./token-goat-chunk-Y4AFKTHK.mjs";
|
|
15
15
|
import "./token-goat-chunk-GMOUBOX4.mjs";
|
|
16
16
|
import "./token-goat-chunk-A37V4PBF.mjs";
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
renderWebFetchRow
|
|
5
|
+
} from "./token-goat-chunk-VSJGPEBB.mjs";
|
|
6
|
+
import {
|
|
7
|
+
SESSIONS_SUBDIR,
|
|
8
|
+
extractChecklistSection,
|
|
9
|
+
getSkillFilePath,
|
|
10
|
+
listSkills
|
|
11
|
+
} from "./token-goat-chunk-KT75DZK4.mjs";
|
|
12
|
+
import {
|
|
13
|
+
getBashOutput
|
|
14
|
+
} from "./token-goat-chunk-SL454EN5.mjs";
|
|
15
|
+
import {
|
|
16
|
+
loadBlob
|
|
17
|
+
} from "./token-goat-chunk-K5XPIUVB.mjs";
|
|
18
|
+
import {
|
|
19
|
+
resolveProjectRoot
|
|
20
|
+
} from "./token-goat-chunk-74ADIZZX.mjs";
|
|
21
|
+
import {
|
|
22
|
+
runGit,
|
|
23
|
+
safeSlice
|
|
24
|
+
} from "./token-goat-chunk-6LJNM4ZZ.mjs";
|
|
25
|
+
import {
|
|
26
|
+
displaySafePath,
|
|
27
|
+
displaySafeText,
|
|
28
|
+
neutralizeSpokenMarkers
|
|
29
|
+
} from "./token-goat-chunk-NMTKNYGF.mjs";
|
|
30
|
+
import {
|
|
31
|
+
init_define_import_meta_env
|
|
32
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
33
|
+
|
|
34
|
+
// src/resume.ts
|
|
35
|
+
init_define_import_meta_env();
|
|
36
|
+
import { readFile } from "node:fs/promises";
|
|
37
|
+
var MAX_RESUME_TOKENS = 2e3;
|
|
38
|
+
var MAX_RESUME_CHARS = MAX_RESUME_TOKENS * 4;
|
|
39
|
+
var MAX_RESUME_WEB_FETCHES = 8;
|
|
40
|
+
var SKILL_MAX_COUNT = 3;
|
|
41
|
+
var SKILL_MAX_CHARS_EACH = 400;
|
|
42
|
+
async function buildSkillsSection(sessionId) {
|
|
43
|
+
const skills = await listSkills(sessionId);
|
|
44
|
+
if (skills.length === 0) return [];
|
|
45
|
+
const skillLines = ["## Skills"];
|
|
46
|
+
for (const skill of skills.slice(0, SKILL_MAX_COUNT)) {
|
|
47
|
+
let checklist = null;
|
|
48
|
+
try {
|
|
49
|
+
const filePath = await getSkillFilePath(skill.name);
|
|
50
|
+
if (filePath !== null) {
|
|
51
|
+
const body = await readFile(filePath, "utf-8");
|
|
52
|
+
checklist = extractChecklistSection(body);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
checklist = null;
|
|
56
|
+
}
|
|
57
|
+
if (checklist !== null) {
|
|
58
|
+
const trimmed = checklist.length > SKILL_MAX_CHARS_EACH ? checklist.slice(0, SKILL_MAX_CHARS_EACH).trimEnd() + "\u2026" : checklist;
|
|
59
|
+
skillLines.push(`**${displaySafeText(skill.name)}**:`);
|
|
60
|
+
skillLines.push(neutralizeSpokenMarkers(trimmed));
|
|
61
|
+
} else {
|
|
62
|
+
skillLines.push(`**${displaySafeText(skill.name)}** \u2014 \`token-goat skill-body ${displaySafeText(skill.name)} --section DoD\``);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
skillLines.push("");
|
|
66
|
+
return skillLines;
|
|
67
|
+
}
|
|
68
|
+
async function buildResumePacket(sessionId) {
|
|
69
|
+
const blob = loadBlob(SESSIONS_SUBDIR, sessionId);
|
|
70
|
+
if (blob === null || typeof blob !== "object") return null;
|
|
71
|
+
const raw = blob;
|
|
72
|
+
const filesArr = Array.isArray(raw["files"]) ? raw["files"] : [];
|
|
73
|
+
const lines = [`# Resume packet \u2014 session ${displaySafeText(sessionId)}`, ""];
|
|
74
|
+
try {
|
|
75
|
+
lines.push(...await buildSkillsSection(sessionId));
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
const editedPaths = filesArr.filter((f) => f["wasEdited"] === true).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean).slice(0, 10);
|
|
79
|
+
if (editedPaths.length > 0) {
|
|
80
|
+
lines.push("## Edited files");
|
|
81
|
+
for (const p of editedPaths) lines.push(`- ${displaySafePath(p)}`);
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
const topRead = [...filesArr].filter((f) => f["wasEdited"] !== true).sort((a, b) => (typeof b["readCount"] === "number" ? b["readCount"] : 0) - (typeof a["readCount"] === "number" ? a["readCount"] : 0)).slice(0, 8).map((f) => typeof f["path"] === "string" ? f["path"] : "").filter(Boolean);
|
|
85
|
+
if (topRead.length > 0) {
|
|
86
|
+
lines.push("## Top files read");
|
|
87
|
+
for (const p of topRead) lines.push(`- ${displaySafePath(p)}`);
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
const bashOutputs = Array.isArray(raw["bashOutputs"]) ? raw["bashOutputs"] : [];
|
|
91
|
+
const recentBash = bashOutputs.slice(-2);
|
|
92
|
+
if (recentBash.length > 0) {
|
|
93
|
+
lines.push("## Recent bash commands");
|
|
94
|
+
for (const entry of recentBash) {
|
|
95
|
+
if (Array.isArray(entry) && typeof entry[1] === "string") {
|
|
96
|
+
const bashEntry = getBashOutput(entry[1]);
|
|
97
|
+
if (bashEntry !== null) lines.push(`- ${displaySafeText(bashEntry.command)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
const webFetches = Array.isArray(raw["webFetches"]) ? raw["webFetches"] : [];
|
|
103
|
+
const webRows = webFetches.slice(-MAX_RESUME_WEB_FETCHES).filter((e) => Array.isArray(e) && typeof e[0] === "string" && typeof e[1] === "string").map(([key, cacheId]) => renderWebFetchRow(key, cacheId));
|
|
104
|
+
if (webRows.length > 0) {
|
|
105
|
+
lines.push("## Web pages fetched");
|
|
106
|
+
lines.push(...webRows);
|
|
107
|
+
lines.push("");
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const projectRoot = resolveProjectRoot();
|
|
111
|
+
const result = runGit(["diff", "--stat"], { cwd: projectRoot });
|
|
112
|
+
if (result.exitCode === 0 && result.stdout.trim().length > 0) {
|
|
113
|
+
lines.push("## Uncommitted changes (git diff --stat)");
|
|
114
|
+
lines.push(result.stdout.trim().split("\n").map(displaySafeText).join("\n"));
|
|
115
|
+
lines.push("");
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
const text = lines.join("\n");
|
|
120
|
+
if (text.length <= MAX_RESUME_CHARS) return text;
|
|
121
|
+
return safeSlice(text, MAX_RESUME_CHARS) + "\n... (truncated to cap)\n";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export {
|
|
125
|
+
MAX_RESUME_TOKENS,
|
|
126
|
+
MAX_RESUME_CHARS,
|
|
127
|
+
buildResumePacket
|
|
128
|
+
};
|