@lapis-notes/ai 0.1.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/CHANGELOG.md +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/ai-plugin.d.ts +88 -0
- package/dist/ai-plugin.js +742 -0
- package/dist/bootstrap/build.d.ts +15 -0
- package/dist/bootstrap/build.js +28 -0
- package/dist/bootstrap/folder-instructions.d.ts +4 -0
- package/dist/bootstrap/folder-instructions.js +49 -0
- package/dist/bootstrap/format.d.ts +2 -0
- package/dist/bootstrap/format.js +46 -0
- package/dist/bootstrap/instructions.d.ts +1 -0
- package/dist/bootstrap/instructions.js +8 -0
- package/dist/bootstrap/types.d.ts +36 -0
- package/dist/bootstrap/types.js +3 -0
- package/dist/catalog/ai-catalog-panel.svelte +742 -0
- package/dist/catalog/ai-catalog-panel.svelte.d.ts +13 -0
- package/dist/catalog/ai-catalog-view-type.d.ts +1 -0
- package/dist/catalog/ai-catalog-view-type.js +1 -0
- package/dist/catalog/ai-catalog-view.d.ts +22 -0
- package/dist/catalog/ai-catalog-view.js +48 -0
- package/dist/catalog/filter.d.ts +3 -0
- package/dist/catalog/filter.js +24 -0
- package/dist/catalog/inventory.d.ts +21 -0
- package/dist/catalog/inventory.js +255 -0
- package/dist/catalog/tree.d.ts +12 -0
- package/dist/catalog/tree.js +60 -0
- package/dist/catalog/types.d.ts +44 -0
- package/dist/catalog/types.js +1 -0
- package/dist/chat/ai-approval-card.svelte +54 -0
- package/dist/chat/ai-approval-card.svelte.d.ts +9 -0
- package/dist/chat/ai-chat-panel.svelte +1288 -0
- package/dist/chat/ai-chat-panel.svelte.d.ts +50 -0
- package/dist/chat/ai-inventory-result.css +55 -0
- package/dist/chat/ai-inventory-result.svelte +63 -0
- package/dist/chat/ai-inventory-result.svelte.d.ts +5 -0
- package/dist/chat/ai-question-card.svelte +132 -0
- package/dist/chat/ai-question-card.svelte.d.ts +9 -0
- package/dist/chat/ai-view-bootstrap.d.ts +13 -0
- package/dist/chat/ai-view-bootstrap.js +86 -0
- package/dist/chat/ai-view-panel.svelte +89 -0
- package/dist/chat/ai-view-panel.svelte.d.ts +13 -0
- package/dist/chat/ai-view-type.d.ts +1 -0
- package/dist/chat/ai-view-type.js +1 -0
- package/dist/chat/ai-view.d.ts +60 -0
- package/dist/chat/ai-view.js +121 -0
- package/dist/chat/chat-controller.svelte.d.ts +82 -0
- package/dist/chat/chat-controller.svelte.js +1926 -0
- package/dist/chat/chat-items.d.ts +88 -0
- package/dist/chat/chat-items.js +9 -0
- package/dist/chat/chat-mentions.d.ts +15 -0
- package/dist/chat/chat-mentions.js +77 -0
- package/dist/chat/chat-result-views.d.ts +5 -0
- package/dist/chat/chat-result-views.js +15 -0
- package/dist/chat/chat-session.d.ts +28 -0
- package/dist/chat/chat-session.js +50 -0
- package/dist/chat/chat-status.d.ts +1 -0
- package/dist/chat/chat-status.js +3 -0
- package/dist/chat/chat-time.d.ts +18 -0
- package/dist/chat/chat-time.js +90 -0
- package/dist/chat/chat-tool-display.d.ts +17 -0
- package/dist/chat/chat-tool-display.js +213 -0
- package/dist/chat/chat-tool-identity.d.ts +4 -0
- package/dist/chat/chat-tool-identity.js +42 -0
- package/dist/chat/chat-trace.d.ts +6 -0
- package/dist/chat/chat-trace.js +256 -0
- package/dist/chat/live-runtime-unavailable.d.ts +1 -0
- package/dist/chat/live-runtime-unavailable.js +1 -0
- package/dist/commands/agent.d.ts +8 -0
- package/dist/commands/agent.js +38 -0
- package/dist/commands/catalog.d.ts +18 -0
- package/dist/commands/catalog.js +115 -0
- package/dist/commands/discovery.d.ts +36 -0
- package/dist/commands/discovery.js +151 -0
- package/dist/commands/groups.d.ts +16 -0
- package/dist/commands/groups.js +121 -0
- package/dist/commands/inspect.d.ts +18 -0
- package/dist/commands/inspect.js +29 -0
- package/dist/commands/markdown.d.ts +21 -0
- package/dist/commands/markdown.js +117 -0
- package/dist/commands/parser.d.ts +4 -0
- package/dist/commands/parser.js +24 -0
- package/dist/commands/reserved.d.ts +4 -0
- package/dist/commands/reserved.js +107 -0
- package/dist/commands/router.d.ts +17 -0
- package/dist/commands/router.js +170 -0
- package/dist/commands/seed.d.ts +4 -0
- package/dist/commands/seed.js +8 -0
- package/dist/commands/types.d.ts +54 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/user-agents-node.d.ts +3 -0
- package/dist/commands/user-agents-node.js +52 -0
- package/dist/commands/user-agents.d.ts +22 -0
- package/dist/commands/user-agents.js +40 -0
- package/dist/conversations/approval-grants.d.ts +20 -0
- package/dist/conversations/approval-grants.js +75 -0
- package/dist/conversations/binding-state.d.ts +6 -0
- package/dist/conversations/binding-state.js +35 -0
- package/dist/conversations/context-handoff.d.ts +25 -0
- package/dist/conversations/context-handoff.js +207 -0
- package/dist/conversations/conversation-index-coordinator.d.ts +18 -0
- package/dist/conversations/conversation-index-coordinator.js +81 -0
- package/dist/conversations/conversation-index.d.ts +16 -0
- package/dist/conversations/conversation-index.js +125 -0
- package/dist/conversations/conversation-locator.d.ts +2 -0
- package/dist/conversations/conversation-locator.js +20 -0
- package/dist/conversations/conversation-palette.d.ts +18 -0
- package/dist/conversations/conversation-palette.js +29 -0
- package/dist/conversations/conversation-repository.d.ts +39 -0
- package/dist/conversations/conversation-repository.js +213 -0
- package/dist/conversations/handoff-summary.d.ts +48 -0
- package/dist/conversations/handoff-summary.js +269 -0
- package/dist/conversations/hashes.d.ts +5 -0
- package/dist/conversations/hashes.js +40 -0
- package/dist/conversations/history-creation-scope.d.ts +12 -0
- package/dist/conversations/history-creation-scope.js +44 -0
- package/dist/conversations/jsonl.d.ts +10 -0
- package/dist/conversations/jsonl.js +41 -0
- package/dist/conversations/memory-transcript-store.d.ts +15 -0
- package/dist/conversations/memory-transcript-store.js +119 -0
- package/dist/conversations/paths.d.ts +14 -0
- package/dist/conversations/paths.js +92 -0
- package/dist/conversations/redaction.d.ts +13 -0
- package/dist/conversations/redaction.js +94 -0
- package/dist/conversations/reveal-scope.d.ts +3 -0
- package/dist/conversations/reveal-scope.js +8 -0
- package/dist/conversations/scope-resolver.d.ts +11 -0
- package/dist/conversations/scope-resolver.js +18 -0
- package/dist/conversations/scope-tree.d.ts +10 -0
- package/dist/conversations/scope-tree.js +71 -0
- package/dist/conversations/transcript-projection.d.ts +9 -0
- package/dist/conversations/transcript-projection.js +409 -0
- package/dist/conversations/transcript-store.d.ts +24 -0
- package/dist/conversations/transcript-store.js +27 -0
- package/dist/conversations/types.d.ts +224 -0
- package/dist/conversations/types.js +12 -0
- package/dist/conversations/validation.d.ts +4 -0
- package/dist/conversations/validation.js +383 -0
- package/dist/conversations/vault-transcript-store.d.ts +22 -0
- package/dist/conversations/vault-transcript-store.js +219 -0
- package/dist/core/event-queue.d.ts +6 -0
- package/dist/core/event-queue.js +39 -0
- package/dist/core/types.d.ts +241 -0
- package/dist/core/types.js +50 -0
- package/dist/history/ai-history-panel.svelte +937 -0
- package/dist/history/ai-history-panel.svelte.d.ts +17 -0
- package/dist/history/ai-history-view-type.d.ts +1 -0
- package/dist/history/ai-history-view-type.js +1 -0
- package/dist/history/ai-history-view.d.ts +26 -0
- package/dist/history/ai-history-view.js +51 -0
- package/dist/history/conversation-history-tree.d.ts +15 -0
- package/dist/history/conversation-history-tree.js +71 -0
- package/dist/host/agent-workspace.d.ts +6 -0
- package/dist/host/agent-workspace.js +16 -0
- package/dist/host/create-host-runtimes.d.ts +2 -0
- package/dist/host/create-host-runtimes.js +14 -0
- package/dist/host/desktop-process-host.d.ts +6 -0
- package/dist/host/desktop-process-host.js +74 -0
- package/dist/host/host-runtime-availability.d.ts +1 -0
- package/dist/host/host-runtime-availability.js +3 -0
- package/dist/host/process-host.d.ts +31 -0
- package/dist/host/process-host.js +6 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +49 -0
- package/dist/jsonl/ai-jsonl-preview.d.ts +27 -0
- package/dist/jsonl/ai-jsonl-preview.js +68 -0
- package/dist/jsonl/ai-jsonl-view-type.d.ts +1 -0
- package/dist/jsonl/ai-jsonl-view-type.js +1 -0
- package/dist/jsonl/ai-jsonl-view.d.ts +16 -0
- package/dist/jsonl/ai-jsonl-view.js +57 -0
- package/dist/jsonl/ai-jsonl-view.svelte +260 -0
- package/dist/jsonl/ai-jsonl-view.svelte.d.ts +9 -0
- package/dist/memory/candidate-extractor.d.ts +25 -0
- package/dist/memory/candidate-extractor.js +191 -0
- package/dist/memory/forget-confirmation.d.ts +3 -0
- package/dist/memory/forget-confirmation.js +45 -0
- package/dist/memory/hashes.d.ts +5 -0
- package/dist/memory/hashes.js +37 -0
- package/dist/memory/memory-ingestion-coordinator.d.ts +19 -0
- package/dist/memory/memory-ingestion-coordinator.js +108 -0
- package/dist/memory/memory-record-store.d.ts +62 -0
- package/dist/memory/memory-record-store.js +369 -0
- package/dist/memory/memory-scheduler.d.ts +21 -0
- package/dist/memory/memory-scheduler.js +143 -0
- package/dist/memory/memory-tools.d.ts +61 -0
- package/dist/memory/memory-tools.js +99 -0
- package/dist/memory/native-memory-service.d.ts +72 -0
- package/dist/memory/native-memory-service.js +1256 -0
- package/dist/memory/paths.d.ts +13 -0
- package/dist/memory/paths.js +60 -0
- package/dist/memory/runtime-consolidation-provider.d.ts +18 -0
- package/dist/memory/runtime-consolidation-provider.js +154 -0
- package/dist/memory/types.d.ts +139 -0
- package/dist/memory/types.js +1 -0
- package/dist/providers/acp-model-provider.d.ts +11 -0
- package/dist/providers/acp-model-provider.js +99 -0
- package/dist/providers/codex-model-catalog.d.ts +7 -0
- package/dist/providers/codex-model-catalog.js +179 -0
- package/dist/providers/codex-model-provider.d.ts +12 -0
- package/dist/providers/codex-model-provider.js +42 -0
- package/dist/providers/model-provider.d.ts +26 -0
- package/dist/providers/model-provider.js +26 -0
- package/dist/public-components.d.ts +3 -0
- package/dist/public-components.js +3 -0
- package/dist/registry/runtime-registry.d.ts +11 -0
- package/dist/registry/runtime-registry.js +71 -0
- package/dist/registry/select-runtime.d.ts +9 -0
- package/dist/registry/select-runtime.js +31 -0
- package/dist/runtime-adapters.d.ts +16 -0
- package/dist/runtime-adapters.js +9 -0
- package/dist/runtimes/acp/acp-event-mapper.d.ts +56 -0
- package/dist/runtimes/acp/acp-event-mapper.js +237 -0
- package/dist/runtimes/acp/acp-runtime.d.ts +46 -0
- package/dist/runtimes/acp/acp-runtime.js +165 -0
- package/dist/runtimes/acp/desktop-acp-backend.d.ts +16 -0
- package/dist/runtimes/acp/desktop-acp-backend.js +243 -0
- package/dist/runtimes/codex/app-server-protocol.d.ts +28 -0
- package/dist/runtimes/codex/app-server-protocol.js +274 -0
- package/dist/runtimes/codex/codex-runtime.d.ts +24 -0
- package/dist/runtimes/codex/codex-runtime.js +318 -0
- package/dist/runtimes/fake/fake-runtime.d.ts +51 -0
- package/dist/runtimes/fake/fake-runtime.js +257 -0
- package/dist/sessions/plugin-data.d.ts +8 -0
- package/dist/sessions/plugin-data.js +29 -0
- package/dist/sessions/session-store.d.ts +47 -0
- package/dist/sessions/session-store.js +117 -0
- package/dist/settings/acp-agents.d.ts +8 -0
- package/dist/settings/acp-agents.js +11 -0
- package/dist/settings/ai-settings-tab.d.ts +8 -0
- package/dist/settings/ai-settings-tab.js +249 -0
- package/dist/settings/ai-settings.d.ts +43 -0
- package/dist/settings/ai-settings.js +147 -0
- package/dist/settings/app-tool-setting-rows.d.ts +16 -0
- package/dist/settings/app-tool-setting-rows.js +30 -0
- package/dist/settings/register-ai-settings.d.ts +3 -0
- package/dist/settings/register-ai-settings.js +393 -0
- package/dist/skills/activation.d.ts +2 -0
- package/dist/skills/activation.js +20 -0
- package/dist/skills/bundled/lapis-notes.d.ts +3 -0
- package/dist/skills/bundled/lapis-notes.js +52 -0
- package/dist/skills/bundled/research.d.ts +6 -0
- package/dist/skills/bundled/research.js +29 -0
- package/dist/skills/discovery.d.ts +21 -0
- package/dist/skills/discovery.js +227 -0
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +39 -0
- package/dist/skills/parser.d.ts +12 -0
- package/dist/skills/parser.js +124 -0
- package/dist/skills/registry.d.ts +21 -0
- package/dist/skills/registry.js +89 -0
- package/dist/skills/seed.d.ts +5 -0
- package/dist/skills/seed.js +18 -0
- package/dist/skills/skill-tools.d.ts +8 -0
- package/dist/skills/skill-tools.js +113 -0
- package/dist/skills/types.d.ts +67 -0
- package/dist/skills/types.js +1 -0
- package/dist/styles.css +514 -0
- package/dist/tools/app-tool-host.d.ts +47 -0
- package/dist/tools/app-tool-host.js +419 -0
- package/dist/tools/desktop-app-tool-bridge.d.ts +23 -0
- package/dist/tools/desktop-app-tool-bridge.js +223 -0
- package/dist/tools/mcp-server-registry.d.ts +7 -0
- package/dist/tools/mcp-server-registry.js +30 -0
- package/manifest.json +34 -0
- package/package.json +83 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const AI_CONVERSATION_SEARCH_PROVIDER_ID = "ai-conversations";
|
|
2
|
+
const INDEX_PATH_PREFIX = "ai-conversation";
|
|
3
|
+
export class AiConversationIndex {
|
|
4
|
+
repository;
|
|
5
|
+
database;
|
|
6
|
+
constructor(repository, database) {
|
|
7
|
+
this.repository = repository;
|
|
8
|
+
this.database = database;
|
|
9
|
+
}
|
|
10
|
+
async sync(location) {
|
|
11
|
+
const snapshot = await this.repository.read(location);
|
|
12
|
+
const document = conversationSearchDocument(snapshot);
|
|
13
|
+
const existing = await this.database.getSearchDocument(document.path);
|
|
14
|
+
if (existing?.checksum === document.checksum)
|
|
15
|
+
return;
|
|
16
|
+
await this.database.upsertSearchDocument(document);
|
|
17
|
+
}
|
|
18
|
+
delete(location) {
|
|
19
|
+
return this.database.deleteSearchDocument(conversationIndexPath(location));
|
|
20
|
+
}
|
|
21
|
+
async search(query, limit = 100) {
|
|
22
|
+
if (!query.trim()) {
|
|
23
|
+
return (await this.repository.listAll()).slice(0, limit);
|
|
24
|
+
}
|
|
25
|
+
const results = await this.database.searchDocuments(query, {
|
|
26
|
+
sourceProviderIds: [AI_CONVERSATION_SEARCH_PROVIDER_ID],
|
|
27
|
+
mode: "lexical",
|
|
28
|
+
limit,
|
|
29
|
+
});
|
|
30
|
+
return results.flatMap((result) => {
|
|
31
|
+
const decoded = decodeConversationIndexDocument(result.document);
|
|
32
|
+
if (!decoded)
|
|
33
|
+
return [];
|
|
34
|
+
const preview = result.snippets.find((snippet) => snippet.field === "content")?.text;
|
|
35
|
+
return [{ ...decoded, ...(preview ? { preview } : {}) }];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async rebuild() {
|
|
39
|
+
const sourceEntries = await this.repository.listAll();
|
|
40
|
+
const sourcePaths = new Set();
|
|
41
|
+
for (const entry of sourceEntries) {
|
|
42
|
+
if (entry.unavailableReason)
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
const snapshot = await this.repository.read(entry.location);
|
|
46
|
+
const document = conversationSearchDocument(snapshot);
|
|
47
|
+
sourcePaths.add(document.path);
|
|
48
|
+
await this.database.upsertSearchDocument(document);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// One unavailable conversation must not prevent history repair.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const indexed = (await this.database.listSearchDocuments()).filter((document) => document.sourceProviderId === AI_CONVERSATION_SEARCH_PROVIDER_ID);
|
|
55
|
+
for (const document of indexed) {
|
|
56
|
+
if (!sourcePaths.has(document.path)) {
|
|
57
|
+
await this.database.deleteSearchDocument(document.path);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function conversationIndexPath(location) {
|
|
63
|
+
return `${INDEX_PATH_PREFIX}/${encodeURIComponent(location.scopeDir || ".")}/${location.conversationId}`;
|
|
64
|
+
}
|
|
65
|
+
export function conversationSearchDocument(snapshot) {
|
|
66
|
+
const content = [
|
|
67
|
+
snapshot.metadata.title,
|
|
68
|
+
...projectSearchableTranscript(snapshot),
|
|
69
|
+
]
|
|
70
|
+
.filter((value) => Boolean(value?.trim()))
|
|
71
|
+
.join("\n");
|
|
72
|
+
const metadataText = JSON.stringify({
|
|
73
|
+
location: snapshot.location,
|
|
74
|
+
metadata: snapshot.metadata,
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
path: conversationIndexPath(snapshot.location),
|
|
78
|
+
sourceProviderId: AI_CONVERSATION_SEARCH_PROVIDER_ID,
|
|
79
|
+
name: snapshot.metadata.title ?? "Untitled conversation",
|
|
80
|
+
extension: "ai-conversation",
|
|
81
|
+
checksum: stableChecksum(`${content}\u0000${metadataText}`),
|
|
82
|
+
content,
|
|
83
|
+
metadataText,
|
|
84
|
+
tags: [],
|
|
85
|
+
tagParts: [],
|
|
86
|
+
tagHierarchy: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function projectSearchableTranscript(snapshot) {
|
|
90
|
+
return snapshot.transcript.flatMap((entry) => {
|
|
91
|
+
if (entry.type === "message" || entry.type === "thinking.summary") {
|
|
92
|
+
return [entry.text];
|
|
93
|
+
}
|
|
94
|
+
if (entry.type === "tool") {
|
|
95
|
+
return [entry.name, ...(entry.input ? [entry.input] : [])];
|
|
96
|
+
}
|
|
97
|
+
return [];
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function decodeConversationIndexDocument(document) {
|
|
101
|
+
if (document.sourceProviderId !== AI_CONVERSATION_SEARCH_PROVIDER_ID ||
|
|
102
|
+
!document.metadataText) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(document.metadataText);
|
|
107
|
+
if (!parsed.location || !parsed.metadata)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
location: structuredClone(parsed.location),
|
|
111
|
+
metadata: structuredClone(parsed.metadata),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function stableChecksum(value) {
|
|
119
|
+
let hash = 2166136261;
|
|
120
|
+
for (const character of value) {
|
|
121
|
+
hash ^= character.codePointAt(0) ?? 0;
|
|
122
|
+
hash = Math.imul(hash, 16777619);
|
|
123
|
+
}
|
|
124
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
125
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { normalizePortableVaultPath } from "./paths";
|
|
2
|
+
export function relocateConversationLocation(location, oldPath, newPath) {
|
|
3
|
+
const previous = normalizePortableVaultPath(oldPath, {
|
|
4
|
+
allowRoot: true,
|
|
5
|
+
label: "Previous vault path",
|
|
6
|
+
});
|
|
7
|
+
const next = normalizePortableVaultPath(newPath, {
|
|
8
|
+
allowRoot: true,
|
|
9
|
+
label: "New vault path",
|
|
10
|
+
});
|
|
11
|
+
if (location.scopeDir !== previous &&
|
|
12
|
+
!location.scopeDir.startsWith(`${previous}/`)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const suffix = location.scopeDir.slice(previous.length).replace(/^\//u, "");
|
|
16
|
+
return {
|
|
17
|
+
...location,
|
|
18
|
+
scopeDir: [next, suffix].filter(Boolean).join("/"),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConversationListEntry } from "./transcript-store";
|
|
2
|
+
export declare const AI_CONVERSATION_PALETTE_TAB: {
|
|
3
|
+
readonly id: "agents";
|
|
4
|
+
readonly label: "Agents";
|
|
5
|
+
readonly order: 10;
|
|
6
|
+
};
|
|
7
|
+
export declare const AI_CONVERSATION_PALETTE_PROVIDER_ID = "lapis-ai-conversations";
|
|
8
|
+
export declare function conversationPaletteTitle(entry: ConversationListEntry): string;
|
|
9
|
+
export declare function conversationPaletteItem(entry: ConversationListEntry, now?: Date): {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle: string | undefined;
|
|
13
|
+
icon: string;
|
|
14
|
+
providerId: string;
|
|
15
|
+
tab: "agents";
|
|
16
|
+
group: string;
|
|
17
|
+
trailing: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { formatChatDateLabel, formatChatRelativeAge, } from "../chat/chat-time";
|
|
2
|
+
export const AI_CONVERSATION_PALETTE_TAB = {
|
|
3
|
+
id: "agents",
|
|
4
|
+
label: "Agents",
|
|
5
|
+
order: 10,
|
|
6
|
+
};
|
|
7
|
+
export const AI_CONVERSATION_PALETTE_PROVIDER_ID = "lapis-ai-conversations";
|
|
8
|
+
export function conversationPaletteTitle(entry) {
|
|
9
|
+
const title = entry.metadata?.title?.trim();
|
|
10
|
+
if (title)
|
|
11
|
+
return title;
|
|
12
|
+
const preview = entry.preview?.trim();
|
|
13
|
+
if (preview)
|
|
14
|
+
return preview;
|
|
15
|
+
return "Untitled chat";
|
|
16
|
+
}
|
|
17
|
+
export function conversationPaletteItem(entry, now = new Date()) {
|
|
18
|
+
const updatedAt = entry.metadata?.updatedAt ?? entry.metadata?.createdAt ?? "";
|
|
19
|
+
return {
|
|
20
|
+
id: `ai-conversation:${entry.location.scopeDir}:${entry.location.conversationId}`,
|
|
21
|
+
title: conversationPaletteTitle(entry),
|
|
22
|
+
subtitle: entry.location.scopeDir || entry.preview,
|
|
23
|
+
icon: "sparkles",
|
|
24
|
+
providerId: AI_CONVERSATION_PALETTE_PROVIDER_ID,
|
|
25
|
+
tab: AI_CONVERSATION_PALETTE_TAB.id,
|
|
26
|
+
group: formatChatDateLabel(updatedAt, now) || "Agents",
|
|
27
|
+
trailing: formatChatRelativeAge(updatedAt, now),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DurableSanitizationOptions } from "./redaction";
|
|
2
|
+
import type { ConversationListEntry, TranscriptStore } from "./transcript-store";
|
|
3
|
+
import { type AgentBindingRecord, type ConversationApprovalGrant, type ConversationLocation, type ConversationSnapshot, type TranscriptEntry } from "./types";
|
|
4
|
+
export type CreateConversationInput = {
|
|
5
|
+
scopeDir: string;
|
|
6
|
+
launchNotePath?: string;
|
|
7
|
+
workspacePath?: string;
|
|
8
|
+
now?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
};
|
|
11
|
+
export type ConversationRepositoryChange = {
|
|
12
|
+
type: "upsert";
|
|
13
|
+
location: ConversationLocation;
|
|
14
|
+
} | {
|
|
15
|
+
type: "delete";
|
|
16
|
+
location: ConversationLocation;
|
|
17
|
+
};
|
|
18
|
+
export declare function deriveConversationTitle(text: string): string | undefined;
|
|
19
|
+
export declare class ConversationRepository {
|
|
20
|
+
private readonly store;
|
|
21
|
+
private readonly sanitization;
|
|
22
|
+
private readonly queue;
|
|
23
|
+
private readonly listeners;
|
|
24
|
+
constructor(store: TranscriptStore, sanitization?: DurableSanitizationOptions);
|
|
25
|
+
create(input: CreateConversationInput): Promise<ConversationSnapshot>;
|
|
26
|
+
read(location: ConversationLocation): Promise<ConversationSnapshot>;
|
|
27
|
+
list(scopeDir: string): Promise<ConversationListEntry[]>;
|
|
28
|
+
listAll(): Promise<ConversationListEntry[]>;
|
|
29
|
+
subscribe(listener: (change: ConversationRepositoryChange) => void): () => void;
|
|
30
|
+
appendTranscript(location: ConversationLocation, entries: TranscriptEntry[]): Promise<ConversationSnapshot>;
|
|
31
|
+
appendAgentRecords(location: ConversationLocation, records: AgentBindingRecord[]): Promise<ConversationSnapshot>;
|
|
32
|
+
writeApprovalGrants(location: ConversationLocation, grants: ConversationApprovalGrant[]): Promise<ConversationSnapshot>;
|
|
33
|
+
writePinned(location: ConversationLocation, pinned: boolean): Promise<ConversationSnapshot>;
|
|
34
|
+
archive(location: ConversationLocation, archived?: boolean): Promise<ConversationSnapshot>;
|
|
35
|
+
activateBinding(location: ConversationLocation, bindingId: string, switchEntry?: TranscriptEntry): Promise<ConversationSnapshot>;
|
|
36
|
+
delete(location: ConversationLocation): Promise<void>;
|
|
37
|
+
private emit;
|
|
38
|
+
private sanitizeEntry;
|
|
39
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { normalizeApprovalGrants } from "./approval-grants";
|
|
2
|
+
import { normalizePortableVaultPath, relativePathWithinScope } from "./paths";
|
|
3
|
+
import { sanitizeDurableField } from "./redaction";
|
|
4
|
+
import { conversationStorageKey, ConversationWriteQueue, } from "./transcript-store";
|
|
5
|
+
import { CONVERSATION_SCHEMA_VERSION, } from "./types";
|
|
6
|
+
export function deriveConversationTitle(text) {
|
|
7
|
+
const normalized = text.replace(/\s+/gu, " ").trim();
|
|
8
|
+
if (!normalized)
|
|
9
|
+
return undefined;
|
|
10
|
+
return [...normalized].slice(0, 80).join("");
|
|
11
|
+
}
|
|
12
|
+
export class ConversationRepository {
|
|
13
|
+
store;
|
|
14
|
+
sanitization;
|
|
15
|
+
queue = new ConversationWriteQueue();
|
|
16
|
+
listeners = new Set();
|
|
17
|
+
constructor(store, sanitization = {}) {
|
|
18
|
+
this.store = store;
|
|
19
|
+
this.sanitization = sanitization;
|
|
20
|
+
}
|
|
21
|
+
async create(input) {
|
|
22
|
+
const conversationId = input.id ?? crypto.randomUUID();
|
|
23
|
+
const location = { scopeDir: input.scopeDir, conversationId };
|
|
24
|
+
const now = input.now ?? new Date().toISOString();
|
|
25
|
+
const metadata = {
|
|
26
|
+
schemaVersion: CONVERSATION_SCHEMA_VERSION,
|
|
27
|
+
id: conversationId,
|
|
28
|
+
createdAt: now,
|
|
29
|
+
updatedAt: now,
|
|
30
|
+
status: "active",
|
|
31
|
+
...(input.launchNotePath
|
|
32
|
+
? {
|
|
33
|
+
launchContext: {
|
|
34
|
+
notePath: relativePathWithinScope(input.scopeDir, input.launchNotePath),
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
: {}),
|
|
38
|
+
...(input.workspacePath
|
|
39
|
+
? {
|
|
40
|
+
workspace: {
|
|
41
|
+
path: normalizePortableVaultPath(input.workspacePath, {
|
|
42
|
+
allowRoot: true,
|
|
43
|
+
label: "Workspace reference",
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
: {}),
|
|
48
|
+
};
|
|
49
|
+
const snapshot = await this.store.create(location, metadata);
|
|
50
|
+
this.emit({ type: "upsert", location: snapshot.location });
|
|
51
|
+
return snapshot;
|
|
52
|
+
}
|
|
53
|
+
read(location) {
|
|
54
|
+
return this.store.read(location);
|
|
55
|
+
}
|
|
56
|
+
list(scopeDir) {
|
|
57
|
+
return this.store.list(scopeDir);
|
|
58
|
+
}
|
|
59
|
+
listAll() {
|
|
60
|
+
return this.store.listAll();
|
|
61
|
+
}
|
|
62
|
+
subscribe(listener) {
|
|
63
|
+
this.listeners.add(listener);
|
|
64
|
+
return () => this.listeners.delete(listener);
|
|
65
|
+
}
|
|
66
|
+
async appendTranscript(location, entries) {
|
|
67
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
68
|
+
const safeEntries = entries.map((entry) => this.sanitizeEntry(entry));
|
|
69
|
+
await this.store.appendTranscriptEntries(location, safeEntries);
|
|
70
|
+
const snapshot = await this.store.read(location);
|
|
71
|
+
const firstUserMessage = snapshot.transcript.find((entry) => entry.type === "message" && entry.role === "user");
|
|
72
|
+
const title = snapshot.metadata.title ??
|
|
73
|
+
deriveConversationTitle(firstUserMessage?.type === "message" ? firstUserMessage.text : "");
|
|
74
|
+
const updatedAt = safeEntries.at(-1)?.createdAt ?? new Date().toISOString();
|
|
75
|
+
const metadata = {
|
|
76
|
+
...snapshot.metadata,
|
|
77
|
+
...(title ? { title } : {}),
|
|
78
|
+
updatedAt,
|
|
79
|
+
};
|
|
80
|
+
await this.store.writeMetadata(location, metadata);
|
|
81
|
+
const result = { ...snapshot, metadata };
|
|
82
|
+
this.emit({ type: "upsert", location: result.location });
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async appendAgentRecords(location, records) {
|
|
87
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
88
|
+
await this.store.appendAgentRecords(location, records);
|
|
89
|
+
const snapshot = await this.store.read(location);
|
|
90
|
+
const updatedAt = records.at(-1)?.createdAt ?? new Date().toISOString();
|
|
91
|
+
const activeBinding = [...records]
|
|
92
|
+
.reverse()
|
|
93
|
+
.find((record) => record.type === "binding.created");
|
|
94
|
+
const metadata = {
|
|
95
|
+
...snapshot.metadata,
|
|
96
|
+
updatedAt,
|
|
97
|
+
...(activeBinding ? { activeAgentBindingId: activeBinding.id } : {}),
|
|
98
|
+
};
|
|
99
|
+
await this.store.writeMetadata(location, metadata);
|
|
100
|
+
const result = { ...snapshot, metadata };
|
|
101
|
+
this.emit({ type: "upsert", location: result.location });
|
|
102
|
+
return result;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async writeApprovalGrants(location, grants) {
|
|
106
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
107
|
+
const snapshot = await this.store.read(location);
|
|
108
|
+
const approvalGrants = normalizeApprovalGrants(grants);
|
|
109
|
+
const metadata = {
|
|
110
|
+
...snapshot.metadata,
|
|
111
|
+
updatedAt: new Date().toISOString(),
|
|
112
|
+
};
|
|
113
|
+
if (approvalGrants.length > 0)
|
|
114
|
+
metadata.approvalGrants = approvalGrants;
|
|
115
|
+
else
|
|
116
|
+
delete metadata.approvalGrants;
|
|
117
|
+
await this.store.writeMetadata(location, metadata);
|
|
118
|
+
const result = { ...snapshot, metadata };
|
|
119
|
+
this.emit({ type: "upsert", location: result.location });
|
|
120
|
+
return result;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async writePinned(location, pinned) {
|
|
124
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
125
|
+
const snapshot = await this.store.read(location);
|
|
126
|
+
const metadata = {
|
|
127
|
+
...snapshot.metadata,
|
|
128
|
+
updatedAt: new Date().toISOString(),
|
|
129
|
+
};
|
|
130
|
+
if (pinned)
|
|
131
|
+
metadata.pinned = true;
|
|
132
|
+
else
|
|
133
|
+
delete metadata.pinned;
|
|
134
|
+
await this.store.writeMetadata(location, metadata);
|
|
135
|
+
const result = { ...snapshot, metadata };
|
|
136
|
+
this.emit({ type: "upsert", location: result.location });
|
|
137
|
+
return result;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async archive(location, archived = true) {
|
|
141
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
142
|
+
const snapshot = await this.store.read(location);
|
|
143
|
+
const metadata = {
|
|
144
|
+
...snapshot.metadata,
|
|
145
|
+
status: archived ? "archived" : "active",
|
|
146
|
+
updatedAt: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
await this.store.writeMetadata(location, metadata);
|
|
149
|
+
const result = { ...snapshot, metadata };
|
|
150
|
+
this.emit({ type: "upsert", location: result.location });
|
|
151
|
+
return result;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async activateBinding(location, bindingId, switchEntry) {
|
|
155
|
+
return this.queue.run(conversationStorageKey(location), async () => {
|
|
156
|
+
const snapshot = await this.store.read(location);
|
|
157
|
+
const binding = snapshot.agents.find((record) => record.type === "binding.created" && record.id === bindingId);
|
|
158
|
+
if (!binding)
|
|
159
|
+
throw new Error(`Unknown agent binding: ${bindingId}`);
|
|
160
|
+
if (switchEntry) {
|
|
161
|
+
await this.store.appendTranscriptEntries(location, [
|
|
162
|
+
this.sanitizeEntry(switchEntry),
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
const metadata = {
|
|
166
|
+
...snapshot.metadata,
|
|
167
|
+
activeAgentBindingId: bindingId,
|
|
168
|
+
updatedAt: switchEntry?.createdAt ?? new Date().toISOString(),
|
|
169
|
+
};
|
|
170
|
+
await this.store.writeMetadata(location, metadata);
|
|
171
|
+
const result = await this.store.read(location);
|
|
172
|
+
this.emit({ type: "upsert", location: result.location });
|
|
173
|
+
return result;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async delete(location) {
|
|
177
|
+
await this.store.delete(location);
|
|
178
|
+
this.emit({ type: "delete", location: { ...location } });
|
|
179
|
+
}
|
|
180
|
+
emit(change) {
|
|
181
|
+
for (const listener of this.listeners)
|
|
182
|
+
listener(change);
|
|
183
|
+
}
|
|
184
|
+
sanitizeEntry(entry) {
|
|
185
|
+
if (entry.type === "tool") {
|
|
186
|
+
const input = sanitizeDurableField(entry.input, this.sanitization);
|
|
187
|
+
const output = sanitizeDurableField(entry.output, this.sanitization);
|
|
188
|
+
return {
|
|
189
|
+
...entry,
|
|
190
|
+
input: input.text,
|
|
191
|
+
output: output.text,
|
|
192
|
+
redacted: entry.redacted || input.redacted || output.redacted || undefined,
|
|
193
|
+
truncated: entry.truncated || input.truncated || output.truncated || undefined,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (entry.type === "approval.request" && entry.tool?.input) {
|
|
197
|
+
const input = sanitizeDurableField(entry.tool.input, this.sanitization);
|
|
198
|
+
return {
|
|
199
|
+
...entry,
|
|
200
|
+
tool: { ...entry.tool, input: input.text },
|
|
201
|
+
redacted: entry.redacted || input.redacted || undefined,
|
|
202
|
+
truncated: entry.truncated || input.truncated || undefined,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (entry.type === "error") {
|
|
206
|
+
return {
|
|
207
|
+
...entry,
|
|
208
|
+
message: sanitizeDurableField(entry.message, this.sanitization).text ?? "",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return entry;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AgentRequest, AgentRuntime } from "../core/types";
|
|
2
|
+
import type { ConversationRepository } from "./conversation-repository";
|
|
3
|
+
import { type ConversationLocation } from "./types";
|
|
4
|
+
export type GroundedHandoffSummaryInput = {
|
|
5
|
+
conversationId: string;
|
|
6
|
+
fromEntryId: string;
|
|
7
|
+
throughEntryId: string;
|
|
8
|
+
sourceHash: string;
|
|
9
|
+
entries: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
type: string;
|
|
13
|
+
role?: "user" | "assistant";
|
|
14
|
+
agentBindingId?: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
export interface HandoffSummaryProvider {
|
|
19
|
+
propose(input: GroundedHandoffSummaryInput, signal?: AbortSignal): Promise<{
|
|
20
|
+
summary: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export type HandoffSummaryProcessorIdentity = {
|
|
24
|
+
runtime: "acp" | "codex-native";
|
|
25
|
+
agent: "codex" | "cursor";
|
|
26
|
+
model: string;
|
|
27
|
+
};
|
|
28
|
+
export declare class HandoffSummaryCoordinator {
|
|
29
|
+
#private;
|
|
30
|
+
private readonly repository;
|
|
31
|
+
private readonly provider;
|
|
32
|
+
private readonly processor;
|
|
33
|
+
private readonly enabled;
|
|
34
|
+
constructor(repository: ConversationRepository, provider: HandoffSummaryProvider, processor: HandoffSummaryProcessorIdentity | (() => HandoffSummaryProcessorIdentity), enabled?: () => boolean);
|
|
35
|
+
afterTerminal(location: ConversationLocation): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export type RuntimeHandoffSummaryProviderOptions = {
|
|
38
|
+
configuration: () => HandoffSummaryProcessorIdentity;
|
|
39
|
+
resolveRuntime: (request: AgentRequest) => Promise<AgentRuntime>;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
};
|
|
42
|
+
export declare class RuntimeHandoffSummaryProvider implements HandoffSummaryProvider {
|
|
43
|
+
private readonly options;
|
|
44
|
+
constructor(options: RuntimeHandoffSummaryProviderOptions);
|
|
45
|
+
propose(input: GroundedHandoffSummaryInput, signal?: AbortSignal): Promise<{
|
|
46
|
+
summary: string;
|
|
47
|
+
}>;
|
|
48
|
+
}
|