om-memory-system 4.30.1 → 4.31.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/README.md +1 -0
- package/dist/adapters/opencode/conversation.js +22 -17
- package/dist/adapters/opencode/skills-tool.d.ts +3 -0
- package/dist/adapters/opencode/skills-tool.js +31 -0
- package/dist/adapters/pi/capture.js +39 -23
- package/dist/adapters/pi/extension.d.ts +1 -0
- package/dist/adapters/pi/extension.js +3 -0
- package/dist/adapters/pi/skills-tool.d.ts +3 -0
- package/dist/adapters/pi/skills-tool.js +34 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/skills-command.d.ts +12 -0
- package/dist/cli/skills-command.js +242 -0
- package/dist/cli/skills-record-past.d.ts +5 -0
- package/dist/cli/skills-record-past.js +85 -0
- package/dist/config-template.d.ts +1 -1
- package/dist/config-template.js +12 -0
- package/dist/config.d.ts +64 -48
- package/dist/config.js +28 -5
- package/dist/core/host.d.ts +7 -0
- package/dist/core/profile-backoff.d.ts +16 -8
- package/dist/core/profile-backoff.js +25 -16
- package/dist/core/skill-agent-modes.d.ts +4 -0
- package/dist/core/skill-agent-modes.js +145 -0
- package/dist/core/skill-operation-reply.d.ts +16 -0
- package/dist/core/skill-operation-reply.js +35 -0
- package/dist/core/skill-operations.d.ts +58 -0
- package/dist/core/skill-operations.js +296 -0
- package/dist/core/skills-tool.d.ts +90 -0
- package/dist/core/skills-tool.js +64 -0
- package/dist/core/tool-input.d.ts +7 -0
- package/dist/core/tool-input.js +21 -0
- package/dist/core/tool-outcome.d.ts +13 -0
- package/dist/core/tool-outcome.js +22 -0
- package/dist/core/turn-outputs.d.ts +30 -0
- package/dist/core/turn-outputs.js +22 -0
- package/dist/importer/auto-backfill.js +13 -1
- package/dist/importer/claude-conversation.d.ts +1 -0
- package/dist/importer/claude-conversation.js +44 -10
- package/dist/importer/claude-hook-api.d.ts +0 -8
- package/dist/importer/claude-hook-api.js +36 -65
- package/dist/importer/claude-import.js +3 -1
- package/dist/importer/claude-reader.d.ts +2 -0
- package/dist/importer/claude-reader.js +2 -2
- package/dist/importer/claude-turn-units.d.ts +26 -0
- package/dist/importer/claude-turn-units.js +85 -0
- package/dist/importer/import-args.d.ts +3 -0
- package/dist/importer/import-args.js +8 -2
- package/dist/importer/import-project.d.ts +6 -0
- package/dist/importer/import-project.js +9 -0
- package/dist/importer/import-runs.d.ts +8 -2
- package/dist/importer/import-runs.js +3 -3
- package/dist/importer/import-sessions.d.ts +8 -1
- package/dist/importer/import-sessions.js +17 -4
- package/dist/importer/import-turn-outputs.d.ts +49 -0
- package/dist/importer/import-turn-outputs.js +88 -0
- package/dist/importer/importer.d.ts +5 -2
- package/dist/importer/importer.js +18 -32
- package/dist/importer/opencode-import.d.ts +2 -0
- package/dist/importer/opencode-import.js +6 -3
- package/dist/importer/opencode-reader.js +11 -5
- package/dist/importer/pi-conversation.d.ts +5 -0
- package/dist/importer/pi-conversation.js +33 -9
- package/dist/importer/profile-catch-up.d.ts +1 -1
- package/dist/importer/record-past-turns.d.ts +28 -0
- package/dist/importer/record-past-turns.js +63 -0
- package/dist/importer/run-import.d.ts +4 -0
- package/dist/importer/run-import.js +24 -3
- package/dist/importer/web-import-api.d.ts +14 -0
- package/dist/importer/web-import-api.js +6 -0
- package/dist/importer/web-import-group.d.ts +4 -1
- package/dist/importer/web-import-group.js +26 -6
- package/dist/importer/web-import-jobs.js +6 -2
- package/dist/index.js +6 -1
- package/dist/services/ai/session/ai-session-manager.js +13 -0
- package/dist/services/auto-capture.d.ts +1 -1
- package/dist/services/auto-capture.js +71 -19
- package/dist/services/chat/chat-runtime.d.ts +2 -0
- package/dist/services/chat/chat-runtime.js +3 -2
- package/dist/services/global-config-writer.js +32 -2
- package/dist/services/profile-cleanup-routes.d.ts +2 -2
- package/dist/services/profile-cleanup-routes.js +19 -2
- package/dist/services/settings-snapshot.js +7 -1
- package/dist/services/skill-learning/agent-proposals.d.ts +76 -0
- package/dist/services/skill-learning/agent-proposals.js +180 -0
- package/dist/services/skill-learning/apply.d.ts +50 -0
- package/dist/services/skill-learning/apply.js +247 -0
- package/dist/services/skill-learning/auto-review.d.ts +40 -0
- package/dist/services/skill-learning/auto-review.js +132 -0
- package/dist/services/skill-learning/draft-turns.d.ts +60 -0
- package/dist/services/skill-learning/draft-turns.js +131 -0
- package/dist/services/skill-learning/drafting.d.ts +73 -0
- package/dist/services/skill-learning/drafting.js +263 -0
- package/dist/services/skill-learning/line-diff.d.ts +6 -0
- package/dist/services/skill-learning/line-diff.js +94 -0
- package/dist/services/skill-learning/operations-deps.d.ts +7 -0
- package/dist/services/skill-learning/operations-deps.js +19 -0
- package/dist/services/skill-learning/proposal-store.d.ts +82 -0
- package/dist/services/skill-learning/proposal-store.js +164 -0
- package/dist/services/skill-learning/ranking.d.ts +36 -0
- package/dist/services/skill-learning/ranking.js +140 -0
- package/dist/services/skill-learning/settings-for.d.ts +11 -0
- package/dist/services/skill-learning/settings-for.js +21 -0
- package/dist/services/skill-learning/skill-config.d.ts +45 -0
- package/dist/services/skill-learning/skill-config.js +70 -0
- package/dist/services/skill-learning/skill-detect.d.ts +29 -0
- package/dist/services/skill-learning/skill-detect.js +130 -0
- package/dist/services/skill-learning/skill-folders.d.ts +24 -0
- package/dist/services/skill-learning/skill-folders.js +42 -0
- package/dist/services/skill-learning/skill-index.d.ts +28 -0
- package/dist/services/skill-learning/skill-index.js +118 -0
- package/dist/services/skill-learning/skill-model.d.ts +10 -0
- package/dist/services/skill-learning/skill-model.js +15 -0
- package/dist/services/skill-learning/store.d.ts +79 -0
- package/dist/services/skill-learning/store.js +245 -0
- package/dist/services/skill-learning/turn-output.d.ts +34 -0
- package/dist/services/skill-learning/turn-output.js +98 -0
- package/dist/services/skill-learning/turn-summaries.d.ts +24 -0
- package/dist/services/skill-learning/turn-summaries.js +64 -0
- package/dist/services/skill-learning/web-review.d.ts +18 -0
- package/dist/services/skill-learning/web-review.js +44 -0
- package/dist/services/skill-routes.d.ts +20 -0
- package/dist/services/skill-routes.js +249 -0
- package/dist/services/turso/connection-manager.js +11 -7
- package/dist/services/turso/table-lease.d.ts +40 -0
- package/dist/services/turso/table-lease.js +77 -0
- package/dist/services/turso/turso-db.d.ts +4 -0
- package/dist/services/turso/turso-db.js +13 -0
- package/dist/services/user-prompt/profile-catch-up-lease.d.ts +3 -22
- package/dist/services/user-prompt/profile-catch-up-lease.js +9 -65
- package/dist/services/web-jobs.d.ts +7 -0
- package/dist/services/web-jobs.js +14 -0
- package/dist/services/web-route-modules.d.ts +2 -2
- package/dist/services/web-route-modules.js +6 -1
- package/dist/services/web-server.js +9 -21
- package/dist/v2/adapter.js +13 -8
- package/dist/web/assets/index-9hu4Z7SV.css +2 -0
- package/dist/web/assets/index-BMt9gPNK.js +129 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BGDKFzoC.css +0 -2
- package/dist/web/assets/index-CE8tXPA_.js +0 -128
|
@@ -68,6 +68,8 @@ export async function scheduleAutoBackfill(options) {
|
|
|
68
68
|
force: false,
|
|
69
69
|
skipMemories: false,
|
|
70
70
|
skipProfile: false,
|
|
71
|
+
// Skill turns follow the same cutoff and sessions as memories.
|
|
72
|
+
skipSkills: !CONFIG.skillLearning,
|
|
71
73
|
scope: "all-projects",
|
|
72
74
|
pathMaps: [],
|
|
73
75
|
until: cutoff,
|
|
@@ -89,8 +91,18 @@ export async function scheduleAutoBackfill(options) {
|
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
91
93
|
if (!current.pending) {
|
|
94
|
+
// No memory work, but turns to record: a skill-turns pass needs no model.
|
|
95
|
+
const skillTurnsOnly = Boolean(dry.skillTurnsWouldRecord);
|
|
96
|
+
if (skillTurnsOnly) {
|
|
97
|
+
await run(options.host, { ...args, dryRun: false, skipMemories: true, skipProfile: true }, {
|
|
98
|
+
cwd: options.cwd,
|
|
99
|
+
models: {},
|
|
100
|
+
signal: controller.signal,
|
|
101
|
+
track: { surface: options.surface ?? "auto", lockHeld: true },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
92
104
|
// A pressed Run now needs an answer on the page: record a finished run.
|
|
93
|
-
if (options.userStarted) {
|
|
105
|
+
if (options.userStarted && !skillTurnsOnly) {
|
|
94
106
|
await startImportRun(options.host, options.surface ?? "web")
|
|
95
107
|
.then((recorder) => recorder.finish("done", {}))
|
|
96
108
|
.catch((error) => log("Backfill run record write failed", {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { boundToolInput, readToolPath } from "../core/tool-input.js";
|
|
2
|
+
import { errorOutcome } from "../core/tool-outcome.js";
|
|
1
3
|
/**
|
|
2
4
|
* Entry types seen in real transcripts. A line with any other type is skipped
|
|
3
5
|
* and counted, so a new Claude Code version shows up in the import report.
|
|
@@ -35,7 +37,6 @@ const NOISE_PREFIXES = [
|
|
|
35
37
|
"<bash-stderr>",
|
|
36
38
|
"<task-notification>",
|
|
37
39
|
];
|
|
38
|
-
const MAX_TOOL_INPUT_LENGTH = 100;
|
|
39
40
|
/** Parse one transcript line. A JSON value without a string `type` is unreadable. */
|
|
40
41
|
export function parseClaudeTranscriptLine(line) {
|
|
41
42
|
if (!line.trim())
|
|
@@ -121,12 +122,6 @@ function formatToolInput(input) {
|
|
|
121
122
|
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
122
123
|
.join(", ");
|
|
123
124
|
}
|
|
124
|
-
function boundToolInput(input) {
|
|
125
|
-
if (input.length > MAX_TOOL_INPUT_LENGTH) {
|
|
126
|
-
return input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
|
|
127
|
-
}
|
|
128
|
-
return input;
|
|
129
|
-
}
|
|
130
125
|
function closeWindow(open) {
|
|
131
126
|
if (open.textResponses.length === 0 && open.toolCalls.length === 0)
|
|
132
127
|
return null;
|
|
@@ -153,10 +148,45 @@ function addAssistantWork(open, entry) {
|
|
|
153
148
|
if (text)
|
|
154
149
|
open.textResponses.push(text);
|
|
155
150
|
for (const block of blocks.filter((item) => item.type === "tool_use")) {
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
// A call stays `unknown` until a `tool_result` block answers it.
|
|
152
|
+
const name = typeof block.name === "string" ? block.name : "unknown";
|
|
153
|
+
const call = {
|
|
154
|
+
name,
|
|
158
155
|
input: boundToolInput(formatToolInput(block.input)),
|
|
159
|
-
|
|
156
|
+
outcome: "unknown",
|
|
157
|
+
};
|
|
158
|
+
const path = readToolPath(name, block.input);
|
|
159
|
+
if (path !== undefined)
|
|
160
|
+
call.path = path;
|
|
161
|
+
open.toolCalls.push(call);
|
|
162
|
+
if (typeof block.id === "string")
|
|
163
|
+
open.callsById.set(block.id, call);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Text of a `tool_result` block: a string, or an array of text blocks. */
|
|
167
|
+
function toolResultText(content) {
|
|
168
|
+
if (typeof content === "string")
|
|
169
|
+
return content;
|
|
170
|
+
if (!Array.isArray(content))
|
|
171
|
+
return "";
|
|
172
|
+
return content
|
|
173
|
+
.filter((block) => block && block.type === "text" && typeof block.text === "string")
|
|
174
|
+
.map((block) => block.text)
|
|
175
|
+
.join("\n");
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Set the outcome of each call a user entry's `tool_result` blocks answer.
|
|
179
|
+
* Only the error text of a failed call is kept; tool output is never stored.
|
|
180
|
+
*/
|
|
181
|
+
function addToolResults(open, entry) {
|
|
182
|
+
for (const block of blocksOf(entry).filter((item) => item.type === "tool_result")) {
|
|
183
|
+
const call = open.callsById.get(block.tool_use_id ?? "");
|
|
184
|
+
if (!call)
|
|
185
|
+
continue;
|
|
186
|
+
if (block.is_error === true)
|
|
187
|
+
Object.assign(call, errorOutcome(toolResultText(block.content)));
|
|
188
|
+
else
|
|
189
|
+
call.outcome = "ok";
|
|
160
190
|
}
|
|
161
191
|
}
|
|
162
192
|
function windowsWithIndex(entries) {
|
|
@@ -180,6 +210,7 @@ function windowsWithIndex(entries) {
|
|
|
180
210
|
textResponses: [],
|
|
181
211
|
toolCalls: [],
|
|
182
212
|
sourceEntryIds: [entry.uuid],
|
|
213
|
+
callsById: new Map(),
|
|
183
214
|
};
|
|
184
215
|
}
|
|
185
216
|
return;
|
|
@@ -187,6 +218,9 @@ function windowsWithIndex(entries) {
|
|
|
187
218
|
if (open && entry.type === "assistant" && entry.isSidechain !== true) {
|
|
188
219
|
addAssistantWork(open, entry);
|
|
189
220
|
}
|
|
221
|
+
if (open && entry.type === "user" && entry.isSidechain !== true) {
|
|
222
|
+
addToolResults(open, entry);
|
|
223
|
+
}
|
|
190
224
|
});
|
|
191
225
|
flush();
|
|
192
226
|
return result;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CaptureSummaryProvider } from "../core/host.js";
|
|
2
2
|
import type { ModelPort } from "../core/profile-analysis.js";
|
|
3
|
-
import { type ClaudeConversationWindow, type ClaudeTranscriptEntry } from "./claude-conversation.js";
|
|
4
3
|
export interface ClaudeHookDeps {
|
|
5
4
|
/** The capture model. Defaults to the external API. */
|
|
6
5
|
captureProvider?: () => CaptureSummaryProvider;
|
|
@@ -34,13 +33,6 @@ export declare function handleClaudeCapture(body: unknown, deps?: ClaudeHookDeps
|
|
|
34
33
|
};
|
|
35
34
|
/** Resolves when every queued capture has finished. */
|
|
36
35
|
export declare function whenClaudeCaptureIdle(): Promise<void>;
|
|
37
|
-
/**
|
|
38
|
-
* The windows after the cursor. The transcript file can lag the hook, so the
|
|
39
|
-
* hook's final text is added as a last assistant reply when the transcript
|
|
40
|
-
* does not already end with it. It carries no entry id, so `sourceEntryIds`
|
|
41
|
-
* stay the reader's ids.
|
|
42
|
-
*/
|
|
43
|
-
export declare function claudeWindowsToCapture(entries: ClaudeTranscriptEntry[], cursor: string | null, finalText: string | undefined): ClaudeConversationWindow[];
|
|
44
36
|
/**
|
|
45
37
|
* Register the Claude Code retry drain in this web app and start one retry
|
|
46
38
|
* pass. The drain uses the external API. When capture is off, the first
|
|
@@ -2,9 +2,9 @@ import { realpathSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
4
|
import { CONFIG, refreshConfigIfChanged } from "../config.js";
|
|
5
|
-
import { captureConversation } from "../core/capture.js";
|
|
6
5
|
import { classifyCaptureFailure, quickRetryDelayMs } from "../core/capture-retry-policy.js";
|
|
7
|
-
import { buildRecentMemoriesSection, buildRetrievalSection, formatMemoriesForCompaction, retrievalWrapperBytes,
|
|
6
|
+
import { buildRecentMemoriesSection, buildRetrievalSection, formatMemoriesForCompaction, retrievalWrapperBytes, wrapRetrievalSection, } from "../core/retrieval.js";
|
|
7
|
+
import { handleFinishedTurn } from "../core/turn-outputs.js";
|
|
8
8
|
import { DEFAULT_RETRIEVAL_MAX_TOKENS, tokensToByteCeiling } from "../core/context-budget.js";
|
|
9
9
|
import { resolveClaudeCodeLiveModel } from "../services/ai/live-model-choice.js";
|
|
10
10
|
import { captureFailureOf, drainCaptureRetries, queueFailedCapture, registerCaptureRetryDrain, startCaptureRetryDrain, } from "../services/capture-retry-drain.js";
|
|
@@ -12,9 +12,11 @@ import { readClaudeCaptureCursor, writeClaudeCaptureCursor, } from "../services/
|
|
|
12
12
|
import { memoryClient } from "../services/client.js";
|
|
13
13
|
import { log } from "../services/logger.js";
|
|
14
14
|
import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
|
|
15
|
+
import { skillTurnRecorder } from "../services/skill-learning/settings-for.js";
|
|
15
16
|
import { getTags } from "../services/tags.js";
|
|
16
|
-
import {
|
|
17
|
+
import { parseClaudeTranscript } from "./claude-conversation.js";
|
|
17
18
|
import { learnClaudeProfile } from "./claude-profile-learning.js";
|
|
19
|
+
import { claudeWindowsToCapture, recordClaudeSkillTurns, sessionDirectory, skillUnitOf, workUnitOf, } from "./claude-turn-units.js";
|
|
18
20
|
import { defaultClaudeProjectsRoot } from "./claude-reader.js";
|
|
19
21
|
// The web app's side of the Claude Code hooks (design decisions 4, 6, 7):
|
|
20
22
|
// retrieval for SessionStart and UserPromptSubmit, a one-worker capture queue
|
|
@@ -251,63 +253,13 @@ async function readTranscript(request) {
|
|
|
251
253
|
}
|
|
252
254
|
return parsed.entries;
|
|
253
255
|
}
|
|
254
|
-
/**
|
|
255
|
-
* The windows after the cursor. The transcript file can lag the hook, so the
|
|
256
|
-
* hook's final text is added as a last assistant reply when the transcript
|
|
257
|
-
* does not already end with it. It carries no entry id, so `sourceEntryIds`
|
|
258
|
-
* stay the reader's ids.
|
|
259
|
-
*/
|
|
260
|
-
export function claudeWindowsToCapture(entries, cursor, finalText) {
|
|
261
|
-
const tail = finalText?.trim();
|
|
262
|
-
if (!tail)
|
|
263
|
-
return extractClaudeWindowsAfter(entries, cursor);
|
|
264
|
-
const hookReply = {
|
|
265
|
-
type: "assistant",
|
|
266
|
-
message: { role: "assistant", content: [{ type: "text", text: tail }] },
|
|
267
|
-
};
|
|
268
|
-
const windows = extractClaudeWindowsAfter([...entries, hookReply], cursor);
|
|
269
|
-
const last = windows.at(-1);
|
|
270
|
-
const replies = last?.textResponses ?? [];
|
|
271
|
-
if (replies.length > 1 && replies[replies.length - 2].trim().endsWith(tail))
|
|
272
|
-
replies.pop();
|
|
273
|
-
return windows;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* The project a session belongs to: the first prompt's directory, the same rule
|
|
277
|
-
* the history import uses. Live capture must file a turn under the project the
|
|
278
|
-
* import would choose, or the import cannot see that the turn is already saved.
|
|
279
|
-
*/
|
|
280
|
-
function sessionDirectory(entries, fallback) {
|
|
281
|
-
const first = entries.find((entry) => entry.type === "user" && entry.isSidechain !== true);
|
|
282
|
-
return typeof first?.cwd === "string" && first.cwd ? first.cwd : fallback;
|
|
283
|
-
}
|
|
284
|
-
/** A cleaned work unit, or null for a prompt with nothing left to capture. */
|
|
285
|
-
function workUnitOf(request, window, directory) {
|
|
286
|
-
const prompt = stripRetrievalSections(window.userPrompt);
|
|
287
|
-
if (!prompt || isFullyPrivate(prompt))
|
|
288
|
-
return null;
|
|
289
|
-
return {
|
|
290
|
-
host: HOST,
|
|
291
|
-
hostSessionId: request.sessionId,
|
|
292
|
-
sourceType: "live-capture",
|
|
293
|
-
projectDirectory: directory,
|
|
294
|
-
userPrompt: stripPrivateContent(prompt),
|
|
295
|
-
promptId: window.userEntryId,
|
|
296
|
-
textResponses: window.textResponses.map(stripPrivateContent),
|
|
297
|
-
toolCalls: window.toolCalls.map((call) => ({
|
|
298
|
-
name: call.name,
|
|
299
|
-
input: stripPrivateContent(call.input),
|
|
300
|
-
})),
|
|
301
|
-
sourceEntryIds: window.sourceEntryIds,
|
|
302
|
-
...(window.sourceTimestamp !== undefined ? { sourceTimestamp: window.sourceTimestamp } : {}),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
256
|
/** Try the turn up to `autoCaptureMaxRetries` times, as the other hosts do. */
|
|
306
|
-
async function captureWithQuickRetries(unit, provider, sleep) {
|
|
257
|
+
async function captureWithQuickRetries(unit, provider, sleep, recordSkillTurn) {
|
|
307
258
|
const maxRetries = Math.max(1, CONFIG.autoCaptureMaxRetries ?? 3);
|
|
308
259
|
for (let attempt = 1;; attempt++) {
|
|
309
260
|
try {
|
|
310
|
-
|
|
261
|
+
const skills = recordSkillTurn !== undefined;
|
|
262
|
+
return await handleFinishedTurn(unit, { memories: true, skills }, { provider, recordSkillTurn });
|
|
311
263
|
}
|
|
312
264
|
catch (error) {
|
|
313
265
|
if (attempt >= maxRetries)
|
|
@@ -320,16 +272,26 @@ async function externalModels() {
|
|
|
320
272
|
const { selectImportModel } = await import("./model-selection.js");
|
|
321
273
|
return selectImportModel({});
|
|
322
274
|
}
|
|
275
|
+
/** Memory capture is on and ready. Logs why when it is not, as before. */
|
|
276
|
+
function memoryCaptureOn(request) {
|
|
277
|
+
if (!CONFIG.autoCaptureEnabled)
|
|
278
|
+
return false;
|
|
279
|
+
const live = resolveClaudeCodeLiveModel(CONFIG);
|
|
280
|
+
if (!live.ready)
|
|
281
|
+
reportCaptureOff(live.issues);
|
|
282
|
+
else if (memoryClient.getEmbeddingInitError?.())
|
|
283
|
+
skipped(request, "not-ready");
|
|
284
|
+
else
|
|
285
|
+
return true;
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
323
288
|
async function captureTurn(request, deps) {
|
|
324
289
|
const started = Date.now();
|
|
325
290
|
refreshConfigIfChanged(request.cwd);
|
|
326
|
-
|
|
291
|
+
const memories = memoryCaptureOn(request);
|
|
292
|
+
// Skill learning records turns even when memory capture is off (ADR-036).
|
|
293
|
+
if (!memories && !CONFIG.skillLearning)
|
|
327
294
|
return;
|
|
328
|
-
const live = resolveClaudeCodeLiveModel(CONFIG);
|
|
329
|
-
if (!live.ready)
|
|
330
|
-
return reportCaptureOff(live.issues);
|
|
331
|
-
if (memoryClient.getEmbeddingInitError?.())
|
|
332
|
-
return skipped(request, "not-ready");
|
|
333
295
|
const entries = await readTranscript(request);
|
|
334
296
|
if (!entries)
|
|
335
297
|
return;
|
|
@@ -342,29 +304,38 @@ async function captureTurn(request, deps) {
|
|
|
342
304
|
return;
|
|
343
305
|
}
|
|
344
306
|
const directory = sessionDirectory(entries, request.cwd);
|
|
307
|
+
const recorder = skillTurnRecorder(directory);
|
|
308
|
+
const recordOnly = (only) => recordClaudeSkillTurns(only, request.sessionId, directory, recorder);
|
|
309
|
+
if (!memories)
|
|
310
|
+
return recordOnly(windows);
|
|
345
311
|
let provider;
|
|
346
312
|
try {
|
|
347
313
|
provider = deps.captureProvider?.() ?? (await externalModels()).capture;
|
|
348
314
|
}
|
|
349
315
|
catch (error) {
|
|
350
|
-
|
|
316
|
+
skipped(request, "model-unavailable", { error: errorCode(error) });
|
|
317
|
+
return recordOnly(windows);
|
|
351
318
|
}
|
|
352
319
|
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
353
320
|
const counts = { captured: 0, skipped: 0, failed: 0, private: 0, queued: 0 };
|
|
354
321
|
let blocked = null;
|
|
355
322
|
for (const window of windows) {
|
|
356
|
-
const unit = workUnitOf(request, window, directory);
|
|
323
|
+
const unit = workUnitOf(request.sessionId, window, directory);
|
|
357
324
|
if (!unit) {
|
|
358
325
|
counts.private++;
|
|
326
|
+
await recordOnly([window]);
|
|
359
327
|
}
|
|
360
328
|
else if (blocked) {
|
|
361
329
|
// The model is out of reach: queue the rest of the turns without calling it.
|
|
330
|
+
await recordOnly([window]);
|
|
362
331
|
if (await queueFailedCapture(unit, blocked, CONFIG))
|
|
363
332
|
counts.queued++;
|
|
364
333
|
}
|
|
365
334
|
else {
|
|
366
335
|
try {
|
|
367
|
-
|
|
336
|
+
// Memory capture reads the cleaned unit; skill learning records the raw one.
|
|
337
|
+
const record = recorder && (() => recorder(skillUnitOf(request.sessionId, window, directory)));
|
|
338
|
+
const result = await captureWithQuickRetries(unit, provider, sleep, record);
|
|
368
339
|
counts[result.status]++;
|
|
369
340
|
await recordPrompt(request, unit, result.status === "captured" ? result.memoryId : null);
|
|
370
341
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { importHistorySource, } from "./importer.js";
|
|
2
2
|
import { openClaudeHistorySource } from "./claude-reader.js";
|
|
3
|
+
import { importSkillTurnsOn, skillTurnsOnly } from "./import-turn-outputs.js";
|
|
3
4
|
/**
|
|
4
5
|
* Import Claude Code history through the shared capture, ledger, and profile
|
|
5
6
|
* steps, with host `claude-code`. Mirrors `importPiHistory`: the same filters,
|
|
@@ -16,6 +17,7 @@ export async function importClaudeHistory(deps, filters) {
|
|
|
16
17
|
...(filters.maxSessions !== undefined ? { maxSessions: filters.maxSessions } : {}),
|
|
17
18
|
...(filters.pathMaps ? { pathMaps: filters.pathMaps } : {}),
|
|
18
19
|
...(filters.scope === "current-project" ? { project: filters.currentDirectory } : {}),
|
|
20
|
+
keepMissingFolders: skillTurnsOnly(filters, Boolean(deps.profile)),
|
|
19
21
|
...(deps.signal ? { signal: deps.signal } : {}),
|
|
20
22
|
}, deps.loadSession ? { loadSession: deps.loadSession } : {});
|
|
21
23
|
const report = {
|
|
@@ -42,7 +44,7 @@ export async function importClaudeHistory(deps, filters) {
|
|
|
42
44
|
unreadableLines: source.unreadableLines,
|
|
43
45
|
unknownTypes: source.unknownTypes,
|
|
44
46
|
};
|
|
45
|
-
if (filters.skipMemories) {
|
|
47
|
+
if (filters.skipMemories && !importSkillTurnsOn(filters)) {
|
|
46
48
|
report.unitsTotal = source.unitsKept;
|
|
47
49
|
}
|
|
48
50
|
else {
|
|
@@ -64,6 +64,8 @@ export interface ClaudeHistoryOptions {
|
|
|
64
64
|
pathMaps?: ImportPathMap[];
|
|
65
65
|
/** Keep only sessions of this project directory (current-project scope). */
|
|
66
66
|
project?: string;
|
|
67
|
+
/** A skills-only run keeps a session whose folder is gone, under its recorded path. */
|
|
68
|
+
keepMissingFolders?: boolean;
|
|
67
69
|
signal?: AbortSignal;
|
|
68
70
|
}
|
|
69
71
|
export interface ClaudeHistoryDeps {
|
|
@@ -6,7 +6,7 @@ import { claudeProjectsRoot } from "../services/claude-folder.js";
|
|
|
6
6
|
import { extractScopeFromContainerTag } from "../services/memory-scope.js";
|
|
7
7
|
import { getTags } from "../services/tags.js";
|
|
8
8
|
import { extractClaudeConversationWindows, parseClaudeTranscript, parseClaudeTranscriptLine, } from "./claude-conversation.js";
|
|
9
|
-
import {
|
|
9
|
+
import { importDirectory } from "./import-project.js";
|
|
10
10
|
import { countImportableUnits, projectFilterTag, selectImportWindows, } from "./importer.js";
|
|
11
11
|
/**
|
|
12
12
|
* Discovery and loading of Claude Code transcripts for the history import.
|
|
@@ -213,7 +213,7 @@ export async function openClaudeHistorySource(options = {}, deps = {}) {
|
|
|
213
213
|
source.sessionsFilteredOut++;
|
|
214
214
|
continue;
|
|
215
215
|
}
|
|
216
|
-
const
|
|
216
|
+
const directory = importDirectory(discovered.cwd, options.pathMaps ?? [], options.keepMissingFolders === true);
|
|
217
217
|
if (!directory) {
|
|
218
218
|
source.unresolvableSessions.push({ file: discovered.file, cwd: discovered.cwd });
|
|
219
219
|
continue;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CaptureWorkUnit } from "../core/capture.js";
|
|
2
|
+
import type { SkillTurnRecorder } from "../services/skill-learning/settings-for.js";
|
|
3
|
+
import { type ClaudeConversationWindow, type ClaudeTranscriptEntry } from "./claude-conversation.js";
|
|
4
|
+
/**
|
|
5
|
+
* The windows after the cursor. The transcript file can lag the hook, so the
|
|
6
|
+
* hook's final text is added as a last assistant reply when the transcript
|
|
7
|
+
* does not already end with it. It carries no entry id, so `sourceEntryIds`
|
|
8
|
+
* stay the reader's ids.
|
|
9
|
+
*/
|
|
10
|
+
export declare function claudeWindowsToCapture(entries: ClaudeTranscriptEntry[], cursor: string | null, finalText: string | undefined): ClaudeConversationWindow[];
|
|
11
|
+
/**
|
|
12
|
+
* The project a session belongs to: the first prompt's directory, the same rule
|
|
13
|
+
* the history import uses. Live capture must file a turn under the project the
|
|
14
|
+
* import would choose, or the import cannot see that the turn is already saved.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sessionDirectory(entries: ClaudeTranscriptEntry[], fallback: string): string;
|
|
17
|
+
/** A cleaned work unit, or null for a prompt with nothing left to capture. */
|
|
18
|
+
export declare function workUnitOf(sessionId: string, window: ClaudeConversationWindow, directory: string): CaptureWorkUnit | null;
|
|
19
|
+
/**
|
|
20
|
+
* The unit skill learning records: OMMS's injected memories removed, nothing
|
|
21
|
+
* else. Private text and secrets are the skill output's job, by its setting,
|
|
22
|
+
* and the tool calls keep their outcome, error and path.
|
|
23
|
+
*/
|
|
24
|
+
export declare function skillUnitOf(sessionId: string, window: ClaudeConversationWindow, directory: string): CaptureWorkUnit;
|
|
25
|
+
/** Records windows that memory capture does not run for. Never moves the capture cursor. */
|
|
26
|
+
export declare function recordClaudeSkillTurns(windows: ClaudeConversationWindow[], sessionId: string, directory: string, recorder: SkillTurnRecorder | undefined): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { stripRetrievalSections } from "../core/retrieval.js";
|
|
2
|
+
import { handleFinishedTurn } from "../core/turn-outputs.js";
|
|
3
|
+
import { isFullyPrivate, stripPrivateContent } from "../services/privacy.js";
|
|
4
|
+
import { extractClaudeWindowsAfter, } from "./claude-conversation.js";
|
|
5
|
+
// Work units for the Claude Code Stop hook (`claude-hook-api.ts`): the cleaned
|
|
6
|
+
// unit memory capture reads, and the raw unit skill learning records.
|
|
7
|
+
const HOST = "claude-code";
|
|
8
|
+
/**
|
|
9
|
+
* The windows after the cursor. The transcript file can lag the hook, so the
|
|
10
|
+
* hook's final text is added as a last assistant reply when the transcript
|
|
11
|
+
* does not already end with it. It carries no entry id, so `sourceEntryIds`
|
|
12
|
+
* stay the reader's ids.
|
|
13
|
+
*/
|
|
14
|
+
export function claudeWindowsToCapture(entries, cursor, finalText) {
|
|
15
|
+
const tail = finalText?.trim();
|
|
16
|
+
if (!tail)
|
|
17
|
+
return extractClaudeWindowsAfter(entries, cursor);
|
|
18
|
+
const hookReply = {
|
|
19
|
+
type: "assistant",
|
|
20
|
+
message: { role: "assistant", content: [{ type: "text", text: tail }] },
|
|
21
|
+
};
|
|
22
|
+
const windows = extractClaudeWindowsAfter([...entries, hookReply], cursor);
|
|
23
|
+
const last = windows.at(-1);
|
|
24
|
+
const replies = last?.textResponses ?? [];
|
|
25
|
+
if (replies.length > 1 && replies[replies.length - 2].trim().endsWith(tail))
|
|
26
|
+
replies.pop();
|
|
27
|
+
return windows;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The project a session belongs to: the first prompt's directory, the same rule
|
|
31
|
+
* the history import uses. Live capture must file a turn under the project the
|
|
32
|
+
* import would choose, or the import cannot see that the turn is already saved.
|
|
33
|
+
*/
|
|
34
|
+
export function sessionDirectory(entries, fallback) {
|
|
35
|
+
const first = entries.find((entry) => entry.type === "user" && entry.isSidechain !== true);
|
|
36
|
+
return typeof first?.cwd === "string" && first.cwd ? first.cwd : fallback;
|
|
37
|
+
}
|
|
38
|
+
/** A cleaned work unit, or null for a prompt with nothing left to capture. */
|
|
39
|
+
export function workUnitOf(sessionId, window, directory) {
|
|
40
|
+
const prompt = stripRetrievalSections(window.userPrompt);
|
|
41
|
+
if (!prompt || isFullyPrivate(prompt))
|
|
42
|
+
return null;
|
|
43
|
+
return {
|
|
44
|
+
host: HOST,
|
|
45
|
+
hostSessionId: sessionId,
|
|
46
|
+
sourceType: "live-capture",
|
|
47
|
+
projectDirectory: directory,
|
|
48
|
+
userPrompt: stripPrivateContent(prompt),
|
|
49
|
+
promptId: window.userEntryId,
|
|
50
|
+
textResponses: window.textResponses.map(stripPrivateContent),
|
|
51
|
+
toolCalls: window.toolCalls.map((call) => ({
|
|
52
|
+
name: call.name,
|
|
53
|
+
input: stripPrivateContent(call.input),
|
|
54
|
+
})),
|
|
55
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
56
|
+
...(window.sourceTimestamp !== undefined ? { sourceTimestamp: window.sourceTimestamp } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The unit skill learning records: OMMS's injected memories removed, nothing
|
|
61
|
+
* else. Private text and secrets are the skill output's job, by its setting,
|
|
62
|
+
* and the tool calls keep their outcome, error and path.
|
|
63
|
+
*/
|
|
64
|
+
export function skillUnitOf(sessionId, window, directory) {
|
|
65
|
+
return {
|
|
66
|
+
host: HOST,
|
|
67
|
+
hostSessionId: sessionId,
|
|
68
|
+
sourceType: "live-capture",
|
|
69
|
+
projectDirectory: directory,
|
|
70
|
+
userPrompt: stripRetrievalSections(window.userPrompt),
|
|
71
|
+
promptId: window.userEntryId,
|
|
72
|
+
textResponses: window.textResponses,
|
|
73
|
+
toolCalls: window.toolCalls,
|
|
74
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
75
|
+
...(window.sourceTimestamp !== undefined ? { sourceTimestamp: window.sourceTimestamp } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Records windows that memory capture does not run for. Never moves the capture cursor. */
|
|
79
|
+
export async function recordClaudeSkillTurns(windows, sessionId, directory, recorder) {
|
|
80
|
+
if (!recorder)
|
|
81
|
+
return;
|
|
82
|
+
for (const window of windows) {
|
|
83
|
+
await handleFinishedTurn(skillUnitOf(sessionId, window, directory), { memories: false, skills: true }, { recordSkillTurn: recorder });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -14,6 +14,8 @@ export interface HistoryImportArgs {
|
|
|
14
14
|
force: boolean;
|
|
15
15
|
skipMemories: boolean;
|
|
16
16
|
skipProfile: boolean;
|
|
17
|
+
/** Record no skill turns. Skill turns are recorded only while `skillLearning` is on. */
|
|
18
|
+
skipSkills: boolean;
|
|
17
19
|
profileBatch?: number;
|
|
18
20
|
/** Session: `provider/id` from the host's signed-in models. CLI: a model id for `--provider`. */
|
|
19
21
|
model?: string;
|
|
@@ -49,6 +51,7 @@ export interface WebImportOptions {
|
|
|
49
51
|
force?: boolean;
|
|
50
52
|
skipMemories?: boolean;
|
|
51
53
|
skipProfile?: boolean;
|
|
54
|
+
skipSkills?: boolean;
|
|
52
55
|
model?: string;
|
|
53
56
|
scope?: HistoryImportArgs["scope"];
|
|
54
57
|
project?: string;
|
|
@@ -11,6 +11,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
11
11
|
"--force",
|
|
12
12
|
"--skip-memories",
|
|
13
13
|
"--skip-profile",
|
|
14
|
+
"--skip-skills",
|
|
14
15
|
]);
|
|
15
16
|
const VALUE_FLAGS = new Set([
|
|
16
17
|
"--profile-batch",
|
|
@@ -55,6 +56,7 @@ export function parseHistoryImportArgs(tokens, options) {
|
|
|
55
56
|
force: false,
|
|
56
57
|
skipMemories: false,
|
|
57
58
|
skipProfile: false,
|
|
59
|
+
skipSkills: false,
|
|
58
60
|
scope: "current-project",
|
|
59
61
|
pathMaps: [],
|
|
60
62
|
errors: [],
|
|
@@ -77,6 +79,8 @@ export function parseHistoryImportArgs(tokens, options) {
|
|
|
77
79
|
result.force = true;
|
|
78
80
|
else if (flag === "--skip-memories")
|
|
79
81
|
result.skipMemories = true;
|
|
82
|
+
else if (flag === "--skip-skills")
|
|
83
|
+
result.skipSkills = true;
|
|
80
84
|
else
|
|
81
85
|
result.skipProfile = true;
|
|
82
86
|
continue;
|
|
@@ -188,6 +192,7 @@ export function webImportTokens(options, _host) {
|
|
|
188
192
|
["force", "--force"],
|
|
189
193
|
["skipMemories", "--skip-memories"],
|
|
190
194
|
["skipProfile", "--skip-profile"],
|
|
195
|
+
["skipSkills", "--skip-skills"],
|
|
191
196
|
]) {
|
|
192
197
|
if (options[field])
|
|
193
198
|
tokens.push(flag);
|
|
@@ -244,8 +249,9 @@ ${model}
|
|
|
244
249
|
--max-sessions <n> Read at most n sessions, oldest first
|
|
245
250
|
--map <old>=<new> Map a recorded directory that no longer exists (repeatable)
|
|
246
251
|
${source}
|
|
247
|
-
--skip-memories
|
|
248
|
-
--skip-profile
|
|
252
|
+
--skip-memories Save no memories
|
|
253
|
+
--skip-profile Record no profile prompts
|
|
254
|
+
--skip-skills Record no skill turns (recorded only while skillLearning is on)
|
|
249
255
|
--profile-batch <n> Prompts per profile analysis batch (default: 50)
|
|
250
256
|
--force Reprocess already-handled memory units and profile prompts
|
|
251
257
|
--help Show this help
|
|
@@ -14,3 +14,9 @@ export interface ResolvedImportProject {
|
|
|
14
14
|
* of the same project.
|
|
15
15
|
*/
|
|
16
16
|
export declare function resolveImportProject(recordedDirectory: string | null, maps?: ImportPathMap[], projectWorktree?: string | null): ResolvedImportProject;
|
|
17
|
+
/**
|
|
18
|
+
* The folder an importer uses for one session. Skill turns need no project
|
|
19
|
+
* folder, so a skills-only run (`keepMissing`) keeps a session whose folder is
|
|
20
|
+
* gone under its recorded path. Every other run leaves it unresolved.
|
|
21
|
+
*/
|
|
22
|
+
export declare function importDirectory(recordedDirectory: string | null, maps: ImportPathMap[], keepMissing: boolean, projectWorktree?: string | null): string | null;
|
|
@@ -33,3 +33,12 @@ export function resolveImportProject(recordedDirectory, maps = [], projectWorktr
|
|
|
33
33
|
}
|
|
34
34
|
return { directory: null, via: "unresolved" };
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* The folder an importer uses for one session. Skill turns need no project
|
|
38
|
+
* folder, so a skills-only run (`keepMissing`) keeps a session whose folder is
|
|
39
|
+
* gone under its recorded path. Every other run leaves it unresolved.
|
|
40
|
+
*/
|
|
41
|
+
export function importDirectory(recordedDirectory, maps, keepMissing, projectWorktree = null) {
|
|
42
|
+
const { directory } = resolveImportProject(recordedDirectory, maps, projectWorktree);
|
|
43
|
+
return directory ?? (keepMissing ? recordedDirectory : null);
|
|
44
|
+
}
|
|
@@ -2,8 +2,12 @@ import type { BackfillHost } from "./backfill-model.js";
|
|
|
2
2
|
import { type ProgressEstimate } from "./import-progress.js";
|
|
3
3
|
export type ImportSurface = "auto" | "web" | "cli" | "slash";
|
|
4
4
|
export type ImportRunState = "running" | "paused" | "stopped" | "done" | "failed";
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* A run imports exchanges first, then learns the profile from the imported
|
|
7
|
+
* prompts. A run without memories that records skill turns starts in
|
|
8
|
+
* `skill-turns` instead of `exchanges`.
|
|
9
|
+
*/
|
|
10
|
+
export type ImportRunPhase = "exchanges" | "skill-turns" | "profile";
|
|
7
11
|
export interface ImportRunCounts {
|
|
8
12
|
total: number;
|
|
9
13
|
done: number;
|
|
@@ -48,4 +52,6 @@ export declare function startImportRun(host: BackfillHost, surface: ImportSurfac
|
|
|
48
52
|
intervalMs?: number;
|
|
49
53
|
sampleIntervalMs?: number;
|
|
50
54
|
onPause?: () => void;
|
|
55
|
+
/** The first phase; `exchanges` unless the run records skill turns only. */
|
|
56
|
+
phase?: Exclude<ImportRunPhase, "profile">;
|
|
51
57
|
}): Promise<ImportRunRecorder>;
|
|
@@ -67,7 +67,7 @@ export async function readImportRun(host) {
|
|
|
67
67
|
failed: Number(row.failed),
|
|
68
68
|
error: row.error === null ? null : String(row.error),
|
|
69
69
|
paused: Number(row.paused) === 1,
|
|
70
|
-
phase: row.phase === "profile"
|
|
70
|
+
phase: row.phase === "profile" || row.phase === "skill-turns" ? row.phase : "exchanges",
|
|
71
71
|
profileDone: Number(row.profile_done ?? 0),
|
|
72
72
|
profileTotal: Number(row.profile_total ?? 0),
|
|
73
73
|
...(state === "running"
|
|
@@ -102,8 +102,8 @@ export async function startImportRun(host, surface, options = {}) {
|
|
|
102
102
|
const startedAt = clock();
|
|
103
103
|
await db.run(`UPDATE import_runs SET surface = ?, state = 'running', pid = ?, started_at = ?, total = 0,
|
|
104
104
|
done = 0, imported = 0, skipped = 0, failed = 0, error = NULL, updated_at = ?, samples = '[]',
|
|
105
|
-
phase =
|
|
106
|
-
WHERE host = ?`, [surface, process.pid, startedAt, startedAt, host]);
|
|
105
|
+
phase = ?, profile_done = 0, profile_total = 0
|
|
106
|
+
WHERE host = ?`, [surface, process.pid, startedAt, startedAt, options.phase ?? "exchanges", host]);
|
|
107
107
|
let samples = [];
|
|
108
108
|
let lastWrite = Number.NEGATIVE_INFINITY;
|
|
109
109
|
let lastSample = Number.NEGATIVE_INFINITY;
|
|
@@ -39,6 +39,11 @@ export interface ImportMatchOptions {
|
|
|
39
39
|
outputs?: ImportOutputs;
|
|
40
40
|
/** A forced run re-analyses handled history, so it keeps handled sessions. */
|
|
41
41
|
force?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The run writes skill turns only. Skill turns need no project folder, so a
|
|
44
|
+
* session whose folder is gone matches under its recorded path.
|
|
45
|
+
*/
|
|
46
|
+
skillTurnsOnly?: boolean;
|
|
42
47
|
}
|
|
43
48
|
export declare class StaleSelectionError extends Error {
|
|
44
49
|
readonly status = 409;
|
|
@@ -48,8 +53,10 @@ interface Matched {
|
|
|
48
53
|
identity: ImportSourceIdentity;
|
|
49
54
|
/** Resolvable sessions in scope, newest first. */
|
|
50
55
|
matching: ImportSessionRow[];
|
|
51
|
-
/** Sessions
|
|
56
|
+
/** Sessions this run cannot use because their directory cannot be resolved, in any scope. */
|
|
52
57
|
unresolved: ImportSessionRow[];
|
|
58
|
+
/** Every session whose directory cannot be resolved; feeds the Directory maps list. */
|
|
59
|
+
missing: ImportSessionRow[];
|
|
53
60
|
revision: string;
|
|
54
61
|
}
|
|
55
62
|
export declare function matchImportSessions(identity: ImportSourceIdentity, options: ImportMatchOptions, snapshotMode: "fresh" | "reuse" | "any"): Promise<Matched>;
|