om-memory-system 3.0.0
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/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { CONFIG } from "../../config.js";
|
|
3
|
+
import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
|
|
4
|
+
import { log } from "../../services/logger.js";
|
|
5
|
+
import { loadOpencodeProvider } from "../../services/ai/opencode-provider-loader.js";
|
|
6
|
+
function buildSystemPrompt(languageName) {
|
|
7
|
+
return `You are a technical memory recorder for a software development project.
|
|
8
|
+
|
|
9
|
+
RULES:
|
|
10
|
+
1. ONLY capture technical work (code, bugs, features, architecture, config)
|
|
11
|
+
2. SKIP non-technical by returning type="skip"
|
|
12
|
+
3. NO meta-commentary or behavior analysis
|
|
13
|
+
4. Include specific file names, functions, technical details
|
|
14
|
+
5. Generate 2-4 technical tags (e.g., "react", "auth", "bug-fix")
|
|
15
|
+
6. You MUST write the summary in ${languageName}.
|
|
16
|
+
|
|
17
|
+
FORMAT:
|
|
18
|
+
## Request
|
|
19
|
+
[1-2 sentences: what was requested, in ${languageName}]
|
|
20
|
+
|
|
21
|
+
## Outcome
|
|
22
|
+
[1-2 sentences: what was done, include files/functions, in ${languageName}]
|
|
23
|
+
|
|
24
|
+
SKIP if: greetings, casual chat, no code/decisions made
|
|
25
|
+
CAPTURE if: code changed, bug fixed, feature added, decision made`;
|
|
26
|
+
}
|
|
27
|
+
export async function generateOpenCodeAutoCaptureSummary(request, notify) {
|
|
28
|
+
let opencodeProviderError;
|
|
29
|
+
if (CONFIG.opencodeProvider && CONFIG.opencodeModel) {
|
|
30
|
+
try {
|
|
31
|
+
if (CONFIG.memoryModel) {
|
|
32
|
+
log("opencodeProvider takes precedence over memoryModel for auto-capture");
|
|
33
|
+
}
|
|
34
|
+
const { isProviderConnected, getV2Client, generateStructuredOutput } = await loadOpencodeProvider();
|
|
35
|
+
let providerID = CONFIG.opencodeProvider;
|
|
36
|
+
let modelID = CONFIG.opencodeModel;
|
|
37
|
+
if (modelID === "inherit") {
|
|
38
|
+
if (!request.prompt?.providerId || !request.prompt?.modelId) {
|
|
39
|
+
throw new Error("omms: opencodeModel is 'inherit' but no session model was recorded for this prompt");
|
|
40
|
+
}
|
|
41
|
+
providerID = request.prompt.providerId;
|
|
42
|
+
modelID = request.prompt.modelId;
|
|
43
|
+
}
|
|
44
|
+
if (!isProviderConnected(providerID)) {
|
|
45
|
+
throw new Error(`opencode provider '${providerID}' is not connected. Check your opencode provider configuration.`);
|
|
46
|
+
}
|
|
47
|
+
const v2Client = getV2Client();
|
|
48
|
+
if (!v2Client) {
|
|
49
|
+
throw new Error("omms: v2 client not initialized; cannot perform structured-output capture");
|
|
50
|
+
}
|
|
51
|
+
const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
|
|
52
|
+
const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
|
|
53
|
+
? detectLanguage(request.userPrompt)
|
|
54
|
+
: CONFIG.autoCaptureLanguage;
|
|
55
|
+
const systemPrompt = buildSystemPrompt(getLanguageName(targetLang));
|
|
56
|
+
const { z } = await import("zod");
|
|
57
|
+
const schema = z.object({
|
|
58
|
+
summary: z.string(),
|
|
59
|
+
type: z.string(),
|
|
60
|
+
tags: z.array(z.string()),
|
|
61
|
+
});
|
|
62
|
+
const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, z.toJSONSchema(schema));
|
|
63
|
+
const result = await generateStructuredOutput({
|
|
64
|
+
client: v2Client,
|
|
65
|
+
providerID,
|
|
66
|
+
modelID,
|
|
67
|
+
systemPrompt,
|
|
68
|
+
userPrompt: aiPrompt,
|
|
69
|
+
schema,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
summary: result.summary,
|
|
73
|
+
type: result.type,
|
|
74
|
+
tags: (result.tags || []).map((tag) => tag.toLowerCase().trim()),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
opencodeProviderError = error;
|
|
79
|
+
log("auto-capture: opencode provider failed, falling back to external API", {
|
|
80
|
+
error: String(error),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!CONFIG.memoryModel || !CONFIG.memoryApiUrl) {
|
|
85
|
+
if (opencodeProviderError)
|
|
86
|
+
throw opencodeProviderError;
|
|
87
|
+
throw new Error("External API not configured for auto-capture");
|
|
88
|
+
}
|
|
89
|
+
if (opencodeProviderError && CONFIG.showErrorToasts && notify) {
|
|
90
|
+
const errMsg = opencodeProviderError instanceof Error
|
|
91
|
+
? opencodeProviderError.message
|
|
92
|
+
: String(opencodeProviderError);
|
|
93
|
+
const shortReason = errMsg.length > 100 ? errMsg.substring(0, 100) + "..." : errMsg;
|
|
94
|
+
await Promise.resolve(notify({
|
|
95
|
+
title: "Using fallback provider",
|
|
96
|
+
message: `OpenCode provider failed (${shortReason}); using configured fallback.`,
|
|
97
|
+
variant: "warning",
|
|
98
|
+
duration: 5000,
|
|
99
|
+
})).catch(() => { });
|
|
100
|
+
}
|
|
101
|
+
const { AIProviderFactory } = await import("../../services/ai/ai-provider-factory.js");
|
|
102
|
+
const { buildMemoryProviderConfig } = await import("../../services/ai/provider-config.js");
|
|
103
|
+
const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
|
|
104
|
+
const providerConfig = buildMemoryProviderConfig(CONFIG);
|
|
105
|
+
const provider = AIProviderFactory.createProvider(CONFIG.memoryProvider, providerConfig);
|
|
106
|
+
const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
|
|
107
|
+
? detectLanguage(request.userPrompt)
|
|
108
|
+
: CONFIG.autoCaptureLanguage;
|
|
109
|
+
const systemPrompt = buildSystemPrompt(getLanguageName(targetLang));
|
|
110
|
+
const toolSchema = {
|
|
111
|
+
type: "function",
|
|
112
|
+
function: {
|
|
113
|
+
name: "save_memory",
|
|
114
|
+
description: "Save the conversation summary as a memory",
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: {
|
|
118
|
+
summary: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Markdown-formatted summary of the conversation",
|
|
121
|
+
},
|
|
122
|
+
type: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Type of memory: 'skip' for non-technical conversations, or technical type (feature, bug-fix, refactor, analysis, configuration, discussion, other)",
|
|
125
|
+
},
|
|
126
|
+
tags: {
|
|
127
|
+
type: "array",
|
|
128
|
+
items: { type: "string" },
|
|
129
|
+
description: "List of 2-4 technical tags related to the memory",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ["summary", "type", "tags"],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
const aiPrompt = buildBoundedSummaryPrompt(request.context, systemPrompt, toolSchema);
|
|
137
|
+
const captureSessionID = `auto-capture-${request.prompt?.id ?? request.sessionId}-${randomUUID()}`;
|
|
138
|
+
const result = await provider.executeToolCall(systemPrompt, aiPrompt, toolSchema, captureSessionID);
|
|
139
|
+
if (!result.success || !result.data) {
|
|
140
|
+
throw new Error(result.error || "Failed to generate summary");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
summary: result.data.summary,
|
|
144
|
+
type: result.data.type,
|
|
145
|
+
tags: (result.data.tags || []).map((tag) => tag.toLowerCase().trim()),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const MAX_TOOL_INPUT_LENGTH = 100;
|
|
2
|
+
export function extractOpenCodeConversation(messages, promptMessageId) {
|
|
3
|
+
const promptIndex = messages.findIndex((message) => message.info?.id === promptMessageId);
|
|
4
|
+
if (promptIndex === -1)
|
|
5
|
+
return null;
|
|
6
|
+
const responseMessages = [];
|
|
7
|
+
for (const message of messages.slice(promptIndex + 1)) {
|
|
8
|
+
if (message.info?.role === "user")
|
|
9
|
+
break;
|
|
10
|
+
responseMessages.push(message);
|
|
11
|
+
}
|
|
12
|
+
const textResponses = [];
|
|
13
|
+
const toolCalls = [];
|
|
14
|
+
const sourceEntryIds = [];
|
|
15
|
+
for (const message of responseMessages) {
|
|
16
|
+
if (message.info?.role !== "assistant")
|
|
17
|
+
continue;
|
|
18
|
+
if (typeof message.info?.id === "string")
|
|
19
|
+
sourceEntryIds.push(message.info.id);
|
|
20
|
+
if (!Array.isArray(message.parts))
|
|
21
|
+
continue;
|
|
22
|
+
const textParts = message.parts.filter((part) => part.type === "text" && typeof part.text === "string" && part.text.trim());
|
|
23
|
+
if (textParts.length > 0) {
|
|
24
|
+
textResponses.push(textParts
|
|
25
|
+
.map((part) => part.text)
|
|
26
|
+
.join("\n")
|
|
27
|
+
.trim());
|
|
28
|
+
}
|
|
29
|
+
for (const tool of message.parts.filter((part) => part.type === "tool")) {
|
|
30
|
+
const name = tool.tool || "unknown";
|
|
31
|
+
let input = "";
|
|
32
|
+
if (tool.state?.input) {
|
|
33
|
+
const inputValue = tool.state.input;
|
|
34
|
+
if (typeof inputValue === "string") {
|
|
35
|
+
input = inputValue;
|
|
36
|
+
}
|
|
37
|
+
else if (typeof inputValue === "object") {
|
|
38
|
+
input = Object.entries(inputValue)
|
|
39
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
40
|
+
.join(", ");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (input.length > MAX_TOOL_INPUT_LENGTH) {
|
|
44
|
+
input = input.substring(0, MAX_TOOL_INPUT_LENGTH) + "...";
|
|
45
|
+
}
|
|
46
|
+
toolCalls.push({ name, input });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { textResponses, toolCalls, sourceEntryIds };
|
|
50
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function isStructuredSummaryPromptMessage(userMessage: string): boolean;
|
|
2
|
+
/** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
|
|
3
|
+
export declare function isInternalPrompt(sessionID: string, userMessage: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Record a real user prompt so idle auto-capture can pair it with the session's
|
|
6
|
+
* response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
|
|
7
|
+
* Returns false when the prompt is empty or internal and was not recorded.
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordUserPrompt(sessionID: string, messageID: string, directory: string, userMessage: string): Promise<boolean>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isInternalStructuredSession } from "../../services/ai/opencode-provider.js";
|
|
2
|
+
import { userPromptManager } from "../../services/user-prompt/user-prompt-manager.js";
|
|
3
|
+
export function isStructuredSummaryPromptMessage(userMessage) {
|
|
4
|
+
// This is the plugin's own structured-summary or profile-analysis request.
|
|
5
|
+
// OpenCode echoes it through chat.message like a normal user message, but
|
|
6
|
+
// capturing it would create self-referential memories / an infinite learning loop.
|
|
7
|
+
if (userMessage.includes("# User Profile Analysis")) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return userMessage.includes("Analyze this conversation.") && userMessage.includes('type="skip"');
|
|
11
|
+
}
|
|
12
|
+
/** True when a prompt is omms's own internal traffic and must not be recorded or searched. */
|
|
13
|
+
export function isInternalPrompt(sessionID, userMessage) {
|
|
14
|
+
return isStructuredSummaryPromptMessage(userMessage) || isInternalStructuredSession(sessionID);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Record a real user prompt so idle auto-capture can pair it with the session's
|
|
18
|
+
* response. Shared by the V1 `chat.message` hook and the v2 `prompt` hook.
|
|
19
|
+
* Returns false when the prompt is empty or internal and was not recorded.
|
|
20
|
+
*/
|
|
21
|
+
export async function recordUserPrompt(sessionID, messageID, directory, userMessage) {
|
|
22
|
+
if (!userMessage.trim() || isInternalPrompt(sessionID, userMessage))
|
|
23
|
+
return false;
|
|
24
|
+
await userPromptManager.savePrompt(sessionID, messageID, directory, userMessage);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CaptureSummaryProvider } from "../../core/host.js";
|
|
2
|
+
import { type PiSessionEntry } from "./conversation.js";
|
|
3
|
+
export interface PiCaptureState {
|
|
4
|
+
/** User entry IDs terminally handled by a settled capture for this session. */
|
|
5
|
+
handledUserEntries: Set<string>;
|
|
6
|
+
running: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPiCaptureState(): PiCaptureState;
|
|
9
|
+
export interface PiSettledCaptureInput {
|
|
10
|
+
sessionId: string;
|
|
11
|
+
directory: string;
|
|
12
|
+
entries: PiSessionEntry[];
|
|
13
|
+
provider: CaptureSummaryProvider;
|
|
14
|
+
state: PiCaptureState;
|
|
15
|
+
prompt?: {
|
|
16
|
+
providerId?: string | null;
|
|
17
|
+
modelId?: string | null;
|
|
18
|
+
};
|
|
19
|
+
notify?: (notification: {
|
|
20
|
+
title: string;
|
|
21
|
+
message: string;
|
|
22
|
+
variant: "success" | "warning" | "error" | "info";
|
|
23
|
+
}) => Promise<void> | void;
|
|
24
|
+
}
|
|
25
|
+
export type PiSettledCaptureResult = {
|
|
26
|
+
status: "captured";
|
|
27
|
+
memoryId: string;
|
|
28
|
+
} | {
|
|
29
|
+
status: "skipped";
|
|
30
|
+
reason: "disabled" | "busy" | "not-ready" | "no-window" | "already-handled" | "extractor-skip";
|
|
31
|
+
} | {
|
|
32
|
+
status: "failed";
|
|
33
|
+
error: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Capture the work unit completed by the most recent user prompt on the
|
|
37
|
+
* settled branch. Safe to call for every `agent_settled` event: a work unit is
|
|
38
|
+
* processed at most once per user entry ID (live source identity), so retries,
|
|
39
|
+
* compaction continuation, and repeated settled events never double-capture.
|
|
40
|
+
*/
|
|
41
|
+
export declare function capturePiSettledWorkUnit(input: PiSettledCaptureInput): Promise<PiSettledCaptureResult>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CONFIG } from "../../config.js";
|
|
2
|
+
import { captureConversation } from "../../core/capture.js";
|
|
3
|
+
import { log } from "../../services/logger.js";
|
|
4
|
+
import { memoryClient } from "../../services/client.js";
|
|
5
|
+
import { extractPiConversation } from "./conversation.js";
|
|
6
|
+
export function createPiCaptureState() {
|
|
7
|
+
return { handledUserEntries: new Set(), running: false };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Capture the work unit completed by the most recent user prompt on the
|
|
11
|
+
* settled branch. Safe to call for every `agent_settled` event: a work unit is
|
|
12
|
+
* processed at most once per user entry ID (live source identity), so retries,
|
|
13
|
+
* compaction continuation, and repeated settled events never double-capture.
|
|
14
|
+
*/
|
|
15
|
+
export async function capturePiSettledWorkUnit(input) {
|
|
16
|
+
if (!CONFIG.autoCaptureEnabled)
|
|
17
|
+
return { status: "skipped", reason: "disabled" };
|
|
18
|
+
if (input.state.running)
|
|
19
|
+
return { status: "skipped", reason: "busy" };
|
|
20
|
+
input.state.running = true;
|
|
21
|
+
try {
|
|
22
|
+
const window = extractPiConversation(input.entries);
|
|
23
|
+
if (!window)
|
|
24
|
+
return { status: "skipped", reason: "no-window" };
|
|
25
|
+
if (input.state.handledUserEntries.has(window.userEntryId)) {
|
|
26
|
+
return { status: "skipped", reason: "already-handled" };
|
|
27
|
+
}
|
|
28
|
+
const embeddingInitError = memoryClient.getEmbeddingInitError?.();
|
|
29
|
+
if (embeddingInitError) {
|
|
30
|
+
return { status: "skipped", reason: "not-ready" };
|
|
31
|
+
}
|
|
32
|
+
const captureResult = await captureConversation({
|
|
33
|
+
host: "pi",
|
|
34
|
+
hostSessionId: input.sessionId,
|
|
35
|
+
sourceType: "live-capture",
|
|
36
|
+
projectDirectory: input.directory,
|
|
37
|
+
userPrompt: window.userPrompt,
|
|
38
|
+
promptId: window.userEntryId,
|
|
39
|
+
prompt: {
|
|
40
|
+
id: window.userEntryId,
|
|
41
|
+
providerId: input.prompt?.providerId ?? null,
|
|
42
|
+
modelId: input.prompt?.modelId ?? null,
|
|
43
|
+
},
|
|
44
|
+
textResponses: window.textResponses,
|
|
45
|
+
toolCalls: window.toolCalls,
|
|
46
|
+
sourceEntryIds: window.sourceEntryIds,
|
|
47
|
+
...(window.sourceTimestamp !== undefined
|
|
48
|
+
? { sourceTimestamp: window.sourceTimestamp }
|
|
49
|
+
: {}),
|
|
50
|
+
}, input.provider);
|
|
51
|
+
input.state.handledUserEntries.add(window.userEntryId);
|
|
52
|
+
if (captureResult.status === "skipped") {
|
|
53
|
+
log("Pi auto-capture skipped by extractor", {
|
|
54
|
+
sessionID: input.sessionId,
|
|
55
|
+
userEntryId: window.userEntryId,
|
|
56
|
+
type: captureResult.type,
|
|
57
|
+
});
|
|
58
|
+
return { status: "skipped", reason: "extractor-skip" };
|
|
59
|
+
}
|
|
60
|
+
log("Pi auto-capture memory persisted", {
|
|
61
|
+
sessionID: input.sessionId,
|
|
62
|
+
userEntryId: window.userEntryId,
|
|
63
|
+
memoryId: captureResult.memoryId,
|
|
64
|
+
host: "pi",
|
|
65
|
+
});
|
|
66
|
+
if (CONFIG.showAutoCaptureToasts && input.notify) {
|
|
67
|
+
await Promise.resolve(input.notify({
|
|
68
|
+
title: "Memory Captured",
|
|
69
|
+
message: "Project memory saved from conversation",
|
|
70
|
+
variant: "success",
|
|
71
|
+
})).catch(() => { });
|
|
72
|
+
}
|
|
73
|
+
return { status: "captured", memoryId: captureResult.memoryId };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
log(`Pi auto-capture error: ${message}`, { sessionID: input.sessionId });
|
|
78
|
+
if (CONFIG.showErrorToasts && input.notify) {
|
|
79
|
+
const shortReason = message.length > 100 ? message.substring(0, 100) + "..." : message;
|
|
80
|
+
await Promise.resolve(input.notify({
|
|
81
|
+
title: "Auto Capture Failed",
|
|
82
|
+
message: shortReason,
|
|
83
|
+
variant: "error",
|
|
84
|
+
})).catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
return { status: "failed", error: message };
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
input.state.running = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CaptureConversation } from "../../core/host.js";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal structural view of Pi session entries (see Pi session-format docs).
|
|
4
|
+
* Kept structural so the normaliser stays testable without importing Pi types.
|
|
5
|
+
*/
|
|
6
|
+
export interface PiSessionEntry {
|
|
7
|
+
type: string;
|
|
8
|
+
id: string;
|
|
9
|
+
parentId: string | null;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
message?: {
|
|
12
|
+
role?: string;
|
|
13
|
+
content?: string | PiContentBlock[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface PiContentBlock {
|
|
17
|
+
type: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
thinking?: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
arguments?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export interface PiConversationWindow extends CaptureConversation {
|
|
24
|
+
userEntryId: string;
|
|
25
|
+
userPrompt: string;
|
|
26
|
+
userTimestamp?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* All capture windows on the branch, oldest first: one per user prompt that
|
|
30
|
+
* produced assistant or tool work. Used by live capture (last window) and by
|
|
31
|
+
* the historical importer (all windows).
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractPiConversationWindows(entries: PiSessionEntry[]): PiConversationWindow[];
|
|
34
|
+
/**
|
|
35
|
+
* The capture window of the most recent user prompt on the branch. Returns
|
|
36
|
+
* null when the branch has no user prompt with a response window.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractPiConversation(entries: PiSessionEntry[]): PiConversationWindow | null;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const MAX_TOOL_INPUT_LENGTH = 100;
|
|
2
|
+
function entryText(content) {
|
|
3
|
+
if (typeof content === "string")
|
|
4
|
+
return content;
|
|
5
|
+
if (!Array.isArray(content))
|
|
6
|
+
return "";
|
|
7
|
+
return content
|
|
8
|
+
.filter((block) => block.type === "text" && typeof block.text === "string")
|
|
9
|
+
.map((block) => block.text)
|
|
10
|
+
.join("\n")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
function isUserEntry(entry) {
|
|
14
|
+
return entry.type === "message" && entry.message?.role === "user";
|
|
15
|
+
}
|
|
16
|
+
function isAssistantEntry(entry) {
|
|
17
|
+
return entry.type === "message" && entry.message?.role === "assistant";
|
|
18
|
+
}
|
|
19
|
+
function formatToolInput(args) {
|
|
20
|
+
if (!args)
|
|
21
|
+
return "";
|
|
22
|
+
return Object.entries(args)
|
|
23
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
24
|
+
.join(", ");
|
|
25
|
+
}
|
|
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
|
+
/**
|
|
33
|
+
* Build the capture window for the user entry at `userIndex`: only that
|
|
34
|
+
* prompt's assistant/tool work, stopping at the next user message. Hidden
|
|
35
|
+
* thinking blocks are never included. Tool inputs are bounded by the same
|
|
36
|
+
* truncation policy as the OpenCode adapter.
|
|
37
|
+
*
|
|
38
|
+
* Returns null when the prompt is empty or has no assistant/tool response
|
|
39
|
+
* window yet.
|
|
40
|
+
*/
|
|
41
|
+
function buildWindow(entries, userIndex) {
|
|
42
|
+
const userEntry = entries[userIndex];
|
|
43
|
+
const userPrompt = entryText(userEntry.message?.content);
|
|
44
|
+
if (!userPrompt)
|
|
45
|
+
return null;
|
|
46
|
+
const textResponses = [];
|
|
47
|
+
const toolCalls = [];
|
|
48
|
+
const sourceEntryIds = [];
|
|
49
|
+
let sourceTimestamp;
|
|
50
|
+
for (const entry of entries.slice(userIndex + 1)) {
|
|
51
|
+
if (isUserEntry(entry))
|
|
52
|
+
break;
|
|
53
|
+
if (!isAssistantEntry(entry))
|
|
54
|
+
continue;
|
|
55
|
+
if (typeof entry.id === "string")
|
|
56
|
+
sourceEntryIds.push(entry.id);
|
|
57
|
+
const timestampMs = Date.parse(entry.timestamp);
|
|
58
|
+
if (!Number.isNaN(timestampMs))
|
|
59
|
+
sourceTimestamp = timestampMs;
|
|
60
|
+
const blocks = entry.message?.content;
|
|
61
|
+
if (!Array.isArray(blocks))
|
|
62
|
+
continue;
|
|
63
|
+
const textParts = blocks.filter((block) => block.type === "text" && typeof block.text === "string" && block.text.trim());
|
|
64
|
+
if (textParts.length > 0) {
|
|
65
|
+
textResponses.push(textParts
|
|
66
|
+
.map((block) => block.text)
|
|
67
|
+
.join("\n")
|
|
68
|
+
.trim());
|
|
69
|
+
}
|
|
70
|
+
for (const block of blocks.filter((item) => item.type === "toolCall")) {
|
|
71
|
+
const name = typeof block.name === "string" ? block.name : "unknown";
|
|
72
|
+
toolCalls.push({
|
|
73
|
+
name,
|
|
74
|
+
input: boundToolInput(formatToolInput(block.arguments)),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (textResponses.length === 0 && toolCalls.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const userTimestampMs = Date.parse(userEntry.timestamp);
|
|
81
|
+
return {
|
|
82
|
+
userEntryId: userEntry.id,
|
|
83
|
+
userPrompt,
|
|
84
|
+
textResponses,
|
|
85
|
+
toolCalls,
|
|
86
|
+
sourceEntryIds,
|
|
87
|
+
...(sourceTimestamp !== undefined ? { sourceTimestamp } : {}),
|
|
88
|
+
...(Number.isNaN(userTimestampMs) ? {} : { userTimestamp: userTimestampMs }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* All capture windows on the branch, oldest first: one per user prompt that
|
|
93
|
+
* produced assistant or tool work. Used by live capture (last window) and by
|
|
94
|
+
* the historical importer (all windows).
|
|
95
|
+
*/
|
|
96
|
+
export function extractPiConversationWindows(entries) {
|
|
97
|
+
const windows = [];
|
|
98
|
+
for (let i = 0; i < entries.length; i++) {
|
|
99
|
+
if (!isUserEntry(entries[i]))
|
|
100
|
+
continue;
|
|
101
|
+
const window = buildWindow(entries, i);
|
|
102
|
+
if (window)
|
|
103
|
+
windows.push(window);
|
|
104
|
+
}
|
|
105
|
+
return windows;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The capture window of the most recent user prompt on the branch. Returns
|
|
109
|
+
* null when the branch has no user prompt with a response window.
|
|
110
|
+
*/
|
|
111
|
+
export function extractPiConversation(entries) {
|
|
112
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
113
|
+
if (!isUserEntry(entries[i]))
|
|
114
|
+
continue;
|
|
115
|
+
return buildWindow(entries, i);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
/**
|
|
3
|
+
* Pi extension entry point for the shared omms engine.
|
|
4
|
+
*
|
|
5
|
+
* Lifecycle mapping:
|
|
6
|
+
* - `session_start` -> load shared config for ctx.cwd, warm storage
|
|
7
|
+
* - `before_agent_start` -> prompt-aware semantic retrieval, injected as a
|
|
8
|
+
* delimited system-prompt section (never a fake
|
|
9
|
+
* user message)
|
|
10
|
+
* - `agent_settled` -> automatic capture of the settled work unit
|
|
11
|
+
* - `session_shutdown` -> idempotent session-scoped cleanup
|
|
12
|
+
* - `memory` tool -> shared add/search/profile/list/forget/help and
|
|
13
|
+
* portability operations
|
|
14
|
+
*
|
|
15
|
+
* Pi types are imported type-only: the compiled extension carries no Pi
|
|
16
|
+
* runtime dependency and uses the host Pi runtime provided at load time.
|
|
17
|
+
*/
|
|
18
|
+
export default function ommsPiExtension(pi: ExtensionAPI): void;
|