om-memory-system 4.29.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/capture-attempt-store.d.ts +2 -0
- package/dist/services/capture-attempt-store.js +11 -0
- 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 +13 -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-CHBKLTiT.js +0 -126
- package/dist/web/assets/index-DsQXsY3T.css +0 -2
|
@@ -86,8 +86,13 @@ export async function matchImportSessions(identity, options, snapshotMode) {
|
|
|
86
86
|
const { runPathMaps } = await import("./import-path-maps.js");
|
|
87
87
|
const merged = { ...options, pathMaps: await runPathMaps(options.pathMaps, options.cwd) };
|
|
88
88
|
const rows = await readRows(identity, merged, snapshotMode);
|
|
89
|
-
const
|
|
90
|
-
|
|
89
|
+
const missing = rows.filter((row) => row.directory === null);
|
|
90
|
+
// `via` stays "unresolved", so a kept row still shows its folder is gone.
|
|
91
|
+
const usable = options.skillTurnsOnly
|
|
92
|
+
? rows.map((row) => (row.directory ? row : { ...row, directory: row.recordedDirectory }))
|
|
93
|
+
: rows;
|
|
94
|
+
const unresolved = usable.filter((row) => row.directory === null);
|
|
95
|
+
const matching = usable
|
|
91
96
|
.filter((row) => row.directory !== null)
|
|
92
97
|
.filter((row) => projectTag === null || projectFilterTag(row.directory) === projectTag)
|
|
93
98
|
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0) || a.key.localeCompare(b.key));
|
|
@@ -95,6 +100,7 @@ export async function matchImportSessions(identity, options, snapshotMode) {
|
|
|
95
100
|
identity,
|
|
96
101
|
matching,
|
|
97
102
|
unresolved,
|
|
103
|
+
missing,
|
|
98
104
|
revision: revisionOf(identity, merged, projectTag, matching.map((row) => row.key)),
|
|
99
105
|
};
|
|
100
106
|
}
|
|
@@ -138,7 +144,13 @@ export function validateSessionListRequest(value) {
|
|
|
138
144
|
throw new Error(`Invalid ${key}`);
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
|
-
for (const key of [
|
|
147
|
+
for (const key of [
|
|
148
|
+
"skipMemories",
|
|
149
|
+
"skipProfile",
|
|
150
|
+
"force",
|
|
151
|
+
"includeHandled",
|
|
152
|
+
"skillTurnsOnly",
|
|
153
|
+
]) {
|
|
142
154
|
if (input[key] !== undefined && typeof input[key] !== "boolean") {
|
|
143
155
|
throw new Error(`Invalid ${key}`);
|
|
144
156
|
}
|
|
@@ -157,6 +169,7 @@ export function validateSessionListRequest(value) {
|
|
|
157
169
|
pathMaps: maps,
|
|
158
170
|
outputs: { memories: input.skipMemories !== true, profile: input.skipProfile !== true },
|
|
159
171
|
...(force ? { force } : {}),
|
|
172
|
+
...(input.skillTurnsOnly === true ? { skillTurnsOnly: true } : {}),
|
|
160
173
|
},
|
|
161
174
|
};
|
|
162
175
|
}
|
|
@@ -174,7 +187,7 @@ export async function listImportSessions(request, options) {
|
|
|
174
187
|
const matched = await matchImportSessions(identity, options, request.refresh ? "fresh" : "any");
|
|
175
188
|
// Feed the Directory maps list; a failure here must not break the listing.
|
|
176
189
|
const { recordUnresolvedDirectories, summarizeUnresolvedDirectories } = await import("../services/backfill-state.js");
|
|
177
|
-
await recordUnresolvedDirectories(options.host, summarizeUnresolvedDirectories(matched.
|
|
190
|
+
await recordUnresolvedDirectories(options.host, summarizeUnresolvedDirectories(matched.missing.map((row) => ({ directory: row.recordedDirectory })))).catch(() => { });
|
|
178
191
|
const handled = await handledKeys(matched.matching, options.outputs ?? ALL_OUTPUTS);
|
|
179
192
|
const listed = [
|
|
180
193
|
...matched.matching.map((row) => ({ ...row, selectable: true, handled: handled.has(row.key) })),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { CaptureWorkUnit } from "../core/capture.js";
|
|
2
|
+
import type { MemoryHost } from "../types/index.js";
|
|
3
|
+
import type { ImportWindow } from "./importer.js";
|
|
4
|
+
/**
|
|
5
|
+
* The outputs of one imported turn (design decision 11). Skill turns run
|
|
6
|
+
* first for every candidate, before the memory ledger is read, so turns the
|
|
7
|
+
* ledger or live capture already handled still reach `skills.db`.
|
|
8
|
+
*/
|
|
9
|
+
/** Skill-turn counts on an import report. Present only when the run records skill turns. */
|
|
10
|
+
export interface ImportSkillTurnCounts {
|
|
11
|
+
/** Real run: turns written to `skills.db`. */
|
|
12
|
+
skillTurnsRecorded?: number;
|
|
13
|
+
/** Real run: turns `skills.db` already held. */
|
|
14
|
+
skillTurnsAlreadyPresent?: number;
|
|
15
|
+
/** Dry run: turns a real run would write. */
|
|
16
|
+
skillTurnsWouldRecord?: number;
|
|
17
|
+
/** Dry run: turns `skills.db` already holds. */
|
|
18
|
+
skillTurnsAlreadyRecorded?: number;
|
|
19
|
+
}
|
|
20
|
+
/** True when an import with these options records skill turns: `skillLearning` on and no `--skip-skills`. */
|
|
21
|
+
export declare function importSkillTurnsOn(options: {
|
|
22
|
+
skipSkills?: boolean;
|
|
23
|
+
}): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* True when a run writes skill turns and nothing else (`profile`: the run also
|
|
26
|
+
* learns the profile). Such a run also reads sessions whose project folder is
|
|
27
|
+
* gone; memory and profile runs still skip them.
|
|
28
|
+
*/
|
|
29
|
+
export declare function skillTurnsOnly(options: {
|
|
30
|
+
skipMemories?: boolean;
|
|
31
|
+
skipSkills?: boolean;
|
|
32
|
+
}, profile: boolean): boolean;
|
|
33
|
+
/** The capture work unit for one imported window, shared by the memory and skill outputs. */
|
|
34
|
+
export declare function importWorkUnit(host: MemoryHost, turn: {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
directory: string;
|
|
37
|
+
sourceFile: string;
|
|
38
|
+
window: ImportWindow;
|
|
39
|
+
}, importId: string): CaptureWorkUnit;
|
|
40
|
+
/**
|
|
41
|
+
* The skill output of one run, or undefined when the run records no skill
|
|
42
|
+
* turns. A real run records through `handleFinishedTurn`; a dry run only
|
|
43
|
+
* checks `turn_key` and never creates `skills.db`. Each project folder's
|
|
44
|
+
* settings are read once, with its project override applied.
|
|
45
|
+
*/
|
|
46
|
+
export declare function importSkillTurnOutput(options: {
|
|
47
|
+
skipSkills?: boolean;
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
}, report: ImportSkillTurnCounts): ((unit: CaptureWorkUnit) => Promise<void>) | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CONFIG } from "../config.js";
|
|
2
|
+
import { handleFinishedTurn } from "../core/turn-outputs.js";
|
|
3
|
+
import { skillTurnSettingsFor } from "../services/skill-learning/settings-for.js";
|
|
4
|
+
import { peekSkillTurn, recordSkillTurn, } from "../services/skill-learning/turn-output.js";
|
|
5
|
+
/** True when an import with these options records skill turns: `skillLearning` on and no `--skip-skills`. */
|
|
6
|
+
export function importSkillTurnsOn(options) {
|
|
7
|
+
return options.skipSkills !== true && CONFIG.skillLearning === true;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* True when a run writes skill turns and nothing else (`profile`: the run also
|
|
11
|
+
* learns the profile). Such a run also reads sessions whose project folder is
|
|
12
|
+
* gone; memory and profile runs still skip them.
|
|
13
|
+
*/
|
|
14
|
+
export function skillTurnsOnly(options, profile) {
|
|
15
|
+
return options.skipMemories === true && !profile && importSkillTurnsOn(options);
|
|
16
|
+
}
|
|
17
|
+
/** The capture work unit for one imported window, shared by the memory and skill outputs. */
|
|
18
|
+
export function importWorkUnit(host, turn, importId) {
|
|
19
|
+
const { window } = turn;
|
|
20
|
+
return {
|
|
21
|
+
host,
|
|
22
|
+
hostSessionId: turn.sessionId,
|
|
23
|
+
sourceType: "history-import",
|
|
24
|
+
projectDirectory: turn.directory,
|
|
25
|
+
userPrompt: window.userPrompt,
|
|
26
|
+
promptId: window.userEntryId,
|
|
27
|
+
textResponses: window.textResponses,
|
|
28
|
+
toolCalls: window.toolCalls,
|
|
29
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
30
|
+
...(window.userTimestamp !== undefined
|
|
31
|
+
? { sourceTimestamp: window.userTimestamp }
|
|
32
|
+
: window.sourceTimestamp !== undefined
|
|
33
|
+
? { sourceTimestamp: window.sourceTimestamp }
|
|
34
|
+
: {}),
|
|
35
|
+
sourceFile: turn.sourceFile,
|
|
36
|
+
importId,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The skill output of one run, or undefined when the run records no skill
|
|
41
|
+
* turns. A real run records through `handleFinishedTurn`; a dry run only
|
|
42
|
+
* checks `turn_key` and never creates `skills.db`. Each project folder's
|
|
43
|
+
* settings are read once, with its project override applied.
|
|
44
|
+
*/
|
|
45
|
+
export function importSkillTurnOutput(options, report) {
|
|
46
|
+
if (!importSkillTurnsOn(options))
|
|
47
|
+
return undefined;
|
|
48
|
+
const dryRun = Boolean(options.dryRun);
|
|
49
|
+
if (dryRun) {
|
|
50
|
+
report.skillTurnsWouldRecord ??= 0;
|
|
51
|
+
report.skillTurnsAlreadyRecorded ??= 0;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
report.skillTurnsRecorded ??= 0;
|
|
55
|
+
report.skillTurnsAlreadyPresent ??= 0;
|
|
56
|
+
}
|
|
57
|
+
const count = (status) => {
|
|
58
|
+
if (status === "new") {
|
|
59
|
+
if (dryRun)
|
|
60
|
+
report.skillTurnsWouldRecord++;
|
|
61
|
+
else
|
|
62
|
+
report.skillTurnsRecorded++;
|
|
63
|
+
}
|
|
64
|
+
else if (status === "duplicate") {
|
|
65
|
+
if (dryRun)
|
|
66
|
+
report.skillTurnsAlreadyRecorded++;
|
|
67
|
+
else
|
|
68
|
+
report.skillTurnsAlreadyPresent++;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const settings = new Map();
|
|
72
|
+
const settingsFor = (directory) => {
|
|
73
|
+
let found = settings.get(directory);
|
|
74
|
+
if (!found) {
|
|
75
|
+
found = skillTurnSettingsFor(directory);
|
|
76
|
+
settings.set(directory, found);
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
};
|
|
80
|
+
const output = dryRun ? peekSkillTurn : recordSkillTurn;
|
|
81
|
+
return async (unit) => {
|
|
82
|
+
const current = settingsFor(unit.projectDirectory);
|
|
83
|
+
if (!current.enabled)
|
|
84
|
+
return;
|
|
85
|
+
// handleFinishedTurn logs a failure as skill-record-failed, so one bad turn never stops the run.
|
|
86
|
+
await handleFinishedTurn(unit, { memories: false, skills: true }, { recordSkillTurn: async (turn) => count(await output(turn, current)) });
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CaptureConversation, CaptureSummaryProvider } from "../core/host.js";
|
|
2
2
|
import type { ModelPort } from "../core/profile-analysis.js";
|
|
3
3
|
import type { ProfileImportReport } from "./profile-import.js";
|
|
4
|
+
import { type ImportSkillTurnCounts } from "./import-turn-outputs.js";
|
|
4
5
|
import { PiImportLedger } from "./ledger.js";
|
|
5
6
|
import type { LoadedPiSession } from "./session-loader.js";
|
|
6
7
|
import type { MemoryHost } from "../types/index.js";
|
|
@@ -27,6 +28,8 @@ export interface ImportFilters {
|
|
|
27
28
|
dryRun?: boolean;
|
|
28
29
|
/** Record profile prompts only; leave memory work units untouched. */
|
|
29
30
|
skipMemories?: boolean;
|
|
31
|
+
/** Record no skill turns (design decision 11). */
|
|
32
|
+
skipSkills?: boolean;
|
|
30
33
|
pathMaps?: ImportPathMap[];
|
|
31
34
|
root?: string;
|
|
32
35
|
/**
|
|
@@ -54,7 +57,7 @@ export interface ImportProjectReport {
|
|
|
54
57
|
sessions: number;
|
|
55
58
|
units: number;
|
|
56
59
|
}
|
|
57
|
-
export interface ImportReport {
|
|
60
|
+
export interface ImportReport extends ImportSkillTurnCounts {
|
|
58
61
|
dryRun: boolean;
|
|
59
62
|
root: string;
|
|
60
63
|
sessionsDiscovered: number;
|
|
@@ -140,5 +143,5 @@ export interface LazyImportSource {
|
|
|
140
143
|
open: () => AsyncIterable<ImportSourceSession>;
|
|
141
144
|
}
|
|
142
145
|
/** Process host-labelled work units through the shared capture and ledger pipeline. */
|
|
143
|
-
export declare function importHistorySource(source: AsyncIterable<ImportSourceSession> | Iterable<ImportSourceSession> | LazyImportSource, host: MemoryHost, deps: Pick<ImporterDeps, "provider" | "ledger" | "onProgress" | "signal">, filters: Pick<ImportFilters, "dryRun" | "force" | "since" | "until">, report: ImportReport): Promise<void>;
|
|
146
|
+
export declare function importHistorySource(source: AsyncIterable<ImportSourceSession> | Iterable<ImportSourceSession> | LazyImportSource, host: MemoryHost, deps: Pick<ImporterDeps, "provider" | "ledger" | "onProgress" | "signal">, filters: Pick<ImportFilters, "dryRun" | "force" | "since" | "until" | "skipMemories" | "skipSkills">, report: ImportReport): Promise<void>;
|
|
144
147
|
export declare function importPiHistory(deps: ImporterDeps, filters: ImportFilters): Promise<ImportReport>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { handleFinishedTurn } from "../core/turn-outputs.js";
|
|
3
3
|
import { memoryClient } from "../services/client.js";
|
|
4
4
|
import { findLiveCapturedEntryIds } from "../services/live-captured-entries.js";
|
|
5
5
|
import { extractScopeFromContainerTag } from "../services/memory-scope.js";
|
|
@@ -8,7 +8,8 @@ import { tursoConnectionManager } from "../services/turso/connection-manager.js"
|
|
|
8
8
|
import { tursoShardManager } from "../services/turso/shard-manager.js";
|
|
9
9
|
import { extractPiConversationWindows, } from "./pi-conversation.js";
|
|
10
10
|
import { discoverPiSessions } from "./discovery.js";
|
|
11
|
-
import {
|
|
11
|
+
import { importDirectory } from "./import-project.js";
|
|
12
|
+
import { importSkillTurnOutput, importSkillTurnsOn, importWorkUnit, skillTurnsOnly, } from "./import-turn-outputs.js";
|
|
12
13
|
import { PiImportLedger, importLedgerDbPath } from "./ledger.js";
|
|
13
14
|
export function buildImportKey(sessionId, window, host = "pi") {
|
|
14
15
|
const terminalEntryId = window.sourceEntryIds?.[window.sourceEntryIds.length - 1];
|
|
@@ -106,13 +107,14 @@ async function lookupExistingMemory(projectHash, key, options) {
|
|
|
106
107
|
/** Process host-labelled work units through the shared capture and ledger pipeline. */
|
|
107
108
|
export async function importHistorySource(source, host, deps, filters, report) {
|
|
108
109
|
const dryRun = Boolean(filters.dryRun);
|
|
110
|
+
const memories = !filters.skipMemories;
|
|
111
|
+
const recordSkills = importSkillTurnOutput(filters, report);
|
|
109
112
|
const ledger = deps.ledger ?? new PiImportLedger();
|
|
110
113
|
const ledgerFileExists = existsSync(importLedgerDbPath());
|
|
111
114
|
const ledgerAccessible = !dryRun || ledgerFileExists;
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
if (!dryRun && memories) {
|
|
116
|
+
// A dry run only reads the ledger (peek), so it never writes its schema.
|
|
114
117
|
await ledger.get("__warmup__");
|
|
115
|
-
if (!dryRun) {
|
|
116
118
|
const embeddingInitError = memoryClient.getEmbeddingInitError?.();
|
|
117
119
|
if (embeddingInitError) {
|
|
118
120
|
throw new Error(`Memory system is not ready for import: ${embeddingInitError}. Start the extension and retry.`);
|
|
@@ -146,7 +148,7 @@ export async function importHistorySource(source, host, deps, filters, report) {
|
|
|
146
148
|
for await (const candidate of candidates) {
|
|
147
149
|
if (deps.signal?.aborted)
|
|
148
150
|
break;
|
|
149
|
-
const { sessionId,
|
|
151
|
+
const { sessionId, sourceFile, hash, window } = candidate;
|
|
150
152
|
const key = buildImportKey(sessionId, window, host);
|
|
151
153
|
processed++;
|
|
152
154
|
deps.onProgress?.(processed, report.unitsTotal, preview(window.userPrompt), report.unitsAlreadyHandled);
|
|
@@ -157,6 +159,11 @@ export async function importHistorySource(source, host, deps, filters, report) {
|
|
|
157
159
|
promptPreview: preview(window.userPrompt),
|
|
158
160
|
status: "would-import",
|
|
159
161
|
};
|
|
162
|
+
const workUnit = importWorkUnit(host, candidate, key);
|
|
163
|
+
await recordSkills?.(workUnit);
|
|
164
|
+
// A skills-only unit leaves the memory ledger untouched.
|
|
165
|
+
if (!memories)
|
|
166
|
+
continue;
|
|
160
167
|
const existing = !ledgerAccessible
|
|
161
168
|
? null
|
|
162
169
|
: dryRun
|
|
@@ -203,31 +210,9 @@ export async function importHistorySource(source, host, deps, filters, report) {
|
|
|
203
210
|
report.units.push(unit);
|
|
204
211
|
continue;
|
|
205
212
|
}
|
|
206
|
-
await ledger.begin({
|
|
207
|
-
key,
|
|
208
|
-
sessionId: sessionId,
|
|
209
|
-
sourceFile: sourceFile,
|
|
210
|
-
projectHash: hash,
|
|
211
|
-
});
|
|
213
|
+
await ledger.begin({ key, sessionId, sourceFile, projectHash: hash });
|
|
212
214
|
try {
|
|
213
|
-
const result = await
|
|
214
|
-
host,
|
|
215
|
-
hostSessionId: sessionId,
|
|
216
|
-
sourceType: "history-import",
|
|
217
|
-
projectDirectory: directory,
|
|
218
|
-
userPrompt: window.userPrompt,
|
|
219
|
-
promptId: window.userEntryId,
|
|
220
|
-
textResponses: window.textResponses,
|
|
221
|
-
toolCalls: window.toolCalls,
|
|
222
|
-
sourceEntryIds: window.sourceEntryIds,
|
|
223
|
-
...(window.userTimestamp !== undefined
|
|
224
|
-
? { sourceTimestamp: window.userTimestamp }
|
|
225
|
-
: window.sourceTimestamp !== undefined
|
|
226
|
-
? { sourceTimestamp: window.sourceTimestamp }
|
|
227
|
-
: {}),
|
|
228
|
-
sourceFile: sourceFile,
|
|
229
|
-
importId: key,
|
|
230
|
-
}, deps.provider);
|
|
215
|
+
const result = await handleFinishedTurn(workUnit, { memories: true, skills: false }, { provider: deps.provider });
|
|
231
216
|
if (result.status === "skipped") {
|
|
232
217
|
await ledger.skip(key, `extractor-skip:${result.type ?? "unknown"}`);
|
|
233
218
|
unit.status = "skipped";
|
|
@@ -282,6 +267,7 @@ export async function importPiHistory(deps, filters) {
|
|
|
282
267
|
const selected = filters.selectionKeys ? new Set(filters.selectionKeys) : null;
|
|
283
268
|
const projectAggregates = new Map();
|
|
284
269
|
const candidates = [];
|
|
270
|
+
const keepMissing = skillTurnsOnly(filters, Boolean(deps.profile));
|
|
285
271
|
for (const discovered of discovery.sessions) {
|
|
286
272
|
if (deps.signal?.aborted)
|
|
287
273
|
break;
|
|
@@ -316,7 +302,7 @@ export async function importPiHistory(deps, filters) {
|
|
|
316
302
|
});
|
|
317
303
|
continue;
|
|
318
304
|
}
|
|
319
|
-
const directory =
|
|
305
|
+
const directory = importDirectory(session.cwd, pathMaps, keepMissing);
|
|
320
306
|
if (!directory) {
|
|
321
307
|
report.unresolvableSessions.push({ file: session.sourceFile, cwd: session.cwd });
|
|
322
308
|
continue;
|
|
@@ -369,7 +355,7 @@ export async function importPiHistory(deps, filters) {
|
|
|
369
355
|
}
|
|
370
356
|
}
|
|
371
357
|
const sourceSessions = [...grouped.values()];
|
|
372
|
-
if (filters.skipMemories) {
|
|
358
|
+
if (filters.skipMemories && !importSkillTurnsOn(filters)) {
|
|
373
359
|
report.unitsTotal = candidates.length;
|
|
374
360
|
}
|
|
375
361
|
else {
|
|
@@ -23,6 +23,8 @@ export interface OpencodeImportOptions {
|
|
|
23
23
|
force?: boolean;
|
|
24
24
|
skipMemories?: boolean;
|
|
25
25
|
skipProfile?: boolean;
|
|
26
|
+
/** Record no skill turns. */
|
|
27
|
+
skipSkills?: boolean;
|
|
26
28
|
profileBatch?: number;
|
|
27
29
|
provider?: CaptureSummaryProvider;
|
|
28
30
|
profileModel?: ModelPort;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getTags } from "../services/tags.js";
|
|
2
2
|
import { extractScopeFromContainerTag } from "../services/memory-scope.js";
|
|
3
3
|
import { countImportableUnits, importHistorySource, projectFilterTag, selectImportWindows, } from "./importer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { importDirectory } from "./import-project.js";
|
|
5
|
+
import { importSkillTurnsOn, skillTurnsOnly } from "./import-turn-outputs.js";
|
|
5
6
|
import { DEFAULT_OPENCODE_DB, openOpencodeHistory, } from "./opencode-reader.js";
|
|
6
7
|
import { importProfileFromHistory } from "./profile-import.js";
|
|
7
8
|
export { DEFAULT_OPENCODE_DB };
|
|
@@ -32,8 +33,9 @@ export async function importOpencodeHistory(options) {
|
|
|
32
33
|
const projects = new Map();
|
|
33
34
|
const selected = [];
|
|
34
35
|
let filtered = 0;
|
|
36
|
+
const keepMissing = skillTurnsOnly(options, options.skipProfile !== true);
|
|
35
37
|
for (const session of history.sessions) {
|
|
36
|
-
const
|
|
38
|
+
const directory = importDirectory(session.recordedDirectory, options.pathMaps ?? [], keepMissing, session.projectWorktree);
|
|
37
39
|
if (!directory) {
|
|
38
40
|
const entry = unresolved.get(session.recordedDirectory) ?? {
|
|
39
41
|
directory: session.recordedDirectory,
|
|
@@ -120,7 +122,8 @@ export async function importOpencodeHistory(options) {
|
|
|
120
122
|
childSessionsFolded: history.childSessions,
|
|
121
123
|
unresolvedProjects: [...unresolved.values()],
|
|
122
124
|
};
|
|
123
|
-
|
|
125
|
+
// The memory path or skill turns read every turn; profile-only runs only count them.
|
|
126
|
+
if (!options.skipMemories || importSkillTurnsOn(options)) {
|
|
124
127
|
await importHistorySource({ total, open }, "opencode", {
|
|
125
128
|
provider: options.provider ?? {
|
|
126
129
|
summarize: async () => {
|
|
@@ -3,6 +3,8 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { boundToolInput, readToolPath } from "../core/tool-input.js";
|
|
7
|
+
import { opencodeToolOutcome } from "../core/tool-outcome.js";
|
|
6
8
|
import { INTERNAL_CAPTURE_SESSION_TITLES } from "./opencode-internal-sessions.js";
|
|
7
9
|
import { acquirePrivateSnapshot, opencodeSnapshots, OpencodeSnapshotError, } from "./opencode-snapshot.js";
|
|
8
10
|
export const DEFAULT_OPENCODE_DB = join(homedir(), ".local/share/opencode/opencode.db");
|
|
@@ -37,9 +39,8 @@ function parseData(json) {
|
|
|
37
39
|
return {};
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
return text.length > 100 ? `${text.slice(0, 100)}...` : text;
|
|
42
|
+
function formatInput(value) {
|
|
43
|
+
return typeof value === "string" ? value : JSON.stringify(value ?? "");
|
|
43
44
|
}
|
|
44
45
|
function buildWindows(db, sessionId, filters) {
|
|
45
46
|
const messages = db
|
|
@@ -98,9 +99,14 @@ function buildWindows(db, sessionId, filters) {
|
|
|
98
99
|
const state = part.state && typeof part.state === "object"
|
|
99
100
|
? part.state
|
|
100
101
|
: {};
|
|
102
|
+
const name = String(part.tool ?? part.name ?? part.type);
|
|
103
|
+
const args = state.input ?? part.input ?? "";
|
|
104
|
+
const path = readToolPath(name, args);
|
|
101
105
|
current.toolCalls.push({
|
|
102
|
-
name
|
|
103
|
-
input:
|
|
106
|
+
name,
|
|
107
|
+
input: boundToolInput(formatInput(args)),
|
|
108
|
+
...opencodeToolOutcome(state),
|
|
109
|
+
...(path === undefined ? {} : { path }),
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
112
|
}
|
|
@@ -11,10 +11,15 @@ export interface PiSessionEntry {
|
|
|
11
11
|
message?: {
|
|
12
12
|
role?: string;
|
|
13
13
|
content?: string | PiContentBlock[];
|
|
14
|
+
/** Set on `toolResult` messages: the `id` of the matching `toolCall` block. */
|
|
15
|
+
toolCallId?: string;
|
|
16
|
+
/** Set on `toolResult` messages. */
|
|
17
|
+
isError?: boolean;
|
|
14
18
|
};
|
|
15
19
|
}
|
|
16
20
|
export interface PiContentBlock {
|
|
17
21
|
type: string;
|
|
22
|
+
id?: string;
|
|
18
23
|
text?: string;
|
|
19
24
|
thinking?: string;
|
|
20
25
|
name?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { boundToolInput, readToolPath } from "../core/tool-input.js";
|
|
2
|
+
import { errorOutcome } from "../core/tool-outcome.js";
|
|
2
3
|
function entryText(content) {
|
|
3
4
|
if (typeof content === "string")
|
|
4
5
|
return content;
|
|
@@ -16,6 +17,24 @@ function isUserEntry(entry) {
|
|
|
16
17
|
function isAssistantEntry(entry) {
|
|
17
18
|
return entry.type === "message" && entry.message?.role === "assistant";
|
|
18
19
|
}
|
|
20
|
+
function isToolResultEntry(entry) {
|
|
21
|
+
return entry.type === "message" && entry.message?.role === "toolResult";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set the outcome of the call a `toolResult` entry answers. Only the error
|
|
25
|
+
* text of a failed call is kept; a successful call's output is never stored.
|
|
26
|
+
*/
|
|
27
|
+
function applyToolResult(entry, callsById) {
|
|
28
|
+
const call = callsById.get(entry.message?.toolCallId ?? "");
|
|
29
|
+
if (!call)
|
|
30
|
+
return;
|
|
31
|
+
if (entry.message?.isError === true) {
|
|
32
|
+
Object.assign(call, errorOutcome(entryText(entry.message.content)));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
call.outcome = "ok";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
19
38
|
function formatToolInput(args) {
|
|
20
39
|
if (!args)
|
|
21
40
|
return "";
|
|
@@ -23,12 +42,6 @@ function formatToolInput(args) {
|
|
|
23
42
|
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
24
43
|
.join(", ");
|
|
25
44
|
}
|
|
26
|
-
function boundToolInput(input) {
|
|
27
|
-
if (input.length > MAX_TOOL_INPUT_LENGTH) {
|
|
28
|
-
return input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
|
|
29
|
-
}
|
|
30
|
-
return input;
|
|
31
|
-
}
|
|
32
45
|
/**
|
|
33
46
|
* Build the capture window for the user entry at `userIndex`: only that
|
|
34
47
|
* prompt's assistant/tool work, stopping at the next user message. Hidden
|
|
@@ -46,10 +59,13 @@ function buildWindow(entries, userIndex) {
|
|
|
46
59
|
const textResponses = [];
|
|
47
60
|
const toolCalls = [];
|
|
48
61
|
const sourceEntryIds = [];
|
|
62
|
+
const callsById = new Map();
|
|
49
63
|
let sourceTimestamp;
|
|
50
64
|
for (const entry of entries.slice(userIndex + 1)) {
|
|
51
65
|
if (isUserEntry(entry))
|
|
52
66
|
break;
|
|
67
|
+
if (isToolResultEntry(entry))
|
|
68
|
+
applyToolResult(entry, callsById);
|
|
53
69
|
if (!isAssistantEntry(entry))
|
|
54
70
|
continue;
|
|
55
71
|
if (typeof entry.id === "string")
|
|
@@ -69,10 +85,18 @@ function buildWindow(entries, userIndex) {
|
|
|
69
85
|
}
|
|
70
86
|
for (const block of blocks.filter((item) => item.type === "toolCall")) {
|
|
71
87
|
const name = typeof block.name === "string" ? block.name : "unknown";
|
|
72
|
-
|
|
88
|
+
// A call stays `unknown` until a `toolResult` entry answers it.
|
|
89
|
+
const call = {
|
|
73
90
|
name,
|
|
74
91
|
input: boundToolInput(formatToolInput(block.arguments)),
|
|
75
|
-
|
|
92
|
+
outcome: "unknown",
|
|
93
|
+
};
|
|
94
|
+
const path = readToolPath(name, block.arguments);
|
|
95
|
+
if (path !== undefined)
|
|
96
|
+
call.path = path;
|
|
97
|
+
toolCalls.push(call);
|
|
98
|
+
if (typeof block.id === "string")
|
|
99
|
+
callsById.set(block.id, call);
|
|
76
100
|
}
|
|
77
101
|
}
|
|
78
102
|
if (textResponses.length === 0 && toolCalls.length === 0)
|
|
@@ -14,7 +14,7 @@ export interface CatchUpState {
|
|
|
14
14
|
* newest run owns the record, and waits for the older run's current batch.
|
|
15
15
|
*/
|
|
16
16
|
export declare function catchUpLeaseHooks(signal?: AbortSignal, lease?: CatchUpLease): Promise<{
|
|
17
|
-
beforeBatch: () => Promise<import("../services/
|
|
17
|
+
beforeBatch: () => Promise<import("../services/turso/table-lease.js").BatchStart>;
|
|
18
18
|
afterBatch: () => Promise<void>;
|
|
19
19
|
release: () => Promise<void>;
|
|
20
20
|
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ImportHost } from "./import-args.js";
|
|
2
|
+
import type { ImportReadiness } from "./import-readiness.js";
|
|
3
|
+
import { type GroupImportRequest, type SettingsImportJobs } from "./web-import-jobs.js";
|
|
4
|
+
/** Hosts whose default history exists on this computer and whose reader loads. */
|
|
5
|
+
export declare function setUpHosts(readiness: ImportReadiness): ImportHost[];
|
|
6
|
+
/**
|
|
7
|
+
* The grouped request for every set-up host. Each host lists its default
|
|
8
|
+
* source now, so the selection is every session up to this moment, including
|
|
9
|
+
* sessions the memory ledger marks handled.
|
|
10
|
+
*/
|
|
11
|
+
export declare function recordPastTurnsRequest(cwd: string, readiness: ImportReadiness, options?: {
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}): Promise<GroupImportRequest>;
|
|
14
|
+
/** Start **Record past turns** in a job slot: the web app's own, or a new one in the terminal. */
|
|
15
|
+
export declare function startRecordPastTurns(jobs: SettingsImportJobs, cwd: string, readiness: ImportReadiness, options?: {
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
id: string;
|
|
19
|
+
host: ImportHost;
|
|
20
|
+
dryRun: boolean;
|
|
21
|
+
state: "failed" | "done" | "running" | "cancelling" | "cancelled";
|
|
22
|
+
sessions: number;
|
|
23
|
+
processed: number;
|
|
24
|
+
total: number;
|
|
25
|
+
report?: string;
|
|
26
|
+
summary?: ReturnType<typeof import("./run-import.js").summarizeHistoryImportReport>;
|
|
27
|
+
error?: string;
|
|
28
|
+
} | import("./web-import-group.js").GroupImportJob>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ImportJobError, } from "./web-import-jobs.js";
|
|
2
|
+
/**
|
|
3
|
+
* **Record past turns** (design decision 11): one skill-turns-only grouped
|
|
4
|
+
* import over every set-up host, all projects and every session, with no date
|
|
5
|
+
* limit. It runs through the grouped-import job, so the Memory page shows its
|
|
6
|
+
* progress, and it calls no model. Safe to run again: `turn_key` drops copies.
|
|
7
|
+
*/
|
|
8
|
+
const HOSTS = ["pi", "opencode", "claude-code"];
|
|
9
|
+
/** Hosts whose default history exists on this computer and whose reader loads. */
|
|
10
|
+
export function setUpHosts(readiness) {
|
|
11
|
+
return HOSTS.filter((host) => readiness.defaultSourceFound[host] && (host !== "pi" || readiness.piReader.available));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The grouped request for every set-up host. Each host lists its default
|
|
15
|
+
* source now, so the selection is every session up to this moment, including
|
|
16
|
+
* sessions the memory ledger marks handled.
|
|
17
|
+
*/
|
|
18
|
+
export async function recordPastTurnsRequest(cwd, readiness, options = {}) {
|
|
19
|
+
const { listImportSessions } = await import("./import-sessions.js");
|
|
20
|
+
const hosts = [];
|
|
21
|
+
for (const host of setUpHosts(readiness)) {
|
|
22
|
+
const page = await listImportSessions({ refresh: true, includeHandled: true, limit: 1 }, {
|
|
23
|
+
host,
|
|
24
|
+
scope: "all-projects",
|
|
25
|
+
pathMaps: [],
|
|
26
|
+
cwd,
|
|
27
|
+
outputs: { memories: false, profile: false },
|
|
28
|
+
// Matches the job's skills-only options, so sessions whose folder is gone are kept.
|
|
29
|
+
skillTurnsOnly: true,
|
|
30
|
+
});
|
|
31
|
+
const selection = {
|
|
32
|
+
mode: "all",
|
|
33
|
+
excludedKeys: [],
|
|
34
|
+
revision: page.revision,
|
|
35
|
+
listedAt: page.listedAt,
|
|
36
|
+
includeHandled: true,
|
|
37
|
+
};
|
|
38
|
+
hosts.push({
|
|
39
|
+
host,
|
|
40
|
+
source: page.source.sourceToken,
|
|
41
|
+
selection,
|
|
42
|
+
// Claude Code imports name the external API; this run never calls it.
|
|
43
|
+
...(host === "claude-code" ? { modelChoice: "external" } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
hosts,
|
|
48
|
+
options: {
|
|
49
|
+
skipMemories: true,
|
|
50
|
+
skipProfile: true,
|
|
51
|
+
scope: "all-projects",
|
|
52
|
+
...(options.dryRun ? { dryRun: true } : {}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Start **Record past turns** in a job slot: the web app's own, or a new one in the terminal. */
|
|
57
|
+
export async function startRecordPastTurns(jobs, cwd, readiness, options = {}) {
|
|
58
|
+
const request = await recordPastTurnsRequest(cwd, readiness, options);
|
|
59
|
+
if (request.hosts.length === 0) {
|
|
60
|
+
throw new ImportJobError("No Pi, OpenCode or Claude Code history was found on this computer");
|
|
61
|
+
}
|
|
62
|
+
return jobs.start(request, cwd);
|
|
63
|
+
}
|
|
@@ -66,6 +66,10 @@ export declare function summarizeHistoryImportReport(report: HistoryImportReport
|
|
|
66
66
|
unitsAlreadyHandled: number;
|
|
67
67
|
unitsHeldBack: number;
|
|
68
68
|
unitsUntimed: number;
|
|
69
|
+
skillTurnsRecorded: number;
|
|
70
|
+
skillTurnsAlreadyPresent: number;
|
|
71
|
+
skillTurnsWouldRecord: number;
|
|
72
|
+
skillTurnsAlreadyRecorded: number;
|
|
69
73
|
projects: number;
|
|
70
74
|
unresolved: number;
|
|
71
75
|
loadErrors: number;
|