om-memory-system 3.2.0-next.9 → 3.3.0-next.18
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 +3 -5
- package/dist/adapters/opencode/auto-capture-summary.js +50 -8
- package/dist/adapters/opencode/backfill-models.d.ts +25 -0
- package/dist/adapters/opencode/backfill-models.js +39 -0
- package/dist/adapters/opencode/backfill-startup.d.ts +7 -0
- package/dist/adapters/opencode/backfill-startup.js +12 -0
- package/dist/adapters/opencode/import-command.d.ts +3 -7
- package/dist/adapters/opencode/import-command.js +6 -54
- package/dist/adapters/pi/backfill-models.d.ts +9 -0
- package/dist/adapters/pi/backfill-models.js +30 -0
- package/dist/adapters/pi/extension.js +53 -0
- package/dist/adapters/pi/import-command.js +3 -0
- package/dist/adapters/pi/profile.js +2 -1
- package/dist/adapters/pi/provider.js +41 -14
- package/dist/cli/index.js +13 -1
- package/dist/cli/web-command.d.ts +3 -0
- package/dist/cli/web-command.js +72 -0
- package/dist/config.d.ts +30 -1
- package/dist/config.js +131 -9
- package/dist/core/capture-context.js +1 -1
- package/dist/core/capture.d.ts +4 -0
- package/dist/core/capture.js +30 -0
- package/dist/core/extraction.d.ts +23 -1
- package/dist/core/extraction.js +41 -0
- package/dist/core/host.d.ts +23 -0
- package/dist/core/host.js +9 -1
- package/dist/core/profile-analysis.js +9 -1
- package/dist/importer/auto-backfill.d.ts +20 -0
- package/dist/importer/auto-backfill.js +165 -0
- package/dist/importer/backfill-lock.d.ts +3 -0
- package/dist/importer/backfill-lock.js +44 -0
- package/dist/importer/backfill-model.d.ts +10 -0
- package/dist/importer/backfill-model.js +13 -0
- package/dist/importer/discovery.d.ts +32 -5
- package/dist/importer/discovery.js +88 -36
- package/dist/importer/import-args.d.ts +23 -1
- package/dist/importer/import-args.js +29 -1
- package/dist/importer/import-project.d.ts +16 -0
- package/dist/importer/import-project.js +35 -0
- package/dist/importer/import-readiness.d.ts +47 -0
- package/dist/importer/import-readiness.js +54 -0
- package/dist/importer/import-sessions.d.ts +98 -0
- package/dist/importer/import-sessions.js +222 -0
- package/dist/importer/import-sources.d.ts +47 -0
- package/dist/importer/import-sources.js +147 -0
- package/dist/importer/importer.d.ts +31 -1
- package/dist/importer/importer.js +96 -24
- package/dist/importer/manual-import-guard.d.ts +4 -0
- package/dist/importer/manual-import-guard.js +9 -0
- package/dist/importer/opencode-import.d.ts +11 -1
- package/dist/importer/opencode-import.js +133 -69
- package/dist/importer/opencode-project.d.ts +1 -4
- package/dist/importer/opencode-project.js +3 -36
- package/dist/importer/opencode-reader.d.ts +25 -5
- package/dist/importer/opencode-reader.js +92 -106
- package/dist/importer/opencode-snapshot.d.ts +82 -0
- package/dist/importer/opencode-snapshot.js +276 -0
- package/dist/importer/profile-import.d.ts +1 -0
- package/dist/importer/profile-import.js +7 -1
- package/dist/importer/run-import.d.ts +10 -0
- package/dist/importer/run-import.js +21 -0
- package/dist/importer/settings-health.d.ts +24 -0
- package/dist/importer/settings-health.js +127 -0
- package/dist/importer/web-import-api.d.ts +8 -0
- package/dist/importer/web-import-api.js +8 -0
- package/dist/importer/web-import-jobs.d.ts +53 -0
- package/dist/importer/web-import-jobs.js +227 -0
- package/dist/index.js +34 -0
- package/dist/services/ai/live-model-choice.js +6 -3
- package/dist/services/ai/opencode-import-models.d.ts +11 -0
- package/dist/services/ai/opencode-import-models.js +55 -0
- package/dist/services/ai/opencode-provider.d.ts +9 -0
- package/dist/services/ai/opencode-provider.js +31 -4
- package/dist/services/ai/providers/anthropic-messages.js +14 -6
- package/dist/services/ai/providers/base-provider.d.ts +8 -0
- package/dist/services/ai/providers/base-provider.js +12 -0
- package/dist/services/ai/providers/google-gemini.js +21 -7
- package/dist/services/ai/providers/openai-chat-completion.js +20 -7
- package/dist/services/ai/providers/openai-responses.js +11 -0
- package/dist/services/backfill-state.d.ts +25 -0
- package/dist/services/backfill-state.js +68 -0
- package/dist/services/capture-attempt-store.d.ts +21 -0
- package/dist/services/capture-attempt-store.js +108 -0
- package/dist/services/capture-diagnostics.d.ts +66 -0
- package/dist/services/capture-diagnostics.js +175 -0
- package/dist/services/cleanup-service.js +9 -0
- package/dist/services/global-config-writer.d.ts +10 -0
- package/dist/services/global-config-writer.js +110 -0
- package/dist/services/live-captured-entries.d.ts +3 -0
- package/dist/services/live-captured-entries.js +34 -0
- package/dist/services/log-path.d.ts +2 -0
- package/dist/services/log-path.js +15 -0
- package/dist/services/logger.js +1 -12
- package/dist/services/safe-health-error.d.ts +2 -0
- package/dist/services/safe-health-error.js +11 -0
- package/dist/services/settings-log.d.ts +5 -0
- package/dist/services/settings-log.js +36 -0
- package/dist/services/settings-models.d.ts +35 -0
- package/dist/services/settings-models.js +39 -0
- package/dist/services/settings-snapshot.d.ts +24 -0
- package/dist/services/settings-snapshot.js +83 -0
- package/dist/services/settings-traces.d.ts +7 -0
- package/dist/services/settings-traces.js +39 -0
- package/dist/services/user-memory-learning.js +2 -1
- package/dist/services/web-autostart.d.ts +29 -0
- package/dist/services/web-autostart.js +208 -0
- package/dist/services/web-server.d.ts +5 -0
- package/dist/services/web-server.js +187 -3
- package/dist/v2/legacy-client.js +19 -2
- package/dist/web/assets/index-B-_gMSn9.js +124 -0
- package/dist/web/assets/index-D5ak5KsI.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/web/assets/index-DPjEpD1h.css +0 -2
- package/dist/web/assets/index-do26c4JJ.js +0 -122
package/README.md
CHANGED
|
@@ -26,8 +26,7 @@ the other. Everything is stored locally on your machine.
|
|
|
26
26
|
OpenCode v1.
|
|
27
27
|
- **Learns your preferences.** A user profile of your habits builds up over
|
|
28
28
|
time and follows you across projects.
|
|
29
|
-
- **Imports your past sessions.**
|
|
30
|
-
history into memories and a profile.
|
|
29
|
+
- **Imports your past sessions.** Pi and OpenCode import older history after startup. A command provides a preview and manual control.
|
|
31
30
|
- **Lets you look and edit.** A local web page shows every memory and your
|
|
32
31
|
profile.
|
|
33
32
|
- **Keeps private things private.** Text inside `<private>` tags is never
|
|
@@ -98,13 +97,12 @@ See [Configuration](docs/configuration.md#choosing-the-model).
|
|
|
98
97
|
### 3. Check it works
|
|
99
98
|
|
|
100
99
|
Work normally for a few turns, then open `http://127.0.0.1:4747` in your
|
|
101
|
-
browser
|
|
100
|
+
browser. OpenCode or the login web app serves this page. New memories appear on the timeline.
|
|
102
101
|
You can also ask the agent: "search memory for what we changed today".
|
|
103
102
|
|
|
104
103
|
## Import your past history
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
the agent:
|
|
105
|
+
Older sessions import automatically after a host starts. This makes model calls. To opt out, set `"autoBackfill": false` in `~/.config/omms/omms.jsonc` before starting. For a manual import or custom source, preview inside the agent:
|
|
108
106
|
|
|
109
107
|
```text
|
|
110
108
|
/memory-import-opencode-history --dry-run
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { CONFIG } from "../../config.js";
|
|
3
3
|
import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
|
|
4
4
|
import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
|
|
5
|
-
import { parseCaptureSummary } from "../../core/extraction.js";
|
|
5
|
+
import { classifyCaptureReply, normalizeStopReason, parseCaptureSummary, } from "../../core/extraction.js";
|
|
6
6
|
import { log } from "../../services/logger.js";
|
|
7
7
|
import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
|
|
8
8
|
function buildSystemPrompt(languageName) {
|
|
@@ -26,8 +26,19 @@ FORMAT:
|
|
|
26
26
|
SKIP if: greetings, casual chat, no code/decisions made
|
|
27
27
|
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
28
28
|
}
|
|
29
|
+
/** Reason code for an error thrown on the OpenCode host-model path. */
|
|
30
|
+
function hostModelFailureReason(error) {
|
|
31
|
+
if (error instanceof Error && error.name === "ZodError")
|
|
32
|
+
return "schema-mismatch";
|
|
33
|
+
if (String(error).includes("returned no structured output"))
|
|
34
|
+
return "empty-text";
|
|
35
|
+
return "call-error";
|
|
36
|
+
}
|
|
29
37
|
export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
30
38
|
let opencodeProviderError;
|
|
39
|
+
// Filled per path; when the host model falls back, the external API attempt
|
|
40
|
+
// overwrites it, so the record describes the call that decided the outcome.
|
|
41
|
+
const diagnostics = request.diagnostics ?? {};
|
|
31
42
|
const hostModel = resolveOpencodeHostModel(CONFIG);
|
|
32
43
|
if (hostModel) {
|
|
33
44
|
try {
|
|
@@ -40,6 +51,9 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
|
40
51
|
...hostModel,
|
|
41
52
|
prompt: request.prompt,
|
|
42
53
|
});
|
|
54
|
+
diagnostics.path = "host-model";
|
|
55
|
+
diagnostics.provider = providerID;
|
|
56
|
+
diagnostics.model = modelID;
|
|
43
57
|
if (!isProviderConnected(providerID)) {
|
|
44
58
|
throw new Error(`opencode provider '${providerID}' is not connected. Check your opencode provider configuration.`);
|
|
45
59
|
}
|
|
@@ -59,6 +73,8 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
|
59
73
|
tags: z.array(z.string()),
|
|
60
74
|
});
|
|
61
75
|
const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, z.toJSONSchema(schema));
|
|
76
|
+
diagnostics.systemPrompt = systemPrompt;
|
|
77
|
+
diagnostics.userPrompt = aiPrompt;
|
|
62
78
|
const result = await generateStructuredOutput({
|
|
63
79
|
client: v2Client,
|
|
64
80
|
providerID,
|
|
@@ -66,6 +82,12 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
|
66
82
|
systemPrompt,
|
|
67
83
|
userPrompt: aiPrompt,
|
|
68
84
|
schema,
|
|
85
|
+
onReply: (reply) => {
|
|
86
|
+
diagnostics.stopReason = normalizeStopReason(reply.finish);
|
|
87
|
+
diagnostics.blockTypes = reply.partTypes;
|
|
88
|
+
diagnostics.rawReply =
|
|
89
|
+
reply.structuredOutput === undefined ? "" : JSON.stringify(reply.structuredOutput);
|
|
90
|
+
},
|
|
69
91
|
});
|
|
70
92
|
return {
|
|
71
93
|
summary: result.summary,
|
|
@@ -75,6 +97,7 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
|
75
97
|
}
|
|
76
98
|
catch (error) {
|
|
77
99
|
opencodeProviderError = error;
|
|
100
|
+
diagnostics.failureReason = hostModelFailureReason(error);
|
|
78
101
|
log("auto-capture: opencode provider failed, falling back to external API", {
|
|
79
102
|
error: String(error),
|
|
80
103
|
});
|
|
@@ -134,19 +157,38 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
|
134
157
|
};
|
|
135
158
|
const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, toolSchema);
|
|
136
159
|
const captureSessionID = `auto-capture-${request.prompt?.id ?? request.sessionId}-${randomUUID()}`;
|
|
137
|
-
|
|
160
|
+
// Replace whatever the failed host-model attempt recorded.
|
|
161
|
+
Object.assign(diagnostics, {
|
|
162
|
+
path: "external-api",
|
|
163
|
+
provider: CONFIG.memoryProvider,
|
|
164
|
+
model: CONFIG.memoryModel,
|
|
165
|
+
systemPrompt,
|
|
166
|
+
userPrompt: aiPrompt,
|
|
167
|
+
stopReason: undefined,
|
|
168
|
+
blockTypes: undefined,
|
|
169
|
+
rawReply: undefined,
|
|
170
|
+
failureReason: undefined,
|
|
171
|
+
});
|
|
172
|
+
let result;
|
|
173
|
+
try {
|
|
174
|
+
result = await provider.executeToolCall(systemPrompt, aiPrompt, toolSchema, captureSessionID);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
diagnostics.failureReason = "call-error";
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
diagnostics.stopReason = normalizeStopReason(result.stopReason);
|
|
138
181
|
if (!result.success || !result.data) {
|
|
182
|
+
diagnostics.failureReason = diagnostics.stopReason === "length" ? "truncated" : "call-error";
|
|
139
183
|
throw new Error(result.error || "Failed to generate summary");
|
|
140
184
|
}
|
|
141
185
|
const rawReply = JSON.stringify(result.data);
|
|
186
|
+
diagnostics.rawReply = rawReply;
|
|
142
187
|
const summary = parseCaptureSummary(rawReply);
|
|
143
188
|
if (!summary) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// The reply can carry conversation content, so log only its size.
|
|
148
|
-
replyLength: rawReply.length,
|
|
149
|
-
});
|
|
189
|
+
// The capture pipeline logs the attempt's metadata; never the reply.
|
|
190
|
+
diagnostics.failureReason =
|
|
191
|
+
classifyCaptureReply({ text: rawReply, stopReason: result.stopReason }) ?? "schema-mismatch";
|
|
150
192
|
throw new Error("omms: OpenCode extraction returned an invalid summary payload");
|
|
151
193
|
}
|
|
152
194
|
return summary;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { selectImportModel } from "../../importer/model-selection.js";
|
|
2
|
+
import type { HistoryImportModels } from "../../importer/run-import.js";
|
|
3
|
+
import { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
|
|
4
|
+
interface BackfillConfig {
|
|
5
|
+
opencodeBackfillModel?: string;
|
|
6
|
+
opencodeProvider?: string;
|
|
7
|
+
opencodeModel?: string;
|
|
8
|
+
memoryProvider?: string;
|
|
9
|
+
memoryModel?: string;
|
|
10
|
+
memoryApiUrl?: string;
|
|
11
|
+
memoryApiKey?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface OpenCodeBackfillContext {
|
|
14
|
+
connected: string[];
|
|
15
|
+
directory: string;
|
|
16
|
+
configModel: () => Promise<string | null>;
|
|
17
|
+
makeModels?: typeof createOpencodeImportModels;
|
|
18
|
+
externalModels?: typeof selectImportModel;
|
|
19
|
+
}
|
|
20
|
+
/** Apply the live-model rule when inherit is selected; named models need sign-in. */
|
|
21
|
+
export declare function resolveOpencodeBackfillModels(config: BackfillConfig | undefined, context: OpenCodeBackfillContext): Promise<{
|
|
22
|
+
model: string;
|
|
23
|
+
models: HistoryImportModels;
|
|
24
|
+
}>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CONFIG } from "../../config.js";
|
|
2
|
+
import { parseBackfillModel } from "../../importer/backfill-model.js";
|
|
3
|
+
import { selectImportModel } from "../../importer/model-selection.js";
|
|
4
|
+
import { getAutoCaptureProviderStatus } from "../../services/ai/live-model-choice.js";
|
|
5
|
+
import { createOpencodeImportModels, } from "../../services/ai/opencode-import-models.js";
|
|
6
|
+
/** Apply the live-model rule when inherit is selected; named models need sign-in. */
|
|
7
|
+
export async function resolveOpencodeBackfillModels(config = CONFIG, context) {
|
|
8
|
+
const choice = parseBackfillModel(config, "opencode");
|
|
9
|
+
let ref;
|
|
10
|
+
if (choice === "inherit") {
|
|
11
|
+
const live = getAutoCaptureProviderStatus(config);
|
|
12
|
+
if (!live.ready)
|
|
13
|
+
throw new Error(`OpenCode backfill: ${live.issues.join("; ")}`);
|
|
14
|
+
if (live.mode === "manual") {
|
|
15
|
+
const external = (context.externalModels ?? selectImportModel)({});
|
|
16
|
+
return {
|
|
17
|
+
model: `${external.provider}/${external.modelId}`,
|
|
18
|
+
models: { capture: external.capture, profile: external.profile },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const selected = live.mode === "opencode"
|
|
22
|
+
? `${config.opencodeProvider}/${config.opencodeModel}`
|
|
23
|
+
: await context.configModel();
|
|
24
|
+
if (!selected)
|
|
25
|
+
throw new Error("OpenCode backfill: no configured default model");
|
|
26
|
+
const parsed = parseBackfillModel({ opencodeBackfillModel: selected }, "opencode");
|
|
27
|
+
if (parsed === "inherit")
|
|
28
|
+
throw new Error("OpenCode backfill: no configured default model");
|
|
29
|
+
ref = { providerID: parsed.provider, modelID: parsed.model };
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
ref = { providerID: choice.provider, modelID: choice.model };
|
|
33
|
+
}
|
|
34
|
+
if (!context.connected.includes(ref.providerID)) {
|
|
35
|
+
throw new Error(`OpenCode backfill provider ${ref.providerID} is not connected`);
|
|
36
|
+
}
|
|
37
|
+
const models = await (context.makeModels ?? createOpencodeImportModels)(ref, context.directory);
|
|
38
|
+
return { model: `${ref.providerID}/${ref.modelID}`, models };
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Start after connected providers load, without delaying plugin initialisation. */
|
|
2
|
+
export declare function startOpencodeBackfill(input: {
|
|
3
|
+
directory: string;
|
|
4
|
+
connected: string[];
|
|
5
|
+
configModel: () => Promise<string | null>;
|
|
6
|
+
notify: (message: string) => void;
|
|
7
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CONFIG } from "../../config.js";
|
|
2
|
+
import { scheduleAutoBackfill } from "../../importer/auto-backfill.js";
|
|
3
|
+
import { resolveOpencodeBackfillModels } from "./backfill-models.js";
|
|
4
|
+
/** Start after connected providers load, without delaying plugin initialisation. */
|
|
5
|
+
export function startOpencodeBackfill(input) {
|
|
6
|
+
return scheduleAutoBackfill({
|
|
7
|
+
host: "opencode",
|
|
8
|
+
cwd: input.directory,
|
|
9
|
+
resolveModels: () => resolveOpencodeBackfillModels(CONFIG, input),
|
|
10
|
+
notify: input.notify,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type OpencodeModelRef } from "../../services/ai/opencode-import-models.js";
|
|
2
|
+
export { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
|
|
3
|
+
export type { OpencodeModelRef } from "../../services/ai/opencode-import-models.js";
|
|
2
4
|
/**
|
|
3
5
|
* OpenCode command surface for the historical-session importer:
|
|
4
6
|
*
|
|
@@ -12,10 +14,6 @@ import { type HistoryImportModels } from "../../importer/run-import.js";
|
|
|
12
14
|
export declare const OPENCODE_IMPORT_COMMAND = "memory-import-opencode-history";
|
|
13
15
|
export declare const OPENCODE_IMPORT_DESCRIPTION = "Import historical OpenCode sessions into the shared memory store (try --dry-run first)";
|
|
14
16
|
export declare const OPENCODE_IMPORT_USAGE: string;
|
|
15
|
-
export interface OpencodeModelRef {
|
|
16
|
-
providerID: string;
|
|
17
|
-
modelID: string;
|
|
18
|
-
}
|
|
19
17
|
export interface OpencodeImportCommandInput {
|
|
20
18
|
argsText: string;
|
|
21
19
|
/** Session directory; relative paths and the default project resolve here. */
|
|
@@ -24,8 +22,6 @@ export interface OpencodeImportCommandInput {
|
|
|
24
22
|
sessionModel: () => Promise<OpencodeModelRef | null>;
|
|
25
23
|
notify?: (message: string) => void;
|
|
26
24
|
}
|
|
27
|
-
/** Capture and profile calls routed through OpenCode's structured-output sessions. */
|
|
28
|
-
export declare function createOpencodeImportModels(ref: OpencodeModelRef, directory: string): Promise<Required<HistoryImportModels>>;
|
|
29
25
|
/** Parse, run, and describe one import; always resolves to text for the session. */
|
|
30
26
|
export declare function runOpencodeImportCommand(input: OpencodeImportCommandInput): Promise<string>;
|
|
31
27
|
/** The model of the session's latest user message, from the V1 message list. */
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
|
|
1
|
+
import { isConfigured } from "../../config.js";
|
|
3
2
|
import { historyImportUsage, importNeedsModel, parseHistoryImportArgs, tokenizeImportArgs, } from "../../importer/import-args.js";
|
|
4
3
|
import { formatHistoryImportReport, summarizeHistoryImportReport, runHistoryImport, } from "../../importer/run-import.js";
|
|
4
|
+
import { createOpencodeImportModels, } from "../../services/ai/opencode-import-models.js";
|
|
5
|
+
export { createOpencodeImportModels } from "../../services/ai/opencode-import-models.js";
|
|
5
6
|
import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
|
|
7
|
+
import { beginManualImport } from "../../importer/manual-import-guard.js";
|
|
6
8
|
import { memoryClient } from "../../services/client.js";
|
|
7
9
|
import { log } from "../../services/logger.js";
|
|
8
10
|
/**
|
|
@@ -23,58 +25,6 @@ function splitModel(value) {
|
|
|
23
25
|
const separator = value.indexOf("/");
|
|
24
26
|
return { providerID: value.slice(0, separator), modelID: value.slice(separator + 1) };
|
|
25
27
|
}
|
|
26
|
-
/** Capture and profile calls routed through OpenCode's structured-output sessions. */
|
|
27
|
-
export async function createOpencodeImportModels(ref, directory) {
|
|
28
|
-
const { getV2Client, generateStructuredOutput } = await loadOpencodeProvider();
|
|
29
|
-
const client = getV2Client();
|
|
30
|
-
if (!client)
|
|
31
|
-
throw new Error("the OpenCode client is not ready; retry in a moment");
|
|
32
|
-
const { z } = await import("zod");
|
|
33
|
-
const { buildCaptureSystemPrompt, createUserProfileAnalysisSchema, parseCaptureSummary } = await import("../../core/extraction.js");
|
|
34
|
-
const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
|
|
35
|
-
const captureSchema = z.object({
|
|
36
|
-
summary: z.string().optional(),
|
|
37
|
-
type: z.string(),
|
|
38
|
-
tags: z.array(z.string()).optional(),
|
|
39
|
-
});
|
|
40
|
-
const capture = {
|
|
41
|
-
async summarize(request) {
|
|
42
|
-
const target = CONFIG.autoCaptureLanguage && CONFIG.autoCaptureLanguage !== "auto"
|
|
43
|
-
? CONFIG.autoCaptureLanguage
|
|
44
|
-
: detectLanguage(request.userPrompt);
|
|
45
|
-
const systemPrompt = buildCaptureSystemPrompt(getLanguageName(target));
|
|
46
|
-
const result = await generateStructuredOutput({
|
|
47
|
-
client,
|
|
48
|
-
...ref,
|
|
49
|
-
systemPrompt,
|
|
50
|
-
userPrompt: buildBoundedSummaryPrompt(request.context, systemPrompt, z.toJSONSchema(captureSchema)),
|
|
51
|
-
schema: captureSchema,
|
|
52
|
-
directory,
|
|
53
|
-
});
|
|
54
|
-
const parsed = parseCaptureSummary(JSON.stringify(result));
|
|
55
|
-
if (!parsed)
|
|
56
|
-
throw new Error("History capture returned an invalid summary");
|
|
57
|
-
return parsed;
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
const profileSchema = createUserProfileAnalysisSchema(z);
|
|
61
|
-
const profile = {
|
|
62
|
-
provider: ref.providerID,
|
|
63
|
-
modelId: ref.modelID,
|
|
64
|
-
async complete(systemPrompt, userPrompt) {
|
|
65
|
-
const result = await generateStructuredOutput({
|
|
66
|
-
client,
|
|
67
|
-
...ref,
|
|
68
|
-
systemPrompt,
|
|
69
|
-
userPrompt,
|
|
70
|
-
schema: profileSchema,
|
|
71
|
-
directory,
|
|
72
|
-
});
|
|
73
|
-
return JSON.stringify(result);
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
return { capture, profile };
|
|
77
|
-
}
|
|
78
28
|
/** Parse, run, and describe one import; always resolves to text for the session. */
|
|
79
29
|
export async function runOpencodeImportCommand(input) {
|
|
80
30
|
const name = OPENCODE_IMPORT_COMMAND;
|
|
@@ -93,6 +43,7 @@ export async function runOpencodeImportCommand(input) {
|
|
|
93
43
|
if (importCommandRunning)
|
|
94
44
|
return `${name}: an import is already running`;
|
|
95
45
|
importCommandRunning = true;
|
|
46
|
+
const endManualImport = beginManualImport("opencode");
|
|
96
47
|
try {
|
|
97
48
|
let ref = null;
|
|
98
49
|
if (parsed.model || importNeedsModel(parsed)) {
|
|
@@ -133,6 +84,7 @@ export async function runOpencodeImportCommand(input) {
|
|
|
133
84
|
return `${name} failed: ${message}`;
|
|
134
85
|
}
|
|
135
86
|
finally {
|
|
87
|
+
endManualImport();
|
|
136
88
|
importCommandRunning = false;
|
|
137
89
|
}
|
|
138
90
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HistoryImportModels } from "../../importer/run-import.js";
|
|
2
|
+
import { type PiModelContext } from "./provider.js";
|
|
3
|
+
/** Resolve the Pi backfill independently of the manual import's session-model rule. */
|
|
4
|
+
export declare function resolvePiBackfillModels(ctx: PiModelContext, config?: {
|
|
5
|
+
piBackfillModel?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
model: string;
|
|
8
|
+
models: HistoryImportModels;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CONFIG } from "../../config.js";
|
|
2
|
+
import { parseBackfillModel } from "../../importer/backfill-model.js";
|
|
3
|
+
import { createPiLiveModels } from "./live-model.js";
|
|
4
|
+
import { adaptPiProfileModel } from "./profile.js";
|
|
5
|
+
import { createPiCaptureProvider, resolveImportModel } from "./provider.js";
|
|
6
|
+
/** Resolve the Pi backfill independently of the manual import's session-model rule. */
|
|
7
|
+
export async function resolvePiBackfillModels(ctx, config = CONFIG) {
|
|
8
|
+
const choice = parseBackfillModel(config, "pi");
|
|
9
|
+
if (choice === "inherit") {
|
|
10
|
+
const live = createPiLiveModels(ctx);
|
|
11
|
+
const profile = live.profile();
|
|
12
|
+
if (!profile)
|
|
13
|
+
throw new Error("Pi backfill: no live-capture model is available");
|
|
14
|
+
const model = profile.modelId || CONFIG.memoryModel;
|
|
15
|
+
return {
|
|
16
|
+
model: `${profile.provider}/${model ?? "unknown"}`,
|
|
17
|
+
models: { capture: live.capture, profile },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const selected = resolveImportModel(ctx, `${choice.provider}/${choice.model}`);
|
|
21
|
+
if (!selected)
|
|
22
|
+
throw new Error(`Pi backfill model not found: ${choice.provider}/${choice.model}`);
|
|
23
|
+
return {
|
|
24
|
+
model: `${selected.provider}/${selected.modelId}`,
|
|
25
|
+
models: {
|
|
26
|
+
capture: createPiCaptureProvider(() => selected),
|
|
27
|
+
profile: adaptPiProfileModel(selected),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { CONFIG, initConfigWithLegacyMigration, isConfigured } from "../../config.js";
|
|
3
|
+
import { pruneTraces } from "../../services/capture-diagnostics.js";
|
|
3
4
|
import { executeMemoryOperation } from "../../core/memory-operations.js";
|
|
4
5
|
import { getLanguageName } from "../../services/language-detector.js";
|
|
5
6
|
import { log } from "../../services/logger.js";
|
|
@@ -10,6 +11,7 @@ import { registerPiHistoryImportCommand } from "./import-command.js";
|
|
|
10
11
|
import { performPiProfileLearning } from "./profile.js";
|
|
11
12
|
import { buildRetrievalSection, wrapRetrievalSection } from "../../core/retrieval.js";
|
|
12
13
|
const GLOBAL_PLUGIN_WARMUP_KEY = Symbol.for("omms.plugin.warmedup");
|
|
14
|
+
const GLOBAL_PI_BACKFILL_KEY = Symbol.for("omms.pi.backfill.scheduled");
|
|
13
15
|
const OMMS_STATUS_KEY = "omms";
|
|
14
16
|
const MEMORY_TOOL_MODES = [
|
|
15
17
|
"add",
|
|
@@ -69,6 +71,7 @@ export default function ommsPiExtension(pi) {
|
|
|
69
71
|
// Latest session context, refreshed on session_start and consumed by the
|
|
70
72
|
// import command (command contexts do not expose the model registry).
|
|
71
73
|
let latestCtx = null;
|
|
74
|
+
let backfillSessionId = null;
|
|
72
75
|
let statusVersion = 0;
|
|
73
76
|
const startStatus = (ctx, status) => {
|
|
74
77
|
const version = ++statusVersion;
|
|
@@ -97,8 +100,53 @@ export default function ommsPiExtension(pi) {
|
|
|
97
100
|
try {
|
|
98
101
|
latestCtx = ctx;
|
|
99
102
|
initConfigWithLegacyMigration(ctx.cwd);
|
|
103
|
+
// Runs even with tracing off, so turning it off does not leave old traces behind.
|
|
104
|
+
pruneTraces(CONFIG);
|
|
100
105
|
captureState = createPiCaptureState();
|
|
106
|
+
if (CONFIG.webServerAutoStart !== undefined &&
|
|
107
|
+
process.env.OMMS_DISABLE_WEB_AUTOSTART !== "1") {
|
|
108
|
+
void import("../../services/web-autostart.js")
|
|
109
|
+
.then(({ reconcileWebAutostart }) => reconcileWebAutostart(CONFIG))
|
|
110
|
+
.catch((error) => log("Pi login item reconciliation failed", {
|
|
111
|
+
code: error instanceof Error ? error.name : "unknown",
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
101
114
|
const globalScope = globalThis;
|
|
115
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
116
|
+
if (isConfigured() &&
|
|
117
|
+
CONFIG.autoBackfill &&
|
|
118
|
+
process.env.OMMS_DISABLE_AUTO_BACKFILL !== "1" &&
|
|
119
|
+
!globalScope[GLOBAL_PI_BACKFILL_KEY] &&
|
|
120
|
+
backfillSessionId !== sessionId) {
|
|
121
|
+
backfillSessionId = sessionId;
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const task = { controller, promise: Promise.resolve() };
|
|
124
|
+
globalScope[GLOBAL_PI_BACKFILL_KEY] = task;
|
|
125
|
+
task.promise = (async () => {
|
|
126
|
+
const { scheduleAutoBackfill } = await import("../../importer/auto-backfill.js");
|
|
127
|
+
await scheduleAutoBackfill({
|
|
128
|
+
host: "pi",
|
|
129
|
+
cwd: ctx.cwd,
|
|
130
|
+
signal: controller.signal,
|
|
131
|
+
resolveModels: async () => {
|
|
132
|
+
const { resolvePiBackfillModels } = await import("./backfill-models.js");
|
|
133
|
+
return resolvePiBackfillModels(ctx);
|
|
134
|
+
},
|
|
135
|
+
notify: (message) => {
|
|
136
|
+
if (!controller.signal.aborted && ctx.hasUI)
|
|
137
|
+
ctx.ui.notify(message, "info");
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
})()
|
|
141
|
+
.catch((error) => log("Pi backfill failed", {
|
|
142
|
+
error: error instanceof Error ? error.message : String(error),
|
|
143
|
+
}))
|
|
144
|
+
.finally(() => {
|
|
145
|
+
if (globalScope[GLOBAL_PI_BACKFILL_KEY] === task) {
|
|
146
|
+
delete globalScope[GLOBAL_PI_BACKFILL_KEY];
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
102
150
|
if (!globalScope[GLOBAL_PLUGIN_WARMUP_KEY] && isConfigured()) {
|
|
103
151
|
(async () => {
|
|
104
152
|
try {
|
|
@@ -189,6 +237,11 @@ export default function ommsPiExtension(pi) {
|
|
|
189
237
|
captureState = createPiCaptureState();
|
|
190
238
|
promptsSinceProfileAnalysis = [];
|
|
191
239
|
globalThis[GLOBAL_PLUGIN_WARMUP_KEY] = false;
|
|
240
|
+
const task = globalThis[GLOBAL_PI_BACKFILL_KEY];
|
|
241
|
+
task?.controller.abort();
|
|
242
|
+
if (task)
|
|
243
|
+
await task.promise;
|
|
244
|
+
backfillSessionId = null;
|
|
192
245
|
try {
|
|
193
246
|
await memoryClient.close();
|
|
194
247
|
}
|
|
@@ -5,6 +5,7 @@ import { historyImportUsage, importNeedsModel, parseHistoryImportArgs, tokenizeI
|
|
|
5
5
|
import { formatHistoryImportReport, summarizeHistoryImportReport, runHistoryImport, } from "../../importer/run-import.js";
|
|
6
6
|
import { createPiCaptureProvider, resolveImportModel } from "./provider.js";
|
|
7
7
|
import { adaptPiProfileModel } from "./profile.js";
|
|
8
|
+
import { beginManualImport } from "../../importer/manual-import-guard.js";
|
|
8
9
|
/**
|
|
9
10
|
* Pi command surface for the historical-session importer:
|
|
10
11
|
*
|
|
@@ -74,6 +75,7 @@ export function registerPiHistoryImportCommand(pi, getExtensionContext) {
|
|
|
74
75
|
return;
|
|
75
76
|
}
|
|
76
77
|
importCommandRunning = true;
|
|
78
|
+
const endManualImport = beginManualImport("pi");
|
|
77
79
|
try {
|
|
78
80
|
// Best-effort warmup so failures surface as per-unit errors instead of
|
|
79
81
|
// every unit failing on embedding initialisation.
|
|
@@ -110,6 +112,7 @@ export function registerPiHistoryImportCommand(pi, getExtensionContext) {
|
|
|
110
112
|
notify(`memory-import-pi-history failed: ${message}`);
|
|
111
113
|
}
|
|
112
114
|
finally {
|
|
115
|
+
endManualImport();
|
|
113
116
|
importCommandRunning = false;
|
|
114
117
|
}
|
|
115
118
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CONFIG } from "../../config.js";
|
|
1
|
+
import { CONFIG, refreshConfigIfChanged } from "../../config.js";
|
|
2
2
|
import { analyzeProfile, } from "../../core/profile-analysis.js";
|
|
3
3
|
import { log } from "../../services/logger.js";
|
|
4
4
|
import { getTags } from "../../services/tags.js";
|
|
@@ -47,6 +47,7 @@ export async function performPiProfileLearning(input) {
|
|
|
47
47
|
if (input.prompts.length === 0)
|
|
48
48
|
return;
|
|
49
49
|
try {
|
|
50
|
+
refreshConfigIfChanged(input.directory);
|
|
50
51
|
const tags = getTags(input.directory);
|
|
51
52
|
if (!tags.user.userEmail) {
|
|
52
53
|
log("pi profile learning: skipped (no user email resolved)", {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { CONFIG } from "../../config.js";
|
|
2
2
|
import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
|
|
3
|
-
import { buildCaptureSystemPrompt, captureSummaryToolSchema, parseCaptureSummary, } from "../../core/extraction.js";
|
|
3
|
+
import { buildCaptureReplyInstruction, buildCaptureSystemPrompt, captureSummaryToolSchema, classifyCaptureReply, normalizeStopReason, parseCaptureSummary, } from "../../core/extraction.js";
|
|
4
4
|
import { detectLanguage, getLanguageName } from "../../services/language-detector.js";
|
|
5
|
-
import { log } from "../../services/logger.js";
|
|
6
5
|
/**
|
|
7
6
|
* Active-model-first resolution: the explicit `piProvider`/`piModel`
|
|
8
7
|
* configuration when both are set, otherwise the active `ctx.model`.
|
|
@@ -80,23 +79,51 @@ export function createPiCaptureProvider(resolveModel) {
|
|
|
80
79
|
? detectLanguage(request.userPrompt)
|
|
81
80
|
: CONFIG.autoCaptureLanguage;
|
|
82
81
|
const systemPrompt = buildCaptureSystemPrompt(getLanguageName(targetLang));
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const replyInstruction = buildCaptureReplyInstruction();
|
|
83
|
+
// The instruction is counted as schema bytes so the context budget leaves room for it.
|
|
84
|
+
const userPrompt = `${buildBoundedSummaryPrompt(request.context, systemPrompt, {
|
|
85
|
+
schema: captureSummaryToolSchema,
|
|
86
|
+
replyInstruction,
|
|
87
|
+
})}\n\n${replyInstruction}`;
|
|
88
|
+
const diagnostics = request.diagnostics;
|
|
89
|
+
if (diagnostics) {
|
|
90
|
+
diagnostics.path = "host-model";
|
|
91
|
+
diagnostics.provider = model.provider;
|
|
92
|
+
diagnostics.model = model.modelId;
|
|
93
|
+
diagnostics.systemPrompt = systemPrompt;
|
|
94
|
+
diagnostics.userPrompt = userPrompt;
|
|
95
|
+
}
|
|
96
|
+
let reply;
|
|
97
|
+
try {
|
|
98
|
+
reply = await model.complete({
|
|
99
|
+
systemPrompt,
|
|
100
|
+
messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (diagnostics)
|
|
105
|
+
diagnostics.failureReason = "call-error";
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
const rawReply = replyText(reply);
|
|
109
|
+
if (diagnostics) {
|
|
110
|
+
diagnostics.stopReason = normalizeStopReason(reply.stopReason);
|
|
111
|
+
diagnostics.blockTypes = reply.content.map((block) => block.type);
|
|
112
|
+
diagnostics.rawReply = rawReply;
|
|
113
|
+
}
|
|
88
114
|
if (reply.stopReason === "error") {
|
|
115
|
+
if (diagnostics)
|
|
116
|
+
diagnostics.failureReason = "call-error";
|
|
89
117
|
throw new Error(`omms: Pi extraction call failed: ${reply.errorMessage || "unknown error"}`);
|
|
90
118
|
}
|
|
91
|
-
const rawReply = replyText(reply);
|
|
92
119
|
const summary = parseCaptureSummary(rawReply);
|
|
93
120
|
if (!summary) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
121
|
+
// The capture pipeline logs the attempt's metadata; never the reply.
|
|
122
|
+
if (diagnostics) {
|
|
123
|
+
diagnostics.failureReason =
|
|
124
|
+
classifyCaptureReply({ text: rawReply, stopReason: reply.stopReason }) ??
|
|
125
|
+
"schema-mismatch";
|
|
126
|
+
}
|
|
100
127
|
throw new Error("omms: Pi extraction returned an invalid summary payload");
|
|
101
128
|
}
|
|
102
129
|
return summary;
|
package/dist/cli/index.js
CHANGED
|
@@ -11,8 +11,10 @@ const usage = `Usage: om-memory-system <command> [options]
|
|
|
11
11
|
Commands:
|
|
12
12
|
import-opencode-history Import OpenCode history into omms
|
|
13
13
|
import-pi-history Import Pi history into omms
|
|
14
|
+
web Start the web app in the foreground
|
|
15
|
+
web install|uninstall|status Manage the web app login item
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
Import commands take the same options; run one with --help to see them.
|
|
16
18
|
Inside a session, /memory-import-opencode-history and /memory-import-pi-history
|
|
17
19
|
use that session's model instead of an API key.`;
|
|
18
20
|
export function parseImportArgs(argv) {
|
|
@@ -30,6 +32,16 @@ export async function runCli(argv) {
|
|
|
30
32
|
console.log(usage);
|
|
31
33
|
return 0;
|
|
32
34
|
}
|
|
35
|
+
if (argv[0] === "web") {
|
|
36
|
+
try {
|
|
37
|
+
const { runWebCommand } = await import("./web-command.js");
|
|
38
|
+
return await runWebCommand(argv.slice(1));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(`OMMS web command failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
33
45
|
const apiKeyEnv = argv.find((_, index) => argv[index - 1] === "--api-key-env") ??
|
|
34
46
|
argv.find((value) => value.startsWith("--api-key-env="))?.slice("--api-key-env=".length);
|
|
35
47
|
try {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WebAutostartOptions } from "../services/web-autostart.js";
|
|
2
|
+
/** Run the standalone web app or manage its per-user login item. */
|
|
3
|
+
export declare function runWebCommand(args: string[], options?: WebAutostartOptions, online?: () => Promise<boolean>): Promise<number>;
|