@wrongstack/core 0.303.0 → 0.305.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chronicle/project-server.js +18 -48
- package/dist/coordination/agents/index.js +530 -130
- package/dist/coordination/agents/project-agent-consolidation.d.ts +5 -0
- package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +26 -12
- package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +46 -1
- package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +55 -10
- package/dist/coordination/agents/types.d.ts +10 -2
- package/dist/coordination/director-prompts.d.ts +19 -6
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/fleet.d.ts +0 -6
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.js +1581 -955
- package/dist/coordination/mailbox-project-server.js +28 -57
- package/dist/core/agent-types.d.ts +4 -2
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/context.d.ts +15 -0
- package/dist/core/conversation-state.d.ts +14 -0
- package/dist/core/fallback-profile-manager.d.ts +70 -2
- package/dist/core/index.js +308 -108
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +13 -1
- package/dist/core/system-prompt-glossary.d.ts +73 -0
- package/dist/core/system-prompt-memory-skills.d.ts +2 -2
- package/dist/defaults/index.js +910 -693
- package/dist/execution/council-orchestrator.d.ts +3 -13
- package/dist/execution/index.js +211 -75
- package/dist/execution/one-shot-llm.d.ts +5 -0
- package/dist/hq/index.js +17 -7
- package/dist/hq/protocol/kanban.d.ts +21 -0
- package/dist/hq/protocol.js +5 -1
- package/dist/hq/redaction.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3505 -2539
- package/dist/infrastructure/index.js +247 -122
- package/dist/plugin/index.js +101 -3
- package/dist/registry/index.js +11 -0
- package/dist/registry/tool-registry.d.ts +8 -0
- package/dist/replay/hash.d.ts +9 -0
- package/dist/replay/index.js +14 -4
- package/dist/replay/replay-provider-runner.d.ts +31 -1
- package/dist/security/index.js +25 -20
- package/dist/security/secret-vault.d.ts +2 -0
- package/dist/session-catalog/index.js +62 -8
- package/dist/session-catalog/project-server.js +109 -78
- package/dist/session-catalog/protocol.d.ts +11 -4
- package/dist/session-catalog/store.d.ts +2 -2
- package/dist/storage/index.js +224 -67
- package/dist/storage/memory-consolidator.d.ts +4 -2
- package/dist/storage/session-resume-validation.d.ts +24 -0
- package/dist/storage/session-store/directory-scan.d.ts +5 -1
- package/dist/storage/session-store/fork-session.d.ts +13 -1
- package/dist/storage/session-store/load-cache.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +5 -0
- package/dist/storage/session-store.d.ts +18 -0
- package/dist/tools/index.js +174 -74
- package/dist/types/config/mcp-features.d.ts +31 -1
- package/dist/types/config/root.d.ts +12 -0
- package/dist/types/config/tools.d.ts +22 -0
- package/dist/types/config/ui.d.ts +7 -4
- package/dist/types/default-config.d.ts +1 -0
- package/dist/types/index.js +24 -1
- package/dist/types/session.d.ts +9 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +214 -76
- package/dist/utils/project-state-guard.d.ts +21 -0
- package/dist/utils/session-scoped-path.d.ts +17 -0
- package/dist/utils/todos-format.d.ts +20 -0
- package/instructions/leader-after-task.md +3 -4
- package/instructions/system-lite.md +10 -13
- package/instructions/system-pro.md +18 -25
- package/instructions/system.md +18 -23
- package/package.json +3 -3
- package/skills/wrongstack-kanban/SKILL.md +95 -124
package/dist/storage/index.js
CHANGED
|
@@ -2152,15 +2152,51 @@ var atomicReplaceWithWriter = primitives.atomicReplaceWithWriter;
|
|
|
2152
2152
|
var ensureDir = primitives.ensureDir;
|
|
2153
2153
|
var withFileLock = primitives.withFileLock;
|
|
2154
2154
|
|
|
2155
|
+
// src/utils/session-scoped-path.ts
|
|
2156
|
+
import * as path6 from "node:path";
|
|
2157
|
+
function sessionScopedPath(dir, sessionId, suffix) {
|
|
2158
|
+
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2159
|
+
throw invalid(sessionId);
|
|
2160
|
+
}
|
|
2161
|
+
const resolved = path6.resolve(dir, `${sessionId}${suffix}`);
|
|
2162
|
+
const rel = path6.relative(path6.resolve(dir), resolved);
|
|
2163
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
2164
|
+
throw invalid(sessionId);
|
|
2165
|
+
}
|
|
2166
|
+
return resolved;
|
|
2167
|
+
}
|
|
2168
|
+
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
2169
|
+
".replay.jsonl",
|
|
2170
|
+
".audit.jsonl",
|
|
2171
|
+
".annotations.jsonl"
|
|
2172
|
+
];
|
|
2173
|
+
var RESERVED_SESSION_JSONL_NAMES = /* @__PURE__ */ new Set([
|
|
2174
|
+
"_index.jsonl",
|
|
2175
|
+
"_mailbox.jsonl"
|
|
2176
|
+
]);
|
|
2177
|
+
function isSessionTranscriptFileName(name) {
|
|
2178
|
+
if (!name.endsWith(".jsonl")) return false;
|
|
2179
|
+
if (RESERVED_SESSION_JSONL_NAMES.has(name)) return false;
|
|
2180
|
+
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
2181
|
+
}
|
|
2182
|
+
function invalid(sessionId) {
|
|
2183
|
+
return new FsError({
|
|
2184
|
+
message: `Invalid sessionId: ${sessionId}`,
|
|
2185
|
+
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
2186
|
+
path: sessionId,
|
|
2187
|
+
context: { reason: "path_traversal" }
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2155
2191
|
// src/session-registry.ts
|
|
2156
2192
|
import * as fs6 from "node:fs/promises";
|
|
2157
|
-
import * as
|
|
2193
|
+
import * as path8 from "node:path";
|
|
2158
2194
|
|
|
2159
2195
|
// src/session-registry-atomic-file.ts
|
|
2160
2196
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2161
2197
|
import * as fs5 from "node:fs/promises";
|
|
2162
2198
|
import { hostname } from "node:os";
|
|
2163
|
-
import * as
|
|
2199
|
+
import * as path7 from "node:path";
|
|
2164
2200
|
var STALE_LOCK_MS = 1e4;
|
|
2165
2201
|
var SAME_HOST_STALE_MS = 2 * STALE_LOCK_MS;
|
|
2166
2202
|
var STALE_TMP_MS = 6e4;
|
|
@@ -2240,9 +2276,9 @@ async function breakLockAtomically(lockPath) {
|
|
|
2240
2276
|
return true;
|
|
2241
2277
|
}
|
|
2242
2278
|
async function writeAtomicFile(filePath, registry) {
|
|
2243
|
-
const tmp =
|
|
2244
|
-
|
|
2245
|
-
`.${
|
|
2279
|
+
const tmp = path7.join(
|
|
2280
|
+
path7.dirname(filePath),
|
|
2281
|
+
`.${path7.basename(filePath)}.${randomUUID2().slice(0, 8)}.tmp`
|
|
2246
2282
|
);
|
|
2247
2283
|
let tmpPersisted = false;
|
|
2248
2284
|
try {
|
|
@@ -2283,21 +2319,21 @@ async function writeAtomicFile(filePath, registry) {
|
|
|
2283
2319
|
}
|
|
2284
2320
|
async function pruneStaleTempFiles(filePath) {
|
|
2285
2321
|
try {
|
|
2286
|
-
const dir =
|
|
2287
|
-
const base =
|
|
2322
|
+
const dir = path7.dirname(filePath);
|
|
2323
|
+
const base = path7.basename(filePath);
|
|
2288
2324
|
const now = Date.now();
|
|
2289
2325
|
const stale = [];
|
|
2290
2326
|
for (const name of await fs5.readdir(dir)) {
|
|
2291
2327
|
const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
|
|
2292
2328
|
if (!isTemp) continue;
|
|
2293
|
-
const stat15 = await fs5.stat(
|
|
2329
|
+
const stat15 = await fs5.stat(path7.join(dir, name)).catch(() => null);
|
|
2294
2330
|
if (!stat15) continue;
|
|
2295
2331
|
if (now - stat15.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat15.mtimeMs });
|
|
2296
2332
|
}
|
|
2297
2333
|
stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2298
2334
|
await Promise.all(
|
|
2299
2335
|
stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
|
|
2300
|
-
await fs5.unlink(
|
|
2336
|
+
await fs5.unlink(path7.join(dir, name)).catch(() => void 0);
|
|
2301
2337
|
})
|
|
2302
2338
|
);
|
|
2303
2339
|
} catch {
|
|
@@ -2361,7 +2397,7 @@ var SessionRegistry = class {
|
|
|
2361
2397
|
lastEntry = null;
|
|
2362
2398
|
ownershipLockWaitMs;
|
|
2363
2399
|
constructor(globalRoot, options = {}) {
|
|
2364
|
-
this.filePath =
|
|
2400
|
+
this.filePath = path8.join(globalRoot, REGISTRY_FILE);
|
|
2365
2401
|
this.ownershipLockWaitMs = Math.max(0, options.ownershipLockWaitMs ?? OWNERSHIP_LOCK_WAIT_MS);
|
|
2366
2402
|
}
|
|
2367
2403
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
@@ -2671,7 +2707,7 @@ var SessionRegistry = class {
|
|
|
2671
2707
|
const deadline = Date.now() + waitBudgetMs;
|
|
2672
2708
|
let attempt = 0;
|
|
2673
2709
|
try {
|
|
2674
|
-
await fs6.mkdir(
|
|
2710
|
+
await fs6.mkdir(path8.dirname(this.filePath), { recursive: true });
|
|
2675
2711
|
await this.maybePruneStaleTempFiles();
|
|
2676
2712
|
} catch (err) {
|
|
2677
2713
|
if (required) {
|
|
@@ -2815,28 +2851,6 @@ function expectDefined(value, label) {
|
|
|
2815
2851
|
return value;
|
|
2816
2852
|
}
|
|
2817
2853
|
|
|
2818
|
-
// src/utils/session-scoped-path.ts
|
|
2819
|
-
import * as path8 from "node:path";
|
|
2820
|
-
function sessionScopedPath(dir, sessionId, suffix) {
|
|
2821
|
-
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
2822
|
-
throw invalid(sessionId);
|
|
2823
|
-
}
|
|
2824
|
-
const resolved = path8.resolve(dir, `${sessionId}${suffix}`);
|
|
2825
|
-
const rel = path8.relative(path8.resolve(dir), resolved);
|
|
2826
|
-
if (rel.startsWith("..") || path8.isAbsolute(rel)) {
|
|
2827
|
-
throw invalid(sessionId);
|
|
2828
|
-
}
|
|
2829
|
-
return resolved;
|
|
2830
|
-
}
|
|
2831
|
-
function invalid(sessionId) {
|
|
2832
|
-
return new FsError({
|
|
2833
|
-
message: `Invalid sessionId: ${sessionId}`,
|
|
2834
|
-
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
2835
|
-
path: sessionId,
|
|
2836
|
-
context: { reason: "path_traversal" }
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
|
-
|
|
2840
2854
|
// src/storage/annotations-store.ts
|
|
2841
2855
|
var FILE_VERSION = 1;
|
|
2842
2856
|
var MAX_TEXT_LENGTH = 2e3;
|
|
@@ -4628,6 +4642,9 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
4628
4642
|
disabledTools: Object.freeze([]),
|
|
4629
4643
|
autoExtendLimit: true,
|
|
4630
4644
|
restrictToProjectRoot: true,
|
|
4645
|
+
// Off by default: the board is a record of the work, not a permit for it.
|
|
4646
|
+
// See ToolsConfig.kanbanGovernance for what turning it on costs and gates.
|
|
4647
|
+
kanbanGovernance: false,
|
|
4631
4648
|
loopDetection: Object.freeze({
|
|
4632
4649
|
mode: "steer-then-cut",
|
|
4633
4650
|
steerThreshold: 3,
|
|
@@ -4685,6 +4702,7 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
|
|
|
4685
4702
|
disabledTools: DEFAULT_TOOLS_CONFIG.disabledTools,
|
|
4686
4703
|
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
4687
4704
|
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot,
|
|
4705
|
+
kanbanGovernance: DEFAULT_TOOLS_CONFIG.kanbanGovernance,
|
|
4688
4706
|
loopDetection: DEFAULT_TOOLS_CONFIG.loopDetection
|
|
4689
4707
|
},
|
|
4690
4708
|
log: { level: "info" },
|
|
@@ -5109,6 +5127,14 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
5109
5127
|
{
|
|
5110
5128
|
path: "tools.restrictToProjectRoot",
|
|
5111
5129
|
reason: "The other half of the filesystem confinement switch."
|
|
5130
|
+
},
|
|
5131
|
+
{
|
|
5132
|
+
// Denied for the direction that loosens: the flag defaults to false, so a
|
|
5133
|
+
// repo can only ever use it to turn OFF a gate the user deliberately
|
|
5134
|
+
// enabled. Same class as `tools.restrictToProjectRoot` — a control the
|
|
5135
|
+
// operator owns, not the checked-out repository.
|
|
5136
|
+
path: "tools.kanbanGovernance",
|
|
5137
|
+
reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
|
|
5112
5138
|
}
|
|
5113
5139
|
];
|
|
5114
5140
|
function deleteNestedPath(target, path34) {
|
|
@@ -7682,10 +7708,38 @@ var SessionMemoryConsolidator = class {
|
|
|
7682
7708
|
process.stderr.write(`[memory] Session consolidation: ${added} added
|
|
7683
7709
|
`);
|
|
7684
7710
|
}
|
|
7711
|
+
if (this.Sage?.rememberSage) {
|
|
7712
|
+
try {
|
|
7713
|
+
const digestBody = buildSessionDigestText(_finalText, _iterations, added);
|
|
7714
|
+
if (digestBody) {
|
|
7715
|
+
const expires = new Date(Date.now() + 14 * 24 * 60 * 6e4).toISOString();
|
|
7716
|
+
await this.Sage.rememberSage({
|
|
7717
|
+
text: digestBody,
|
|
7718
|
+
scope: "session",
|
|
7719
|
+
kind: "session_digest",
|
|
7720
|
+
importance: 0.4,
|
|
7721
|
+
confidence: 0.65,
|
|
7722
|
+
persistence: "short_lived",
|
|
7723
|
+
tags: ["session_digest", "auto"],
|
|
7724
|
+
anchors: [],
|
|
7725
|
+
sources: [{ type: "session", sessionId: _sessionId }],
|
|
7726
|
+
ownerSessionId: _sessionId,
|
|
7727
|
+
expiresAt: expires
|
|
7728
|
+
});
|
|
7729
|
+
}
|
|
7730
|
+
} catch {
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7685
7733
|
} catch {
|
|
7686
7734
|
}
|
|
7687
7735
|
};
|
|
7688
7736
|
};
|
|
7737
|
+
function buildSessionDigestText(finalText, iterations, factsAdded) {
|
|
7738
|
+
const cleaned = finalText.replace(/\s+/g, " ").trim();
|
|
7739
|
+
if (cleaned.length < 40) return void 0;
|
|
7740
|
+
const summary2 = cleaned.slice(0, 400);
|
|
7741
|
+
return `Session digest (${iterations} iter${factsAdded > 0 ? `, ${factsAdded} facts added` : ""}): ` + summary2 + (cleaned.length > 400 ? "\u2026" : "");
|
|
7742
|
+
}
|
|
7689
7743
|
|
|
7690
7744
|
// src/storage/memory-graph-backend.ts
|
|
7691
7745
|
import * as fs14 from "node:fs/promises";
|
|
@@ -9277,11 +9331,15 @@ function sortKeys(value) {
|
|
|
9277
9331
|
}
|
|
9278
9332
|
return value;
|
|
9279
9333
|
}
|
|
9334
|
+
function semanticMessage(message) {
|
|
9335
|
+
const { ts: _ts, _estTokens: _estimate, origin: _origin, ...semantic } = message;
|
|
9336
|
+
return semantic;
|
|
9337
|
+
}
|
|
9280
9338
|
function hashRequest(request) {
|
|
9281
9339
|
const payload = {
|
|
9282
9340
|
model: request.model,
|
|
9283
9341
|
system: request.system,
|
|
9284
|
-
messages: request.messages,
|
|
9342
|
+
messages: request.messages.map(semanticMessage),
|
|
9285
9343
|
tools: request.tools,
|
|
9286
9344
|
maxTokens: request.maxTokens,
|
|
9287
9345
|
temperature: request.temperature,
|
|
@@ -11030,9 +11088,6 @@ import * as path25 from "node:path";
|
|
|
11030
11088
|
function shouldSkipSessionDirectoryEntry(name) {
|
|
11031
11089
|
return name.startsWith(".") && name !== ".wrongstack" || name === "shared" || name === "subagents" || name === "attachments";
|
|
11032
11090
|
}
|
|
11033
|
-
function isSessionJsonlFileName(name) {
|
|
11034
|
-
return name.endsWith(".jsonl") && name !== "_index.jsonl";
|
|
11035
|
-
}
|
|
11036
11091
|
|
|
11037
11092
|
// src/storage/session-store/directory-session-files.ts
|
|
11038
11093
|
function sessionIdForFile(prefix, name) {
|
|
@@ -11055,7 +11110,7 @@ async function collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
|
11055
11110
|
if (shouldSkipSessionDirectoryEntry(entry.name)) continue;
|
|
11056
11111
|
if (entry.isDirectory()) {
|
|
11057
11112
|
dirEntries.push(entry);
|
|
11058
|
-
} else if (entry.isFile() &&
|
|
11113
|
+
} else if (entry.isFile() && isSessionTranscriptFileName(entry.name)) {
|
|
11059
11114
|
files.push({ id: sessionIdForFile(prefix, entry.name), filePath: path25.join(dir, entry.name) });
|
|
11060
11115
|
}
|
|
11061
11116
|
}
|
|
@@ -11079,7 +11134,7 @@ async function collectSessionIds(dir, prefix = "", depth = 0) {
|
|
|
11079
11134
|
if (shouldSkipSessionDirectoryEntry(entry.name)) continue;
|
|
11080
11135
|
if (entry.isDirectory()) {
|
|
11081
11136
|
dirEntries.push(entry);
|
|
11082
|
-
} else if (entry.isFile() &&
|
|
11137
|
+
} else if (entry.isFile() && isSessionTranscriptFileName(entry.name)) {
|
|
11083
11138
|
fileIds.push(sessionIdForFile(prefix, entry.name));
|
|
11084
11139
|
}
|
|
11085
11140
|
}
|
|
@@ -11243,11 +11298,8 @@ var SessionRecovery = class _SessionRecovery {
|
|
|
11243
11298
|
}
|
|
11244
11299
|
continue;
|
|
11245
11300
|
}
|
|
11246
|
-
if (!entry.isFile() || !entry.name
|
|
11247
|
-
if (entry.name === "_index.jsonl" || entry.name === "_mailbox.jsonl") continue;
|
|
11301
|
+
if (!entry.isFile() || !isSessionTranscriptFileName(entry.name)) continue;
|
|
11248
11302
|
const base = entry.name.slice(0, -".jsonl".length);
|
|
11249
|
-
if (base.includes(".replay") || base.includes(".annotations") || base.includes(".audit"))
|
|
11250
|
-
continue;
|
|
11251
11303
|
const sessionId = prefix ? `${prefix}/${base}` : base;
|
|
11252
11304
|
const stale = await this.detectStaleExact(sessionId);
|
|
11253
11305
|
if (stale) out.push(stale);
|
|
@@ -12605,6 +12657,15 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
12605
12657
|
staleFiles: results.filter((entry) => entry !== null)
|
|
12606
12658
|
};
|
|
12607
12659
|
}
|
|
12660
|
+
var RESUME_NOTICE_HEADERS = [
|
|
12661
|
+
"[SESSION RESUME FILE VALIDATION]",
|
|
12662
|
+
"[SESSION RESUME INTERRUPTED WORK]"
|
|
12663
|
+
];
|
|
12664
|
+
function isResumeNoticeMessage(message) {
|
|
12665
|
+
if (message.role !== "system" || typeof message.content !== "string") return false;
|
|
12666
|
+
return RESUME_NOTICE_HEADERS.some((header) => message.content === header || message.content.startsWith(`${header}
|
|
12667
|
+
`));
|
|
12668
|
+
}
|
|
12608
12669
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
12609
12670
|
if (validation.staleFiles.length === 0) return null;
|
|
12610
12671
|
const root = path28.resolve(projectRoot);
|
|
@@ -12801,13 +12862,13 @@ function inheritsIntoFork(event) {
|
|
|
12801
12862
|
|
|
12802
12863
|
// src/storage/session-store/fork-session.ts
|
|
12803
12864
|
async function forkSession(host, id, opts = {}) {
|
|
12804
|
-
const
|
|
12805
|
-
let boundary =
|
|
12865
|
+
const parentEvents = await host.readRawEvents(id);
|
|
12866
|
+
let boundary = parentEvents.length - 1;
|
|
12806
12867
|
let targetCheckpoint;
|
|
12807
12868
|
if (opts.checkpointPromptIndex !== void 0) {
|
|
12808
12869
|
boundary = -1;
|
|
12809
|
-
for (let i = 0; i <
|
|
12810
|
-
const event =
|
|
12870
|
+
for (let i = 0; i < parentEvents.length; i++) {
|
|
12871
|
+
const event = parentEvents[i];
|
|
12811
12872
|
if (event?.type === "checkpoint" && event.promptIndex === opts.checkpointPromptIndex) {
|
|
12812
12873
|
boundary = i;
|
|
12813
12874
|
targetCheckpoint = event;
|
|
@@ -12817,15 +12878,18 @@ async function forkSession(host, id, opts = {}) {
|
|
|
12817
12878
|
throw new Error(`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`);
|
|
12818
12879
|
}
|
|
12819
12880
|
}
|
|
12820
|
-
const parentPrefix =
|
|
12881
|
+
const parentPrefix = parentEvents.slice(0, boundary + 1);
|
|
12821
12882
|
const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
|
|
12822
12883
|
const checkpointHash = createHash9("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
|
|
12823
12884
|
const inherited = parentPrefix.filter(inheritsIntoFork);
|
|
12885
|
+
const start = parentEvents.find(
|
|
12886
|
+
(event) => event.type === "session_start"
|
|
12887
|
+
);
|
|
12824
12888
|
const writer = await host.create({
|
|
12825
12889
|
id: "",
|
|
12826
|
-
title:
|
|
12827
|
-
model:
|
|
12828
|
-
provider:
|
|
12890
|
+
title: "",
|
|
12891
|
+
model: start?.model,
|
|
12892
|
+
provider: start?.provider
|
|
12829
12893
|
});
|
|
12830
12894
|
try {
|
|
12831
12895
|
await writer.append({
|
|
@@ -12931,6 +12995,17 @@ var SessionLoadCache = class {
|
|
|
12931
12995
|
this.entries.clear();
|
|
12932
12996
|
this.bytes = 0;
|
|
12933
12997
|
}
|
|
12998
|
+
/**
|
|
12999
|
+
* A hit hands back fresh `messages` / `events` arrays over the cached
|
|
13000
|
+
* contents.
|
|
13001
|
+
*
|
|
13002
|
+
* The entry outlives every caller, and callers treat what they get as their
|
|
13003
|
+
* own: `resume()` passes `messages` straight into a live conversation, and
|
|
13004
|
+
* anything walking `events` may splice it. Returning the cached arrays
|
|
13005
|
+
* themselves let one caller's edit rewrite what the next one loads. The
|
|
13006
|
+
* elements are still shared — copying them would defeat the cache — so
|
|
13007
|
+
* entries remain read-only *contents* behind private containers.
|
|
13008
|
+
*/
|
|
12934
13009
|
getFresh(id, stat15, full) {
|
|
12935
13010
|
const cached = this.entries.get(id);
|
|
12936
13011
|
if (!cached || cached.mtimeMs !== stat15.mtimeMs || cached.size !== stat15.size) {
|
|
@@ -12938,8 +13013,11 @@ var SessionLoadCache = class {
|
|
|
12938
13013
|
}
|
|
12939
13014
|
this.entries.delete(id);
|
|
12940
13015
|
this.entries.set(id, cached);
|
|
12941
|
-
|
|
12942
|
-
|
|
13016
|
+
return {
|
|
13017
|
+
...cached.data,
|
|
13018
|
+
messages: full ? [...cached.data.messages] : [],
|
|
13019
|
+
events: [...cached.data.events]
|
|
13020
|
+
};
|
|
12943
13021
|
}
|
|
12944
13022
|
set(id, stat15, data) {
|
|
12945
13023
|
this.delete(id);
|
|
@@ -13128,6 +13206,9 @@ function stripSnapshotPayload(event) {
|
|
|
13128
13206
|
event.messagesOmitted = event.messages.length;
|
|
13129
13207
|
event.messages = [];
|
|
13130
13208
|
}
|
|
13209
|
+
function isStrippedSnapshot(event) {
|
|
13210
|
+
return event.messages.length === 0 && typeof event.messagesOmitted === "number" && event.messagesOmitted > 0;
|
|
13211
|
+
}
|
|
13131
13212
|
function isSessionEventLike(value) {
|
|
13132
13213
|
return value !== null && typeof value === "object" && typeof value.type === "string" && typeof value.ts === "string";
|
|
13133
13214
|
}
|
|
@@ -13157,7 +13238,12 @@ function replaySessionEvent(params) {
|
|
|
13157
13238
|
emitDamaged(params, `Ignored malformed message_updated event at index ${ev.index}`);
|
|
13158
13239
|
}
|
|
13159
13240
|
} else if (ev.type === "messages_replaced" && ev.version === 1) {
|
|
13160
|
-
if (
|
|
13241
|
+
if (isStrippedSnapshot(ev)) {
|
|
13242
|
+
emitDamaged(
|
|
13243
|
+
params,
|
|
13244
|
+
`Ignored messages_replaced event whose payload was stripped before persistence (${String(ev.messagesOmitted)} messages)`
|
|
13245
|
+
);
|
|
13246
|
+
} else if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
13161
13247
|
exactJournalActive = true;
|
|
13162
13248
|
} else {
|
|
13163
13249
|
emitDamaged(params, "Ignored malformed messages_replaced event");
|
|
@@ -13173,9 +13259,16 @@ function replaySessionEvent(params) {
|
|
|
13173
13259
|
emitDamaged(params, `Ignored malformed messages_dropped event (count ${String(ev.count)})`);
|
|
13174
13260
|
}
|
|
13175
13261
|
} else if (ev.type === "context_snapshot") {
|
|
13176
|
-
if (
|
|
13262
|
+
if (isStrippedSnapshot(ev)) {
|
|
13263
|
+
emitDamaged(
|
|
13264
|
+
params,
|
|
13265
|
+
`Ignored context_snapshot event whose payload was stripped before persistence (${String(ev.messagesOmitted)} messages)`
|
|
13266
|
+
);
|
|
13267
|
+
} else if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
13177
13268
|
emitDamaged(params, "Ignored malformed context_snapshot event");
|
|
13178
13269
|
}
|
|
13270
|
+
} else if (ev.type === "messages_replaced" || ev.type === "message_appended" || ev.type === "message_updated" || ev.type === "messages_dropped") {
|
|
13271
|
+
emitDamaged(params, `Ignored ${ev.type} event with unsupported version`);
|
|
13179
13272
|
} else if (!exactJournalActive && ev.type === "user_input") {
|
|
13180
13273
|
openToolUses.clear();
|
|
13181
13274
|
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
@@ -13228,7 +13321,7 @@ function emitDamaged(params, detail) {
|
|
|
13228
13321
|
import * as fsp15 from "node:fs/promises";
|
|
13229
13322
|
import * as path31 from "node:path";
|
|
13230
13323
|
function isPrunableSessionJsonl(name) {
|
|
13231
|
-
return
|
|
13324
|
+
return isSessionTranscriptFileName(name);
|
|
13232
13325
|
}
|
|
13233
13326
|
async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
13234
13327
|
const cutoff = Date.now() - maxAgeDays * 864e5;
|
|
@@ -13751,6 +13844,21 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13751
13844
|
async fork(id, opts = {}) {
|
|
13752
13845
|
return forkSession(this, id, opts);
|
|
13753
13846
|
}
|
|
13847
|
+
/**
|
|
13848
|
+
* Implements {@link SessionForkHost.readRawEvents} — the parent stream a fork
|
|
13849
|
+
* inherits, unmodified.
|
|
13850
|
+
*
|
|
13851
|
+
* Deliberately NOT `load()`: that loader empties superseded snapshot payloads
|
|
13852
|
+
* in place and front-drops events past its retention budget, both of which
|
|
13853
|
+
* are correct for reconstructing a conversation and wrong for copying a
|
|
13854
|
+
* journal prefix into a child. Streaming with an accept-everything predicate
|
|
13855
|
+
* keeps the scrubbing contract (`searchEvents` scrubs each line the same way
|
|
13856
|
+
* `load()` does) without either transformation.
|
|
13857
|
+
*/
|
|
13858
|
+
async readRawEvents(id) {
|
|
13859
|
+
const hits = await this.searchEvents(id, () => true);
|
|
13860
|
+
return hits.map((hit) => hit.event);
|
|
13861
|
+
}
|
|
13754
13862
|
/**
|
|
13755
13863
|
* Capture the deterministic post-tool workspace identity through the store-owned CAS.
|
|
13756
13864
|
*/
|
|
@@ -13839,14 +13947,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13839
13947
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
13840
13948
|
});
|
|
13841
13949
|
}
|
|
13950
|
+
const carriedMessages = data.messages.filter((message) => !isResumeNoticeMessage(message));
|
|
13842
13951
|
const resumedData = {
|
|
13843
13952
|
...data,
|
|
13844
13953
|
...resumeValidation ? { resumeValidation } : {},
|
|
13845
|
-
|
|
13954
|
+
messages: [...carriedMessages, ...noticeMessages]
|
|
13846
13955
|
};
|
|
13847
13956
|
let handle;
|
|
13848
13957
|
try {
|
|
13849
|
-
handle = await
|
|
13958
|
+
handle = await openSessionForAppend(file);
|
|
13850
13959
|
} catch (err) {
|
|
13851
13960
|
emitSessionStoreError(this.events, canonicalId, file, "resume", toErrorMessage(err), false);
|
|
13852
13961
|
throw new Error(
|
|
@@ -14025,10 +14134,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14025
14134
|
const limit = criteria.limit ?? 100;
|
|
14026
14135
|
if (this.catalogClient) {
|
|
14027
14136
|
const records = await this.catalogClient.call("list_catalog", {
|
|
14028
|
-
limit
|
|
14029
|
-
...criteria
|
|
14137
|
+
limit,
|
|
14138
|
+
...criteria
|
|
14030
14139
|
});
|
|
14031
|
-
return this.scrubSummaries(records)
|
|
14140
|
+
return this.scrubSummaries(records);
|
|
14032
14141
|
}
|
|
14033
14142
|
try {
|
|
14034
14143
|
const indexed = await this.readIndex();
|
|
@@ -14253,11 +14362,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14253
14362
|
return shardKeys;
|
|
14254
14363
|
}
|
|
14255
14364
|
async readOrBuildShardManifest(shardKey) {
|
|
14256
|
-
const cached = this.shardManifestCache.get(shardKey);
|
|
14257
|
-
if (cached) return cached;
|
|
14258
14365
|
const manifestPath = this.shardManifestPath(shardKey);
|
|
14366
|
+
const cached = await this.freshShardManifestCacheEntry(shardKey, manifestPath);
|
|
14367
|
+
if (cached) return cached;
|
|
14259
14368
|
return withFileLock(manifestPath, async () => {
|
|
14260
|
-
const lockedCached = this.
|
|
14369
|
+
const lockedCached = await this.freshShardManifestCacheEntry(shardKey, manifestPath);
|
|
14261
14370
|
if (lockedCached) return lockedCached;
|
|
14262
14371
|
const entry = await readOrBuildShardManifestEntry({
|
|
14263
14372
|
shardKey,
|
|
@@ -14268,10 +14377,38 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14268
14377
|
summaryHeaderFor: (ref) => this.summaryHeaderFor(ref),
|
|
14269
14378
|
summaryFor: (id) => this.summaryFor(id)
|
|
14270
14379
|
});
|
|
14271
|
-
|
|
14380
|
+
try {
|
|
14381
|
+
const stat15 = await fsp19.stat(manifestPath);
|
|
14382
|
+
this.shardManifestCache.set(shardKey, {
|
|
14383
|
+
entry,
|
|
14384
|
+
mtimeMs: stat15.mtimeMs,
|
|
14385
|
+
size: stat15.size,
|
|
14386
|
+
ino: stat15.ino
|
|
14387
|
+
});
|
|
14388
|
+
} catch {
|
|
14389
|
+
this.shardManifestCache.delete(shardKey);
|
|
14390
|
+
}
|
|
14272
14391
|
return entry;
|
|
14273
14392
|
});
|
|
14274
14393
|
}
|
|
14394
|
+
/**
|
|
14395
|
+
* Shard manifests are invalidated by other store processes via atomic
|
|
14396
|
+
* delete/rebuild. Validate the in-memory projection against the persisted
|
|
14397
|
+
* file so one long-lived process cannot retain another process's stale view.
|
|
14398
|
+
*/
|
|
14399
|
+
async freshShardManifestCacheEntry(shardKey, manifestPath) {
|
|
14400
|
+
const cached = this.shardManifestCache.get(shardKey);
|
|
14401
|
+
if (!cached) return void 0;
|
|
14402
|
+
try {
|
|
14403
|
+
const stat15 = await fsp19.stat(manifestPath);
|
|
14404
|
+
if (stat15.mtimeMs === cached.mtimeMs && stat15.size === cached.size && stat15.ino === cached.ino) {
|
|
14405
|
+
return cached.entry;
|
|
14406
|
+
}
|
|
14407
|
+
} catch {
|
|
14408
|
+
}
|
|
14409
|
+
this.shardManifestCache.delete(shardKey);
|
|
14410
|
+
return void 0;
|
|
14411
|
+
}
|
|
14275
14412
|
async collectSessionFilesInShard(shardKey) {
|
|
14276
14413
|
const dir = shardKey ? path32.join(this.dir, shardKey) : this.dir;
|
|
14277
14414
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
@@ -14551,6 +14688,23 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14551
14688
|
});
|
|
14552
14689
|
}
|
|
14553
14690
|
};
|
|
14691
|
+
async function openSessionForAppend(file) {
|
|
14692
|
+
const handle = await fsp19.open(file, "a+", 384);
|
|
14693
|
+
try {
|
|
14694
|
+
const stat15 = await handle.stat();
|
|
14695
|
+
if (stat15.size > 0) {
|
|
14696
|
+
const tail = Buffer.allocUnsafe(1);
|
|
14697
|
+
const { bytesRead } = await handle.read(tail, 0, 1, stat15.size - 1);
|
|
14698
|
+
if (bytesRead === 1 && tail[0] !== 10) {
|
|
14699
|
+
await handle.appendFile("\n", "utf8");
|
|
14700
|
+
}
|
|
14701
|
+
}
|
|
14702
|
+
return handle;
|
|
14703
|
+
} catch (err) {
|
|
14704
|
+
await handle.close().catch(() => void 0);
|
|
14705
|
+
throw err;
|
|
14706
|
+
}
|
|
14707
|
+
}
|
|
14554
14708
|
|
|
14555
14709
|
// src/storage/session-rewind-apply.ts
|
|
14556
14710
|
async function applyRewindToConversation(opts) {
|
|
@@ -14663,17 +14817,20 @@ var DefaultSessionRewinder = class {
|
|
|
14663
14817
|
const targetIndex = checkpoints[n - 1]?.promptIndex ?? 0;
|
|
14664
14818
|
const snapshotsToRevert = [];
|
|
14665
14819
|
let shouldRevert = false;
|
|
14820
|
+
let removedEvents = 0;
|
|
14666
14821
|
for await (const event of this.readEvents(file)) {
|
|
14667
14822
|
if (event.type === "checkpoint" && event.promptIndex === targetIndex) {
|
|
14668
14823
|
shouldRevert = true;
|
|
14669
14824
|
continue;
|
|
14670
14825
|
}
|
|
14671
|
-
if (shouldRevert
|
|
14826
|
+
if (!shouldRevert) continue;
|
|
14827
|
+
removedEvents++;
|
|
14828
|
+
if (event.type === "file_snapshot") {
|
|
14672
14829
|
snapshotsToRevert.push({ promptIndex: event.promptIndex, files: event.files });
|
|
14673
14830
|
}
|
|
14674
14831
|
}
|
|
14675
14832
|
const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);
|
|
14676
|
-
return { ...result, toPromptIndex: targetIndex, removedEvents
|
|
14833
|
+
return { ...result, toPromptIndex: targetIndex, removedEvents };
|
|
14677
14834
|
}
|
|
14678
14835
|
async rewindToStart(sessionId) {
|
|
14679
14836
|
const file = this.sessionFile(sessionId);
|
|
@@ -9,7 +9,7 @@ import type { Provider } from '../types/provider.js';
|
|
|
9
9
|
* rememberSage rather than the legacy MemoryStore.remember(),
|
|
10
10
|
* making them visible to searchSage/turn-middleware.
|
|
11
11
|
*/
|
|
12
|
-
type ConsolidatorSageKind = 'fact' | 'decision' | 'convention' | 'preference' | 'anti_pattern' | 'warning' | 'workflow' | 'bug_root_cause' | 'file_note' | 'symbol_note' | 'command_note';
|
|
12
|
+
type ConsolidatorSageKind = 'fact' | 'decision' | 'convention' | 'preference' | 'anti_pattern' | 'warning' | 'workflow' | 'bug_root_cause' | 'file_note' | 'symbol_note' | 'command_note' | 'session_digest';
|
|
13
13
|
interface ConsolidatorMemoryAnchor {
|
|
14
14
|
type: 'file' | 'directory' | 'symbol' | 'package' | 'command' | 'test' | 'git';
|
|
15
15
|
path?: string | undefined;
|
|
@@ -25,12 +25,14 @@ export interface ConsolidatorSage {
|
|
|
25
25
|
priority?: string | undefined;
|
|
26
26
|
importance?: number | undefined;
|
|
27
27
|
confidence?: number | undefined;
|
|
28
|
-
persistence?: 'long_lived' | undefined;
|
|
28
|
+
persistence?: 'long_lived' | 'short_lived' | undefined;
|
|
29
29
|
anchors?: ConsolidatorMemoryAnchor[] | undefined;
|
|
30
30
|
sources?: Array<{
|
|
31
31
|
type: string;
|
|
32
32
|
sessionId?: string | undefined;
|
|
33
33
|
}> | undefined;
|
|
34
|
+
ownerSessionId?: string | undefined;
|
|
35
|
+
expiresAt?: string | undefined;
|
|
34
36
|
}): Promise<unknown>;
|
|
35
37
|
listSage?(statuses?: Array<'active'>): Promise<unknown[]>;
|
|
36
38
|
searchSage?(query: string, opts?: {
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { ResumeValidation, SessionEvent } from '../types/session.js';
|
|
2
2
|
/** Revalidate the latest persisted hash for every distinct observed path. */
|
|
3
3
|
export declare function validateResumeFileObservations(events: readonly SessionEvent[], projectRoot: string): Promise<ResumeValidation>;
|
|
4
|
+
/**
|
|
5
|
+
* Headers of the system messages `resume()` injects.
|
|
6
|
+
*
|
|
7
|
+
* They are described as ephemeral, but every consumer hands the resumed
|
|
8
|
+
* message list to `replaceMessages`, which journals it as a
|
|
9
|
+
* `messages_replaced` snapshot — so a notice written on one resume is replayed
|
|
10
|
+
* as ordinary conversation on the next one, and a fresh notice is added on top.
|
|
11
|
+
* Three resumes with the same modified file left three copies, all but the last
|
|
12
|
+
* describing a check that had already been superseded. `resume()` therefore
|
|
13
|
+
* strips previous notices from the replayed conversation before appending the
|
|
14
|
+
* current ones; these prefixes are how it recognizes them.
|
|
15
|
+
*/
|
|
16
|
+
export declare const RESUME_NOTICE_HEADERS: readonly ['[SESSION RESUME FILE VALIDATION]', '[SESSION RESUME INTERRUPTED WORK]'];
|
|
17
|
+
/**
|
|
18
|
+
* True for a system message this module produced on an earlier resume.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately narrow: only a `system` message whose text *starts* with one of
|
|
21
|
+
* the headers matches, so a user or model quoting a notice back is never
|
|
22
|
+
* mistaken for one.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isResumeNoticeMessage(message: {
|
|
25
|
+
role: string;
|
|
26
|
+
content: unknown;
|
|
27
|
+
}): boolean;
|
|
4
28
|
/** Build the ephemeral system message injected into the first resumed turn. */
|
|
5
29
|
export declare function formatResumeValidationNotice(validation: ResumeValidation, projectRoot: string): string | null;
|
|
6
30
|
/**
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export declare function shouldSkipSessionDirectoryEntry(name: string): boolean;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Re-exported under the store's own name; the rule itself lives with
|
|
4
|
+
* `sessionScopedPath`, which is what writes the sidecars this excludes.
|
|
5
|
+
*/
|
|
6
|
+
export { isSessionTranscriptFileName as isSessionJsonlFileName } from '../../utils/session-scoped-path.js';
|
|
3
7
|
//# sourceMappingURL=directory-scan.d.ts.map
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import type { ForkedSession, SessionData, SessionForkOptions, SessionMetadata, SessionWriter } from '../../types/session.js';
|
|
1
|
+
import type { ForkedSession, SessionData, SessionEvent, SessionForkOptions, SessionMetadata, SessionWriter } from '../../types/session.js';
|
|
2
2
|
export interface SessionForkHost {
|
|
3
3
|
load(id: string): Promise<SessionData>;
|
|
4
4
|
create(meta: Omit<SessionMetadata, 'startedAt'>): Promise<SessionWriter>;
|
|
5
5
|
delete(id: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Every persisted event of `id`, exactly as it sits on disk.
|
|
8
|
+
*
|
|
9
|
+
* `load()` is NOT a substitute. Its loader strips the payload out of every
|
|
10
|
+
* superseded `messages_replaced` / `context_snapshot` to bound heap, in
|
|
11
|
+
* place, on the array it returns. Forking from that array copies the
|
|
12
|
+
* emptied snapshots into the child journal, where replay reads them as
|
|
13
|
+
* "the conversation is now zero messages" and the child loses every turn
|
|
14
|
+
* that preceded the newest snapshot inside the fork boundary. A fork is a
|
|
15
|
+
* byte-level inheritance of the parent prefix, so it has to read the bytes.
|
|
16
|
+
*/
|
|
17
|
+
readRawEvents(id: string): Promise<SessionEvent[]>;
|
|
6
18
|
}
|
|
7
19
|
export declare function forkSession(host: SessionForkHost, id: string, opts?: SessionForkOptions): Promise<ForkedSession>;
|
|
8
20
|
//# sourceMappingURL=fork-session.d.ts.map
|
|
@@ -9,6 +9,17 @@ export declare class SessionLoadCache {
|
|
|
9
9
|
constructor(entries?: Map<string, LoadCacheEntry>);
|
|
10
10
|
private bytes;
|
|
11
11
|
clear(sessionId?: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* A hit hands back fresh `messages` / `events` arrays over the cached
|
|
14
|
+
* contents.
|
|
15
|
+
*
|
|
16
|
+
* The entry outlives every caller, and callers treat what they get as their
|
|
17
|
+
* own: `resume()` passes `messages` straight into a live conversation, and
|
|
18
|
+
* anything walking `events` may splice it. Returning the cached arrays
|
|
19
|
+
* themselves let one caller's edit rewrite what the next one loads. The
|
|
20
|
+
* elements are still shared — copying them would defeat the cache — so
|
|
21
|
+
* entries remain read-only *contents* behind private containers.
|
|
22
|
+
*/
|
|
12
23
|
getFresh(id: string, stat: FileStatSnapshot, full: boolean): SessionData | null;
|
|
13
24
|
set(id: string, stat: FileStatSnapshot, data: SessionData): void;
|
|
14
25
|
private delete;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prunable === is a transcript. This module held the only complete sidecar
|
|
3
|
+
* list in the codebase while the listing scans held shorter ones; sharing the
|
|
4
|
+
* predicate is what stops them disagreeing again.
|
|
5
|
+
*/
|
|
1
6
|
export declare function isPrunableSessionJsonl(name: string): boolean;
|
|
2
7
|
export declare function pruneSessionFiles(storeDir: string, maxAgeDays: number, deleteSession: (id: string) => Promise<void>): Promise<number>;
|
|
3
8
|
//# sourceMappingURL=prune-helpers.d.ts.map
|