@hanhnd/agent-kit 1.0.32 → 1.0.33
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/cli/commands/cli-digest-pending.test.js +111 -0
- package/dist/cli/commands/cli-memory.test.js +34 -0
- package/dist/cli/{memory.js → commands/memory.js} +24 -11
- package/dist/cli/index.js +2 -2
- package/dist/{utils → cli/utils}/args.d.ts +2 -1
- package/dist/{kit-cli.js → cli.js} +342 -58
- package/dist/{tools/config.d.ts → core/config/index.d.ts} +2 -7
- package/dist/{tools/config.js → core/config/index.js} +2 -13
- package/dist/core/handoffs/index.d.ts +16 -0
- package/dist/{tools/handoffs.js → core/handoffs/index.js} +3 -3
- package/dist/{tools/security.d.ts → core/security/index.d.ts} +0 -19
- package/dist/{tools/security.js → core/security/index.js} +3 -76
- package/dist/entrypoints/cli.js +11 -0
- package/dist/entrypoints/server.js +37 -0
- package/dist/{tools → mcp}/agent.js +1 -1
- package/dist/{tools → mcp}/core.d.ts +1 -1
- package/dist/{tools → mcp}/core.js +2 -2
- package/dist/{tools → mcp}/integration.js +3 -3
- package/dist/mcp/memory.d.ts +15 -0
- package/dist/{tools → mcp}/memory.js +15 -13
- package/dist/{kit-server.js → server.js} +80 -70
- package/dist/services/digest/background.d.ts +11 -0
- package/dist/services/digest/background.js +258 -0
- package/dist/{memory → services}/digest/constants.d.ts +4 -1
- package/dist/{memory → services}/digest/constants.js +6 -2
- package/dist/services/digest/digest-background.test.js +206 -0
- package/dist/{tools/__tests__ → services/digest}/digest-init.test.js +4 -3
- package/dist/{memory/__tests__ → services/digest}/digest-llama-local.test.js +1 -13
- package/dist/{memory/__tests__ → services/digest}/digest-pending.test.js +39 -11
- package/dist/{memory/__tests__ → services/digest}/digest-processor.test.js +5 -7
- package/dist/{memory → services}/digest/files.d.ts +0 -1
- package/dist/{memory → services}/digest/files.js +2 -2
- package/dist/services/digest/model-registry.d.ts +2 -0
- package/dist/{memory → services}/digest/model-registry.js +9 -8
- package/dist/services/digest/processor.d.ts +13 -0
- package/dist/{memory → services}/digest/processor.js +48 -31
- package/dist/services/digest/providers/llama-local.d.ts +2 -0
- package/dist/{memory → services}/digest/providers/llama-local.js +3 -4
- package/dist/{memory → services}/digest/types.d.ts +34 -2
- package/dist/services/digest/types.js +6 -0
- package/dist/{memory → services}/digest/worker.d.ts +0 -1
- package/dist/{memory → services}/digest/worker.js +2 -2
- package/dist/{utils → services/integration}/credentials.js +1 -1
- package/dist/{utils/parser.js → services/integration/parser/adf.js} +1 -0
- package/dist/{memory → services/memory}/chunker.d.ts +0 -2
- package/dist/{memory → services/memory}/chunker.js +3 -3
- package/dist/{memory/__tests__ → services/memory}/chunker.test.js +1 -34
- package/dist/{memory → services/memory}/constants.d.ts +1 -1
- package/dist/{memory → services/memory}/constants.js +2 -1
- package/dist/{memory → services/memory}/embedder.d.ts +2 -2
- package/dist/{memory → services/memory}/embedder.js +5 -5
- package/dist/services/memory/embedder.test.js +13 -0
- package/dist/{memory → services/memory}/indexer.d.ts +2 -1
- package/dist/{memory → services/memory}/indexer.js +1 -1
- package/dist/{memory/__tests__ → services/memory}/indexer.test.js +4 -3
- package/dist/{tools/__tests__ → services/memory}/memory.test.js +54 -13
- package/dist/{memory/__tests__ → services/memory}/store.test.js +3 -2
- package/dist/{memory → services/memory}/types.d.ts +7 -2
- package/dist/services/memory/types.js +6 -0
- package/dist/utils/files.d.ts +0 -1
- package/dist/utils/files.js +1 -1
- package/dist/{memory/__tests__/lockfile.test.js → utils/files.test.js} +14 -14
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/json.d.ts +0 -1
- package/dist/utils/json.js +0 -10
- package/dist/utils/paths.d.ts +0 -1
- package/dist/utils/paths.js +1 -1
- package/package.json +6 -6
- package/dist/memory/__tests__/digest-worker.test.js +0 -46
- package/dist/memory/__tests__/embedder.test.js +0 -14
- package/dist/memory/digest/model-registry.d.ts +0 -6
- package/dist/memory/digest/processor.d.ts +0 -7
- package/dist/memory/digest/providers/llama-local.d.ts +0 -7
- package/dist/memory/digest/types.js +0 -1
- package/dist/memory/types.js +0 -1
- package/dist/tools/__tests__/cli-digest-pending.test.js +0 -51
- package/dist/tools/__tests__/cli-memory.test.js +0 -22
- package/dist/tools/handoffs.d.ts +0 -22
- package/dist/tools/memory.d.ts +0 -16
- /package/dist/{tools/__tests__ → cli/commands}/cli-digest-pending.test.d.ts +0 -0
- /package/dist/{tools/__tests__ → cli/commands}/cli-memory.test.d.ts +0 -0
- /package/dist/cli/{memory.d.ts → commands/memory.d.ts} +0 -0
- /package/dist/{utils → cli/utils}/args.js +0 -0
- /package/dist/{kit-cli.d.ts → entrypoints/cli.d.ts} +0 -0
- /package/dist/{kit-server.d.ts → entrypoints/server.d.ts} +0 -0
- /package/dist/{tools → mcp}/agent.d.ts +0 -0
- /package/dist/{tools → mcp}/integration.d.ts +0 -0
- /package/dist/{memory/__tests__/chunker.test.d.ts → services/digest/digest-background.test.d.ts} +0 -0
- /package/dist/{tools/__tests__ → services/digest}/digest-init.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-llama-local.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-pending.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-processor.test.d.ts +0 -0
- /package/dist/{utils → services/integration}/credentials.d.ts +0 -0
- /package/dist/{utils/parser.d.ts → services/integration/parser/adf.d.ts} +0 -0
- /package/dist/{memory/__tests__/digest-worker.test.d.ts → services/memory/chunker.test.d.ts} +0 -0
- /package/dist/{memory/__tests__ → services/memory}/embedder.test.d.ts +0 -0
- /package/dist/{memory → services/memory}/index.d.ts +0 -0
- /package/dist/{memory → services/memory}/index.js +0 -0
- /package/dist/{memory/__tests__ → services/memory}/indexer.test.d.ts +0 -0
- /package/dist/{tools/__tests__ → services/memory}/memory.test.d.ts +0 -0
- /package/dist/{memory → services/memory}/store.d.ts +0 -0
- /package/dist/{memory → services/memory}/store.js +0 -0
- /package/dist/{memory/__tests__ → services/memory}/store.test.d.ts +0 -0
- /package/dist/{memory/__tests__/lockfile.test.d.ts → utils/files.test.d.ts} +0 -0
- /package/dist/{__tests__/temp-dir.d.ts → utils/temp-dir.test.d.ts} +0 -0
- /package/dist/{__tests__/temp-dir.js → utils/temp-dir.test.js} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { DigestModelId } from './types.js';
|
|
2
|
+
export declare const DEFAULT_DIGEST_MODEL_ID = DigestModelId.DEFAULT;
|
|
2
3
|
export declare const DEFAULT_DIGEST_MAX_INPUT_CHARS = 3000;
|
|
3
4
|
export declare const DEFAULT_DIGEST_TIMEOUT_MS = 120000;
|
|
4
5
|
export declare const DIGEST_TIMEOUT_GRACE_MS = 5000;
|
|
@@ -9,6 +10,8 @@ export declare const WIKI_DIR: string;
|
|
|
9
10
|
export declare const WIKI_RAW_DIR: string;
|
|
10
11
|
export declare const PROVISIONAL_DIGEST_DIR: string;
|
|
11
12
|
export declare const DIGEST_LOCKFILE_REL_PATH: string;
|
|
13
|
+
export declare const DIGEST_WORKER_STATUS_REL_PATH: string;
|
|
14
|
+
export declare const DIGEST_WORKER_LOG_REL_PATH: string;
|
|
12
15
|
export declare const LLAMA_CONTEXT_SIZE = 4096;
|
|
13
16
|
export declare const LLAMA_MAX_GENERATED_TOKENS = 512;
|
|
14
17
|
export declare const LLAMA_TEMPERATURE = 0.1;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
-
|
|
2
|
+
import { DigestModelId } from './types.js';
|
|
3
|
+
export const DEFAULT_DIGEST_MODEL_ID = DigestModelId.DEFAULT;
|
|
3
4
|
export const DEFAULT_DIGEST_MAX_INPUT_CHARS = 3000;
|
|
4
5
|
export const DEFAULT_DIGEST_TIMEOUT_MS = 120_000;
|
|
5
6
|
export const DIGEST_TIMEOUT_GRACE_MS = 5_000;
|
|
@@ -9,7 +10,10 @@ export const KIT_DIR = '.agent-kit';
|
|
|
9
10
|
export const WIKI_DIR = path.join(KIT_DIR, 'wiki');
|
|
10
11
|
export const WIKI_RAW_DIR = path.join(WIKI_DIR, 'raw');
|
|
11
12
|
export const PROVISIONAL_DIGEST_DIR = path.join(WIKI_DIR, 'compiled', 'provisional', 'conversation-digests');
|
|
12
|
-
|
|
13
|
+
const DIGEST_WORKER_DIR = path.join(WIKI_DIR, 'digest');
|
|
14
|
+
export const DIGEST_LOCKFILE_REL_PATH = path.join(DIGEST_WORKER_DIR, 'digest-worker.lock');
|
|
15
|
+
export const DIGEST_WORKER_STATUS_REL_PATH = path.join(DIGEST_WORKER_DIR, 'digest-worker.status.json');
|
|
16
|
+
export const DIGEST_WORKER_LOG_REL_PATH = path.join(DIGEST_WORKER_DIR, 'digest-worker.log');
|
|
13
17
|
export const LLAMA_CONTEXT_SIZE = 4096;
|
|
14
18
|
export const LLAMA_MAX_GENERATED_TOKENS = 512;
|
|
15
19
|
export const LLAMA_TEMPERATURE = 0.1;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import * as assert from 'node:assert/strict';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, describe, test } from 'node:test';
|
|
5
|
+
import { createTempDirTracker } from '../../utils/temp-dir.test.js';
|
|
6
|
+
import { writeConversationDigestSettings } from './files.js';
|
|
7
|
+
import { launchDigestPendingWorker, runDigestPendingWorker } from './background.js';
|
|
8
|
+
import { DIGEST_WORKER_LOG_REL_PATH, DIGEST_WORKER_STATUS_REL_PATH } from './constants.js';
|
|
9
|
+
import { DigestModelId } from './types.js';
|
|
10
|
+
const tempDirs = createTempDirTracker();
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
tempDirs.cleanup();
|
|
13
|
+
});
|
|
14
|
+
function digestWorkerLogPath(workspace) {
|
|
15
|
+
return path.join(workspace, DIGEST_WORKER_LOG_REL_PATH);
|
|
16
|
+
}
|
|
17
|
+
function digestWorkerStatusPath(workspace) {
|
|
18
|
+
return path.join(workspace, DIGEST_WORKER_STATUS_REL_PATH);
|
|
19
|
+
}
|
|
20
|
+
function readDigestWorkerStatus(workspace) {
|
|
21
|
+
try {
|
|
22
|
+
const p = digestWorkerStatusPath(workspace);
|
|
23
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function writeDigestWorkerStatus(workspace, status) {
|
|
30
|
+
const p = digestWorkerStatusPath(workspace);
|
|
31
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
32
|
+
fs.writeFileSync(p, JSON.stringify(status), 'utf8');
|
|
33
|
+
}
|
|
34
|
+
function writeInitState(workspace) {
|
|
35
|
+
writeConversationDigestSettings(workspace, {
|
|
36
|
+
enabled: true,
|
|
37
|
+
initialized: true,
|
|
38
|
+
modelId: DigestModelId.DEFAULT,
|
|
39
|
+
initializedAt: new Date().toISOString(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function writeConvFile(workspace) {
|
|
43
|
+
const rawDir = path.join(workspace, '.agent-kit', 'wiki', 'raw');
|
|
44
|
+
fs.mkdirSync(rawDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(rawDir, 'conv_2026-01-01T00-00-00-001Z.md'), '**User:** hello\n', 'utf8');
|
|
46
|
+
}
|
|
47
|
+
function status(overrides = {}) {
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
return {
|
|
50
|
+
state: 'running',
|
|
51
|
+
pid: process.pid,
|
|
52
|
+
startedAt: now,
|
|
53
|
+
updatedAt: now,
|
|
54
|
+
pendingAtStart: 1,
|
|
55
|
+
processed: 0,
|
|
56
|
+
skipped: 0,
|
|
57
|
+
errors: 0,
|
|
58
|
+
...overrides,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
describe('digest background helpers', () => {
|
|
62
|
+
test('uses .agent-kit/wiki/digest paths for worker files', () => {
|
|
63
|
+
const workspace = tempDirs.makeTempDir('digest-bg-paths-');
|
|
64
|
+
assert.equal(digestWorkerStatusPath(workspace), path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.status.json'));
|
|
65
|
+
assert.equal(digestWorkerLogPath(workspace), path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.log'));
|
|
66
|
+
});
|
|
67
|
+
test('writes valid status atomically and tolerates invalid status reads', () => {
|
|
68
|
+
const workspace = tempDirs.makeTempDir('digest-bg-status-');
|
|
69
|
+
const written = status({ state: 'complete', pid: null, processed: 1 });
|
|
70
|
+
writeDigestWorkerStatus(workspace, written);
|
|
71
|
+
assert.deepEqual(readDigestWorkerStatus(workspace), written);
|
|
72
|
+
fs.writeFileSync(digestWorkerStatusPath(workspace), '{bad json', 'utf8');
|
|
73
|
+
assert.equal(readDigestWorkerStatus(workspace), undefined);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('launchDigestPendingWorker', () => {
|
|
77
|
+
test('writes not-initialized and does not spawn before digest init', () => {
|
|
78
|
+
const workspace = tempDirs.makeTempDir('digest-bg-not-init-');
|
|
79
|
+
const result = launchDigestPendingWorker({
|
|
80
|
+
workspaceRoot: workspace,
|
|
81
|
+
args: ['--background'],
|
|
82
|
+
entrypoint: 'cli.js',
|
|
83
|
+
});
|
|
84
|
+
assert.equal(result.ok, true);
|
|
85
|
+
assert.equal(result.spawned, false);
|
|
86
|
+
assert.equal(result.status.state, 'not-initialized');
|
|
87
|
+
assert.equal(readDigestWorkerStatus(workspace)?.state, 'not-initialized');
|
|
88
|
+
});
|
|
89
|
+
test('writes no-pending and does not spawn when initialized with no candidates', () => {
|
|
90
|
+
const workspace = tempDirs.makeTempDir('digest-bg-no-pending-');
|
|
91
|
+
writeInitState(workspace);
|
|
92
|
+
const result = launchDigestPendingWorker({
|
|
93
|
+
workspaceRoot: workspace,
|
|
94
|
+
args: ['--background'],
|
|
95
|
+
entrypoint: 'cli.js',
|
|
96
|
+
});
|
|
97
|
+
assert.equal(result.ok, true);
|
|
98
|
+
assert.equal(result.spawned, false);
|
|
99
|
+
assert.equal(result.status.state, 'no-pending');
|
|
100
|
+
});
|
|
101
|
+
test('writes locked when an existing running worker pid is live', () => {
|
|
102
|
+
const workspace = tempDirs.makeTempDir('digest-bg-locked-status-');
|
|
103
|
+
writeDigestWorkerStatus(workspace, status());
|
|
104
|
+
const result = launchDigestPendingWorker({
|
|
105
|
+
workspaceRoot: workspace,
|
|
106
|
+
args: ['--background'],
|
|
107
|
+
entrypoint: 'cli.js',
|
|
108
|
+
});
|
|
109
|
+
assert.equal(result.spawned, false);
|
|
110
|
+
assert.equal(result.status.state, 'locked');
|
|
111
|
+
});
|
|
112
|
+
test('marks stale running status before continuing to no-pending preflight', () => {
|
|
113
|
+
const workspace = tempDirs.makeTempDir('digest-bg-stale-status-');
|
|
114
|
+
writeInitState(workspace);
|
|
115
|
+
writeDigestWorkerStatus(workspace, status({ pid: 999999999 }));
|
|
116
|
+
const result = launchDigestPendingWorker({
|
|
117
|
+
workspaceRoot: workspace,
|
|
118
|
+
args: ['--background'],
|
|
119
|
+
entrypoint: 'cli.js',
|
|
120
|
+
});
|
|
121
|
+
assert.equal(result.spawned, false);
|
|
122
|
+
assert.equal(result.status.state, 'no-pending');
|
|
123
|
+
});
|
|
124
|
+
test('tolerates corrupt stale lock files and continues preflight', () => {
|
|
125
|
+
const workspace = tempDirs.makeTempDir('digest-bg-stale-lock-');
|
|
126
|
+
writeInitState(workspace);
|
|
127
|
+
const lockDir = path.join(workspace, '.agent-kit', 'wiki', 'digest');
|
|
128
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
129
|
+
fs.writeFileSync(path.join(lockDir, 'digest-worker.lock'), 'not-json', 'utf8');
|
|
130
|
+
const result = launchDigestPendingWorker({
|
|
131
|
+
workspaceRoot: workspace,
|
|
132
|
+
args: ['--background'],
|
|
133
|
+
entrypoint: 'cli.js',
|
|
134
|
+
});
|
|
135
|
+
assert.equal(result.spawned, false);
|
|
136
|
+
assert.equal(result.status.state, 'no-pending');
|
|
137
|
+
});
|
|
138
|
+
test('writes failed status when pending work exists but entrypoint is unavailable', () => {
|
|
139
|
+
const workspace = tempDirs.makeTempDir('digest-bg-spawn-failed-');
|
|
140
|
+
writeInitState(workspace);
|
|
141
|
+
writeConvFile(workspace);
|
|
142
|
+
const result = launchDigestPendingWorker({
|
|
143
|
+
workspaceRoot: workspace,
|
|
144
|
+
args: ['--background'],
|
|
145
|
+
entrypoint: undefined,
|
|
146
|
+
});
|
|
147
|
+
assert.equal(result.ok, false);
|
|
148
|
+
assert.equal(result.spawned, false);
|
|
149
|
+
assert.equal(result.status.state, 'failed');
|
|
150
|
+
assert.equal(result.status.pendingAtStart, 1);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('runDigestPendingWorker', () => {
|
|
154
|
+
test('writes final no-pending status for initialized workspace without candidates', async () => {
|
|
155
|
+
const workspace = tempDirs.makeTempDir('digest-bg-worker-no-pending-');
|
|
156
|
+
writeInitState(workspace);
|
|
157
|
+
const result = await runDigestPendingWorker({ workspaceRoot: workspace, pendingAtStart: 0 });
|
|
158
|
+
assert.equal(result.state, 'no-pending');
|
|
159
|
+
assert.equal(readDigestWorkerStatus(workspace)?.state, 'no-pending');
|
|
160
|
+
assert.match(fs.readFileSync(digestWorkerLogPath(workspace), 'utf8'), /worker finished/);
|
|
161
|
+
});
|
|
162
|
+
test('writes failed final status for run-level processor failure', async () => {
|
|
163
|
+
const workspace = tempDirs.makeTempDir('digest-bg-worker-failed-');
|
|
164
|
+
writeInitState(workspace);
|
|
165
|
+
const wikiDir = path.join(workspace, '.agent-kit', 'wiki');
|
|
166
|
+
fs.mkdirSync(wikiDir, { recursive: true });
|
|
167
|
+
fs.writeFileSync(path.join(wikiDir, 'raw'), 'not-a-directory', 'utf8');
|
|
168
|
+
const result = await runDigestPendingWorker({ workspaceRoot: workspace, pendingAtStart: 1 });
|
|
169
|
+
assert.equal(result.state, 'failed');
|
|
170
|
+
assert.equal(result.errors, 1);
|
|
171
|
+
assert.ok(result.lastError);
|
|
172
|
+
});
|
|
173
|
+
test('updates status file in real-time as conversations are digested', async () => {
|
|
174
|
+
const workspace = tempDirs.makeTempDir('digest-bg-worker-realtime-');
|
|
175
|
+
writeInitState(workspace);
|
|
176
|
+
const rawDir = path.join(workspace, '.agent-kit', 'wiki', 'raw');
|
|
177
|
+
fs.mkdirSync(rawDir, { recursive: true });
|
|
178
|
+
fs.writeFileSync(path.join(rawDir, 'conv_1.md'), '**User:** one\n', 'utf8');
|
|
179
|
+
fs.writeFileSync(path.join(rawDir, 'conv_2.md'), '**User:** two\n', 'utf8');
|
|
180
|
+
const statusesSeen = [];
|
|
181
|
+
const mockDigestFn = async () => {
|
|
182
|
+
statusesSeen.push(readDigestWorkerStatus(workspace));
|
|
183
|
+
return {
|
|
184
|
+
markdown: 'out.md',
|
|
185
|
+
status: 'provisional',
|
|
186
|
+
contentHash: 'hash',
|
|
187
|
+
skipped: false,
|
|
188
|
+
indexed: false,
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const result = await runDigestPendingWorker({
|
|
192
|
+
workspaceRoot: workspace,
|
|
193
|
+
pendingAtStart: 2,
|
|
194
|
+
digestFn: mockDigestFn,
|
|
195
|
+
});
|
|
196
|
+
assert.equal(statusesSeen.length, 2);
|
|
197
|
+
assert.ok(statusesSeen[0]);
|
|
198
|
+
assert.equal(statusesSeen[0].state, 'running');
|
|
199
|
+
assert.equal(statusesSeen[0].processed, 0);
|
|
200
|
+
assert.ok(statusesSeen[1]);
|
|
201
|
+
assert.equal(statusesSeen[1].state, 'running');
|
|
202
|
+
assert.equal(statusesSeen[1].processed, 1);
|
|
203
|
+
assert.equal(result.state, 'complete');
|
|
204
|
+
assert.equal(result.processed, 2);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -2,8 +2,9 @@ import * as assert from 'node:assert/strict';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { afterEach, describe, test } from 'node:test';
|
|
5
|
-
import { createTempDirTracker } from '../../
|
|
6
|
-
import { initializeConversationDigestModel } from '
|
|
5
|
+
import { createTempDirTracker } from '../../utils/temp-dir.test.js';
|
|
6
|
+
import { initializeConversationDigestModel } from './processor.js';
|
|
7
|
+
import { DigestModelId } from './types.js';
|
|
7
8
|
const tempDirs = createTempDirTracker();
|
|
8
9
|
afterEach(() => {
|
|
9
10
|
tempDirs.cleanup();
|
|
@@ -23,7 +24,7 @@ describe('initializeConversationDigestModel', () => {
|
|
|
23
24
|
const workspace = tempDirs.makeTempDir('digest-init-');
|
|
24
25
|
const result = await initializeConversationDigestModel({
|
|
25
26
|
workspaceRoot: workspace,
|
|
26
|
-
modelId:
|
|
27
|
+
modelId: DigestModelId.DEFAULT,
|
|
27
28
|
allowDownload: false,
|
|
28
29
|
});
|
|
29
30
|
assert.equal(result.initialized, false);
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import * as assert from 'node:assert/strict';
|
|
2
2
|
import { describe, test } from 'node:test';
|
|
3
|
-
import { createLlamaLocalDigestProvider
|
|
3
|
+
import { createLlamaLocalDigestProvider } from '../digest/providers/llama-local.js';
|
|
4
4
|
const INPUT = {
|
|
5
5
|
sourcePath: '.agent-kit/wiki/archive/conversations/conv_2026-05-19T00-45-17-697Z.md',
|
|
6
6
|
contentHash: 'tiny',
|
|
7
7
|
content: '**User:** use local digest generation for archived conversations',
|
|
8
8
|
};
|
|
9
9
|
describe('createLlamaLocalDigestProvider', () => {
|
|
10
|
-
test('strips code fences without rewriting model output', () => {
|
|
11
|
-
const markdown = sanitizeConversationDigestMarkdown('```md\n# Notes\n\n**Summary**\n\nUse local digesting.\n```');
|
|
12
|
-
assert.match(markdown, /^# Notes/);
|
|
13
|
-
assert.match(markdown, /\*\*Summary\*\*/);
|
|
14
|
-
assert.doesNotMatch(markdown, /```/);
|
|
15
|
-
});
|
|
16
|
-
test('trims conversation exports from the end without parsing turns', () => {
|
|
17
|
-
const trimmed = trimConversationExport('first decision\nsecond decision\nfinal decision', 14);
|
|
18
|
-
assert.match(trimmed, /omitted/);
|
|
19
|
-
assert.doesNotMatch(trimmed, /first decision/);
|
|
20
|
-
assert.match(trimmed, /final decision/);
|
|
21
|
-
});
|
|
22
10
|
test('runs an opt-in smoke test against a real model', { skip: !process.env.AGENT_KIT_DIGEST_MODEL_ID }, async () => {
|
|
23
11
|
const modelId = process.env.AGENT_KIT_DIGEST_MODEL_ID;
|
|
24
12
|
const provider = await createLlamaLocalDigestProvider(modelId);
|
|
@@ -2,14 +2,15 @@ import * as assert from 'node:assert/strict';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { afterEach, describe, test } from 'node:test';
|
|
5
|
-
import { createTempDirTracker } from '../../
|
|
6
|
-
import { writeConversationDigestSettings } from '
|
|
7
|
-
import {
|
|
5
|
+
import { createTempDirTracker } from '../../utils/temp-dir.test.js';
|
|
6
|
+
import { defaultProvisionalDigestDir, readConversationDigestInput, writeConversationDigestSettings, writeProvisionalDigestFile, } from './files.js';
|
|
7
|
+
import { DigestModelId } from './types.js';
|
|
8
|
+
import { digestPendingConversations, summarizePendingConversations } from './processor.js';
|
|
8
9
|
const tempDirs = createTempDirTracker();
|
|
9
10
|
afterEach(() => {
|
|
10
11
|
tempDirs.cleanup();
|
|
11
12
|
});
|
|
12
|
-
function writeInitState(workspace, modelId =
|
|
13
|
+
function writeInitState(workspace, modelId = DigestModelId.DEFAULT) {
|
|
13
14
|
writeConversationDigestSettings(workspace, {
|
|
14
15
|
enabled: true,
|
|
15
16
|
initialized: true,
|
|
@@ -47,7 +48,7 @@ describe('digestPendingConversations', () => {
|
|
|
47
48
|
test('C14: init state, live lockfile → noop locked', async () => {
|
|
48
49
|
const workspace = tempDirs.makeTempDir('pending-c14-');
|
|
49
50
|
writeInitState(workspace);
|
|
50
|
-
const lockDir = path.join(workspace, '.agent-kit');
|
|
51
|
+
const lockDir = path.join(workspace, '.agent-kit', 'wiki', 'digest');
|
|
51
52
|
fs.mkdirSync(lockDir, { recursive: true });
|
|
52
53
|
fs.writeFileSync(path.join(lockDir, 'digest-worker.lock'), JSON.stringify({ pid: process.pid }));
|
|
53
54
|
const result = await digestPendingConversations({ workspaceRoot: workspace });
|
|
@@ -65,7 +66,7 @@ describe('digestPendingConversations', () => {
|
|
|
65
66
|
assert.equal(result.action, 'noop');
|
|
66
67
|
if (result.action === 'noop')
|
|
67
68
|
assert.equal(result.reason, 'no-pending');
|
|
68
|
-
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'digest-worker.lock')), false);
|
|
69
|
+
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.lock')), false);
|
|
69
70
|
});
|
|
70
71
|
test('C19: wiki/raw dir does not exist → noop no-pending', async () => {
|
|
71
72
|
const workspace = tempDirs.makeTempDir('pending-c19-');
|
|
@@ -89,7 +90,7 @@ describe('digestPendingConversations', () => {
|
|
|
89
90
|
assert.equal(result.count, 1);
|
|
90
91
|
assert.equal(result.errors, 0);
|
|
91
92
|
}
|
|
92
|
-
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'digest-worker.lock')), false);
|
|
93
|
+
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.lock')), false);
|
|
93
94
|
});
|
|
94
95
|
test('C17: digestFn throws for one file → errors:1, lock released', async () => {
|
|
95
96
|
const workspace = tempDirs.makeTempDir('pending-c17-');
|
|
@@ -108,19 +109,19 @@ describe('digestPendingConversations', () => {
|
|
|
108
109
|
assert.equal(result.errors, 1);
|
|
109
110
|
assert.equal(result.count, 0);
|
|
110
111
|
}
|
|
111
|
-
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'digest-worker.lock')), false);
|
|
112
|
+
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.lock')), false);
|
|
112
113
|
});
|
|
113
114
|
test('C18: stale lockfile (dead PID) → reclaims lock and returns (no-pending since no conv files)', async () => {
|
|
114
115
|
const workspace = tempDirs.makeTempDir('pending-c18-');
|
|
115
116
|
writeInitState(workspace);
|
|
116
|
-
const lockDir = path.join(workspace, '.agent-kit');
|
|
117
|
+
const lockDir = path.join(workspace, '.agent-kit', 'wiki', 'digest');
|
|
117
118
|
fs.mkdirSync(lockDir, { recursive: true });
|
|
118
119
|
fs.writeFileSync(path.join(lockDir, 'digest-worker.lock'), JSON.stringify({ pid: 999999999 }));
|
|
119
120
|
const result = await digestPendingConversations({ workspaceRoot: workspace });
|
|
120
121
|
assert.ok(result.ok);
|
|
121
122
|
if (result.action === 'noop')
|
|
122
123
|
assert.notEqual(result.reason, 'locked');
|
|
123
|
-
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'digest-worker.lock')), false);
|
|
124
|
+
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.lock')), false);
|
|
124
125
|
});
|
|
125
126
|
test('C20: 3 undigested conv files → digestFn called 3 times, count+skipped+errors=3', async () => {
|
|
126
127
|
const workspace = tempDirs.makeTempDir('pending-c20-');
|
|
@@ -159,6 +160,33 @@ describe('digestPendingConversations', () => {
|
|
|
159
160
|
const result = await digestPendingConversations({ workspaceRoot: workspace });
|
|
160
161
|
assert.equal(result.ok, false);
|
|
161
162
|
assert.equal(result.action, 'error');
|
|
162
|
-
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'digest-worker.lock')), false);
|
|
163
|
+
assert.equal(fs.existsSync(path.join(workspace, '.agent-kit', 'wiki', 'digest', 'digest-worker.lock')), false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('summarizePendingConversations', () => {
|
|
167
|
+
test('returns not-initialized without raw discovery side effects', () => {
|
|
168
|
+
const workspace = tempDirs.makeTempDir('pending-summary-init-');
|
|
169
|
+
const result = summarizePendingConversations(workspace);
|
|
170
|
+
assert.equal(result.initialized, false);
|
|
171
|
+
assert.equal(result.pending, 0);
|
|
172
|
+
assert.equal(result.reason, 'not-initialized');
|
|
173
|
+
});
|
|
174
|
+
test('returns no-pending when initialized workspace has no raw conversations', () => {
|
|
175
|
+
const workspace = tempDirs.makeTempDir('pending-summary-empty-');
|
|
176
|
+
writeInitState(workspace);
|
|
177
|
+
const result = summarizePendingConversations(workspace);
|
|
178
|
+
assert.equal(result.initialized, true);
|
|
179
|
+
assert.equal(result.pending, 0);
|
|
180
|
+
assert.equal(result.reason, 'no-pending');
|
|
181
|
+
});
|
|
182
|
+
test('counts only conversations without existing provisional digests', () => {
|
|
183
|
+
const workspace = tempDirs.makeTempDir('pending-summary-count-');
|
|
184
|
+
writeInitState(workspace);
|
|
185
|
+
const first = writeConvFile(workspace, 'conv_2026-01-01T00-00-00-001Z.md', '**User:** a\n');
|
|
186
|
+
writeConvFile(workspace, 'conv_2026-01-01T00-00-00-002Z.md', '**User:** b\n');
|
|
187
|
+
writeProvisionalDigestFile(defaultProvisionalDigestDir(workspace), readConversationDigestInput(workspace, first), '# Existing\n');
|
|
188
|
+
const result = summarizePendingConversations(workspace);
|
|
189
|
+
assert.equal(result.initialized, true);
|
|
190
|
+
assert.equal(result.pending, 1);
|
|
163
191
|
});
|
|
164
192
|
});
|
|
@@ -2,10 +2,10 @@ import * as assert from 'node:assert/strict';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { afterEach, describe, test } from 'node:test';
|
|
5
|
-
import { createTempDirTracker } from '../../
|
|
6
|
-
import { DEFAULT_DIGEST_MODEL_ID } from '
|
|
7
|
-
import { defaultProvisionalDigestDir,
|
|
8
|
-
import { digestConversationFile } from '
|
|
5
|
+
import { createTempDirTracker } from '../../utils/temp-dir.test.js';
|
|
6
|
+
import { DEFAULT_DIGEST_MODEL_ID } from './constants.js';
|
|
7
|
+
import { defaultProvisionalDigestDir, writeProvisionalDigestFile, readConversationDigestInput } from './files.js';
|
|
8
|
+
import { digestConversationFile } from './processor.js';
|
|
9
9
|
const tempDirs = createTempDirTracker();
|
|
10
10
|
afterEach(() => {
|
|
11
11
|
tempDirs.cleanup();
|
|
@@ -18,9 +18,7 @@ describe('digestConversationFile', () => {
|
|
|
18
18
|
fs.writeFileSync(inputPath, '**User:** remember this decision', 'utf8');
|
|
19
19
|
const input = readConversationDigestInput(workspace, inputPath);
|
|
20
20
|
const outDir = defaultProvisionalDigestDir(workspace);
|
|
21
|
-
const markdownPath =
|
|
22
|
-
fs.mkdirSync(path.dirname(markdownPath), { recursive: true });
|
|
23
|
-
fs.writeFileSync(markdownPath, '# Conversation Digest: conv\n', 'utf8');
|
|
21
|
+
const markdownPath = writeProvisionalDigestFile(outDir, input, '# Conversation Digest: conv\n');
|
|
24
22
|
const result = await digestConversationFile({
|
|
25
23
|
workspaceRoot: workspace,
|
|
26
24
|
inputPath,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ConversationDigestInput, ConversationDigestSettings } from './types.js';
|
|
2
2
|
export declare function defaultProvisionalDigestDir(workspaceRoot: string): string;
|
|
3
3
|
export declare function readConversationDigestInput(workspaceRoot: string, inputPath: string): ConversationDigestInput;
|
|
4
|
-
export declare function provisionalDigestPath(outDir: string, input: Pick<ConversationDigestInput, 'sourcePath' | 'contentHash'>): string;
|
|
5
4
|
export declare function findExistingProvisionalDigest(outDir: string, input: ConversationDigestInput): string | undefined;
|
|
6
5
|
export declare function writeProvisionalDigestFile(outDir: string, input: ConversationDigestInput, markdown: string): string;
|
|
7
6
|
export declare function writeConversationDigestSettings(workspaceRoot: string, digest: ConversationDigestSettings): void;
|
|
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { PROVISIONAL_DIGEST_DIR } from './constants.js';
|
|
4
4
|
import { atomicWriteTextFile } from '../../utils/files.js';
|
|
5
5
|
import { sha256Hex } from '../../utils/hash.js';
|
|
6
|
-
import { loadProjectSettings, writeProjectSettings } from '../../
|
|
6
|
+
import { loadProjectSettings, writeProjectSettings } from '../../core/config/index.js';
|
|
7
7
|
export function defaultProvisionalDigestDir(workspaceRoot) {
|
|
8
8
|
return path.join(workspaceRoot, PROVISIONAL_DIGEST_DIR);
|
|
9
9
|
}
|
|
@@ -16,7 +16,7 @@ export function readConversationDigestInput(workspaceRoot, inputPath) {
|
|
|
16
16
|
contentHash: sha256Hex(content),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
function provisionalDigestPath(outDir, input) {
|
|
20
20
|
const safeName = path
|
|
21
21
|
.basename(input.sourcePath)
|
|
22
22
|
.replace(/\.[^.]+$/, '')
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
import { DigestModelId } from './types.js';
|
|
2
|
+
class DigestModelRegistryError extends Error {
|
|
2
3
|
constructor(message) {
|
|
3
4
|
super(message);
|
|
4
5
|
this.name = 'DigestModelRegistryError';
|
|
5
6
|
}
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
id:
|
|
8
|
+
const DIGEST_MODEL_REGISTRY = {
|
|
9
|
+
[DigestModelId.TINY]: {
|
|
10
|
+
id: DigestModelId.TINY,
|
|
10
11
|
ggufUri: 'hf:bartowski/Qwen2.5-0.5B-Instruct-GGUF/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf',
|
|
11
12
|
approxSizeBytes: 400_000_000,
|
|
12
13
|
license: 'Apache-2.0',
|
|
13
14
|
sourceUrl: 'https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF',
|
|
14
15
|
enabled: true,
|
|
15
16
|
},
|
|
16
|
-
|
|
17
|
-
id:
|
|
17
|
+
[DigestModelId.DEFAULT]: {
|
|
18
|
+
id: DigestModelId.DEFAULT,
|
|
18
19
|
ggufUri: 'hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf',
|
|
19
20
|
approxSizeBytes: 1_000_000_000,
|
|
20
21
|
license: 'Apache-2.0',
|
|
21
22
|
sourceUrl: 'https://huggingface.co/bartowski/Qwen2.5-1.5B-Instruct-GGUF',
|
|
22
23
|
enabled: true,
|
|
23
24
|
},
|
|
24
|
-
|
|
25
|
-
id:
|
|
25
|
+
[DigestModelId.LARGE]: {
|
|
26
|
+
id: DigestModelId.LARGE,
|
|
26
27
|
ggufUri: 'hf:bartowski/Qwen2.5-3B-Instruct-GGUF/Qwen2.5-3B-Instruct-Q4_K_M.gguf',
|
|
27
28
|
approxSizeBytes: 1_900_000_000,
|
|
28
29
|
license: 'Apache-2.0',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DigestFileOptions, DigestPendingCandidateSummary, DigestPendingResult, ProvisionalDigestResult, ConversationDigestInitResult, InitializeConversationDigestInput } from './types.js';
|
|
2
|
+
export declare function digestConversationFile(options: DigestFileOptions): Promise<ProvisionalDigestResult>;
|
|
3
|
+
export declare function summarizePendingConversations(workspaceRoot: string): DigestPendingCandidateSummary;
|
|
4
|
+
export declare function digestPendingConversations({ workspaceRoot, digestFn, onProgress, }: {
|
|
5
|
+
workspaceRoot: string;
|
|
6
|
+
digestFn?: (opts: DigestFileOptions) => Promise<ProvisionalDigestResult>;
|
|
7
|
+
onProgress?: (progress: {
|
|
8
|
+
processed: number;
|
|
9
|
+
skipped: number;
|
|
10
|
+
errors: number;
|
|
11
|
+
}) => void | Promise<void>;
|
|
12
|
+
}): Promise<DigestPendingResult>;
|
|
13
|
+
export declare function initializeConversationDigestModel(input: InitializeConversationDigestInput): Promise<ConversationDigestInitResult>;
|
|
@@ -4,13 +4,42 @@ import { DEFAULT_DIGEST_TIMEOUT_MS, DEFAULT_DIGEST_MAX_INPUT_CHARS, DIGEST_LOCKF
|
|
|
4
4
|
import { defaultProvisionalDigestDir, readConversationDigestInput, findExistingProvisionalDigest, writeProvisionalDigestFile, writeConversationDigestSettings, } from './files.js';
|
|
5
5
|
import { getDigestModelSpec } from './model-registry.js';
|
|
6
6
|
import { createLlamaLocalDigestProvider } from './providers/llama-local.js';
|
|
7
|
-
import { loadProjectSettings, resolveConversationDigestConfig, resolveMemoryConfig } from '../../
|
|
8
|
-
import { MemoryStore } from '../store.js';
|
|
9
|
-
import { MemoryIndexer } from '../indexer.js';
|
|
7
|
+
import { loadProjectSettings, resolveConversationDigestConfig, resolveMemoryConfig } from '../../core/config/index.js';
|
|
8
|
+
import { MemoryStore } from '../memory/store.js';
|
|
9
|
+
import { MemoryIndexer } from '../memory/indexer.js';
|
|
10
10
|
import { releaseLock, tryAcquireProcessLock } from '../../utils/files.js';
|
|
11
11
|
function digestLockPath(workspaceRoot) {
|
|
12
12
|
return path.join(workspaceRoot, DIGEST_LOCKFILE_REL_PATH);
|
|
13
13
|
}
|
|
14
|
+
function discoverPendingConversationFiles(workspaceRoot) {
|
|
15
|
+
const rawDir = path.join(workspaceRoot, WIKI_RAW_DIR);
|
|
16
|
+
let files;
|
|
17
|
+
try {
|
|
18
|
+
files = fs
|
|
19
|
+
.readdirSync(rawDir)
|
|
20
|
+
.filter((f) => f.startsWith('conv_') && f.endsWith('.md'))
|
|
21
|
+
.map((f) => path.join(rawDir, f))
|
|
22
|
+
.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err.code === 'ENOENT') {
|
|
26
|
+
files = [];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const outDir = defaultProvisionalDigestDir(workspaceRoot);
|
|
33
|
+
return files.filter((f) => {
|
|
34
|
+
try {
|
|
35
|
+
const input = readConversationDigestInput(workspaceRoot, f);
|
|
36
|
+
return !findExistingProvisionalDigest(outDir, input);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
14
43
|
async function indexProvisionalDigestFile(workspaceRoot, markdownPath) {
|
|
15
44
|
try {
|
|
16
45
|
const settings = loadProjectSettings(workspaceRoot);
|
|
@@ -63,7 +92,18 @@ export async function digestConversationFile(options) {
|
|
|
63
92
|
await provider.dispose?.();
|
|
64
93
|
}
|
|
65
94
|
}
|
|
66
|
-
export
|
|
95
|
+
export function summarizePendingConversations(workspaceRoot) {
|
|
96
|
+
const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
|
|
97
|
+
if (!digestConfig || digestConfig.enabled === false) {
|
|
98
|
+
return { initialized: false, pending: 0, reason: 'not-initialized' };
|
|
99
|
+
}
|
|
100
|
+
const candidates = discoverPendingConversationFiles(workspaceRoot);
|
|
101
|
+
if (candidates.length === 0) {
|
|
102
|
+
return { initialized: true, pending: 0, reason: 'no-pending' };
|
|
103
|
+
}
|
|
104
|
+
return { initialized: true, pending: candidates.length };
|
|
105
|
+
}
|
|
106
|
+
export async function digestPendingConversations({ workspaceRoot, digestFn = digestConversationFile, onProgress, }) {
|
|
67
107
|
const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot));
|
|
68
108
|
if (!digestConfig || digestConfig.enabled === false) {
|
|
69
109
|
return { ok: true, initialized: false, action: 'noop', reason: 'not-initialized' };
|
|
@@ -73,33 +113,7 @@ export async function digestPendingConversations({ workspaceRoot, digestFn = dig
|
|
|
73
113
|
return { ok: true, initialized: true, action: 'noop', reason: 'locked' };
|
|
74
114
|
}
|
|
75
115
|
try {
|
|
76
|
-
const
|
|
77
|
-
let files;
|
|
78
|
-
try {
|
|
79
|
-
files = fs
|
|
80
|
-
.readdirSync(rawDir)
|
|
81
|
-
.filter((f) => f.startsWith('conv_') && f.endsWith('.md'))
|
|
82
|
-
.map((f) => path.join(rawDir, f))
|
|
83
|
-
.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
if (err.code === 'ENOENT') {
|
|
87
|
-
files = [];
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
throw err;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const outDir = defaultProvisionalDigestDir(workspaceRoot);
|
|
94
|
-
const candidates = files.filter((f) => {
|
|
95
|
-
try {
|
|
96
|
-
const input = readConversationDigestInput(workspaceRoot, f);
|
|
97
|
-
return !findExistingProvisionalDigest(outDir, input);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
116
|
+
const candidates = discoverPendingConversationFiles(workspaceRoot);
|
|
103
117
|
if (candidates.length === 0) {
|
|
104
118
|
return { ok: true, initialized: true, action: 'noop', reason: 'no-pending' };
|
|
105
119
|
}
|
|
@@ -123,6 +137,9 @@ export async function digestPendingConversations({ workspaceRoot, digestFn = dig
|
|
|
123
137
|
catch {
|
|
124
138
|
errors++;
|
|
125
139
|
}
|
|
140
|
+
if (onProgress) {
|
|
141
|
+
await onProgress({ processed: count, skipped, errors });
|
|
142
|
+
}
|
|
126
143
|
}
|
|
127
144
|
return { ok: true, initialized: true, action: 'digested', count, skipped, errors };
|
|
128
145
|
}
|