@vellumai/assistant 0.11.0-dev.202607301413.bdcf8bc → 0.11.0-dev.202607301449.cefb1f3
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/ARCHITECTURE.md +4 -4
- package/docs/architecture/memory.md +59 -0
- package/openapi.yaml +86 -1
- package/package.json +1 -1
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -4
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +1 -0
- package/src/cli/commands/memory/__tests__/memory-ingest.test.ts +591 -0
- package/src/cli/commands/memory/index.help.ts +55 -1
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-ingest.ts +273 -0
- package/src/config/schemas/memory-v3.ts +7 -7
- package/src/home/feed-source-enrichment.test.ts +7 -0
- package/src/home/feed-source-enrichment.ts +5 -0
- package/src/plugins/defaults/memory/AGENTS.md +10 -7
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +1 -0
- package/src/plugins/defaults/memory/src/__tests__/memory-ingest-routes.test.ts +176 -0
- package/src/plugins/defaults/memory/src/memory-ingest-routes.ts +125 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-lock.test.ts +136 -0
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +440 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +129 -2
- package/src/plugins/defaults/memory/substrate/__tests__/page-store.test.ts +31 -0
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +10 -218
- package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +250 -0
- package/src/plugins/defaults/memory/substrate/ingest.ts +360 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +110 -1
- package/src/plugins/defaults/memory/substrate/page-store.ts +20 -24
- package/src/plugins/defaults/memory/substrate/types.ts +12 -0
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/edge.test.ts +9 -1
- package/src/plugins/defaults/memory/v3/__tests__/fresh-set.test.ts +39 -5
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +2 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +4 -0
- package/src/plugins/defaults/memory/v3/card.ts +1 -1
- package/src/plugins/defaults/memory/v3/fresh-set.ts +24 -14
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +24 -17
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +6 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +8 -3
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +21 -9
- package/src/runtime/routes/conversations-import-routes.ts +28 -2
- package/src/runtime/routes/index.ts +2 -0
package/ARCHITECTURE.md
CHANGED
|
@@ -624,10 +624,10 @@ Real-time conversation chat message capture on macOS uses a WebSocket-based stre
|
|
|
624
624
|
|
|
625
625
|
Two provider adapters are supported, each implementing the `StreamingTranscriber` interface from `src/stt/types.ts`:
|
|
626
626
|
|
|
627
|
-
| Provider | Adapter | Mode | Mechanism
|
|
628
|
-
| ----------------- | ----------------------------------------------------------- | ------------- |
|
|
629
|
-
| **Deepgram** | `src/providers/speech-to-text/deepgram-realtime.ts` | `realtime-ws` | Opens a WebSocket to Deepgram's `/v1/listen` endpoint, forwards raw PCM audio, normalizes Deepgram's `is_final`/`speech_final` semantics into `partial`/`final` events. Uses model `nova-2`.
|
|
630
|
-
| **Google Gemini** | `src/providers/speech-to-text/google-gemini-live-stream.ts` | `realtime-ws` | Opens a bidirectional streaming session against Gemini's Live API (`ai.live.connect`), forwards PCM audio frames, and normalizes `serverContent.inputTranscription` events into `partial`/`final` events. Uses model `gemini-
|
|
627
|
+
| Provider | Adapter | Mode | Mechanism |
|
|
628
|
+
| ----------------- | ----------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
629
|
+
| **Deepgram** | `src/providers/speech-to-text/deepgram-realtime.ts` | `realtime-ws` | Opens a WebSocket to Deepgram's `/v1/listen` endpoint, forwards raw PCM audio, normalizes Deepgram's `is_final`/`speech_final` semantics into `partial`/`final` events. Uses model `nova-2`. |
|
|
630
|
+
| **Google Gemini** | `src/providers/speech-to-text/google-gemini-live-stream.ts` | `realtime-ws` | Opens a bidirectional streaming session against Gemini's Live API (`ai.live.connect`), forwards PCM audio frames, and normalizes `serverContent.inputTranscription` events into `partial`/`final` events. Uses model `gemini-2.5-flash-native-audio-latest`. |
|
|
631
631
|
|
|
632
632
|
**Provider-specific behavior differences:**
|
|
633
633
|
|
|
@@ -67,6 +67,9 @@ graph LR
|
|
|
67
67
|
CONS --> VIEWS["essentials / threads / recent"]
|
|
68
68
|
CONS --> REEMBED["memory_v2_reembed →<br/>concept-page Qdrant collection"]
|
|
69
69
|
CONS --> MAINT["memory_v3_maintain<br/>(v3-live follow-up)"]
|
|
70
|
+
ING["POST /v1/memory/ingest<br/>(deterministic batch import)"] --> PAGES
|
|
71
|
+
ING --> REEMBED
|
|
72
|
+
ING --> MAINT
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
- `handleRemember` (`graph/tool-handlers.ts`) appends timestamped bullets to
|
|
@@ -85,6 +88,62 @@ graph LR
|
|
|
85
88
|
failure-backoff-respecting);
|
|
86
89
|
- manual "Run now" via `POST /v1/consolidation/run-now`.
|
|
87
90
|
Failed runs enter an exponential backoff (transient vs billing curves).
|
|
91
|
+
- **Ingestion** (`substrate/ingest.ts`, exposed as `POST /v1/memory/ingest`;
|
|
92
|
+
generated HTTP operation id `memory_ingest_post`, IPC method
|
|
93
|
+
`memory_ingest`) is the second sanctioned writer of
|
|
94
|
+
`memory/concepts/`: a deterministic batch import of fully-formed,
|
|
95
|
+
page-shaped markdown (frontmatter + body) that bypasses the buffer
|
|
96
|
+
entirely. The bypass is load-bearing, not a shortcut: buffer timestamps
|
|
97
|
+
have minute precision, so a bulk append lands every entry on one shared
|
|
98
|
+
minute stamp, the chunker's timestamp cutoff cannot split inside that
|
|
99
|
+
minute, and its same-minute burst guard falls back to processing the
|
|
100
|
+
whole buffer in a single oversized run. Ingest writes validated pages
|
|
101
|
+
directly instead. Purely mechanical: each page is validated and reported
|
|
102
|
+
individually, writes hold the consolidation lock so a batch cannot
|
|
103
|
+
interleave with a consolidation pass, and a batch that wrote at least one
|
|
104
|
+
page enqueues the same reindex follow-ups as consolidation
|
|
105
|
+
(`memory_v2_reembed`, `memory_v3_maintain`). Consolidation remains the only
|
|
106
|
+
LLM-driven writer.
|
|
107
|
+
|
|
108
|
+
### Ingestion tracks and provenance
|
|
109
|
+
|
|
110
|
+
Two user-facing flows feed the ingest route, chosen by the shape of the
|
|
111
|
+
source material rather than its size:
|
|
112
|
+
|
|
113
|
+
- **Distilled import** (`skills/assistant-migration`): memory exported from
|
|
114
|
+
another assistant is parsed, creator-reviewed, and shaped into concept
|
|
115
|
+
pages. The input is already distilled, so everything approved becomes
|
|
116
|
+
pages; assistant exports route here regardless of size because they need
|
|
117
|
+
candidate parsing and creator review.
|
|
118
|
+
- **Large-corpus skim** (`skills/memory-corpus-ingest`): the raw dataset
|
|
119
|
+
(recording archives, document dumps) is cold-stored under a workspace
|
|
120
|
+
imports directory and never enters the memory corpus. Only a small map of
|
|
121
|
+
roughly 10 to 50 skim pages is ingested, each pointing back at the raw
|
|
122
|
+
files, and a drill-in retrieval skill authored alongside searches the cold
|
|
123
|
+
store on demand.
|
|
124
|
+
|
|
125
|
+
Both stage pages on disk and write them through the `assistant memory ingest`
|
|
126
|
+
CLI (`src/cli/commands/memory/memory-ingest.ts`: staging directory, JSON
|
|
127
|
+
manifest, or stdin; dry-run; overwrite; 200-page batches). The one exception
|
|
128
|
+
is a migration yielding only a handful of approved facts: assistant-migration
|
|
129
|
+
saves those through the normal `remember` tool, so they enter via the buffer
|
|
130
|
+
like any organic capture instead of the ingest route.
|
|
131
|
+
|
|
132
|
+
Ingested pages carry provenance frontmatter with distinct consumers:
|
|
133
|
+
|
|
134
|
+
- `source:` marks a page's origin, by convention `import:<provider>`
|
|
135
|
+
(e.g. `import:chatgpt`), so imported pages stay distinguishable from
|
|
136
|
+
consolidation-authored ones, which never carry the key.
|
|
137
|
+
- `origin_date:` (ISO 8601 date or datetime) declares when the page's
|
|
138
|
+
content is from and drives `PageIndexEntry.freshAt`, the effective-recency
|
|
139
|
+
signal: any finite parse is accepted (pre-epoch dates included; an
|
|
140
|
+
offset-less datetime is read as UTC), and an absent or unparseable value
|
|
141
|
+
falls back to file mtime. The v3 fresh lane ranks on `freshAt` and the
|
|
142
|
+
card's `dated` stamp renders it, so a backdated import ranks by what its
|
|
143
|
+
content is about rather than when it reached disk. `modifiedAt` stays raw
|
|
144
|
+
mtime because the maintain job's re-embed delta diffs on it. Synthetic
|
|
145
|
+
entries (skills, CLI commands) carry `freshAt: null` and are skipped by
|
|
146
|
+
recency ranking.
|
|
88
147
|
|
|
89
148
|
### Read paths
|
|
90
149
|
|
package/openapi.yaml
CHANGED
|
@@ -8264,7 +8264,10 @@ paths:
|
|
|
8264
8264
|
post:
|
|
8265
8265
|
operationId: conversations_import_post
|
|
8266
8266
|
summary: Import conversations
|
|
8267
|
-
description:
|
|
8267
|
+
description:
|
|
8268
|
+
Import conversations from a standard JSON payload. Created conversations record a provenance source of
|
|
8269
|
+
`import:<provider>` derived from the `sourceKey` prefix (e.g. `chatgpt:abc123` -> `import:chatgpt`), or
|
|
8270
|
+
`import:unknown` when no prefix is present.
|
|
8268
8271
|
tags:
|
|
8269
8272
|
- conversations
|
|
8270
8273
|
requestBody:
|
|
@@ -17742,6 +17745,88 @@ paths:
|
|
|
17742
17745
|
- confident
|
|
17743
17746
|
- notes
|
|
17744
17747
|
additionalProperties: false
|
|
17748
|
+
/v1/memory/ingest:
|
|
17749
|
+
post:
|
|
17750
|
+
operationId: memory_ingest_post
|
|
17751
|
+
summary:
|
|
17752
|
+
Batch-ingest fully-formed concept pages directly into memory/concepts/ (bypassing the consolidation buffer) and
|
|
17753
|
+
enqueue reindex jobs
|
|
17754
|
+
tags:
|
|
17755
|
+
- memory
|
|
17756
|
+
requestBody:
|
|
17757
|
+
required: true
|
|
17758
|
+
content:
|
|
17759
|
+
application/json:
|
|
17760
|
+
schema:
|
|
17761
|
+
type: object
|
|
17762
|
+
properties:
|
|
17763
|
+
pages:
|
|
17764
|
+
minItems: 1
|
|
17765
|
+
maxItems: 200
|
|
17766
|
+
type: array
|
|
17767
|
+
items:
|
|
17768
|
+
type: object
|
|
17769
|
+
properties:
|
|
17770
|
+
slug:
|
|
17771
|
+
type: string
|
|
17772
|
+
content:
|
|
17773
|
+
type: string
|
|
17774
|
+
required:
|
|
17775
|
+
- slug
|
|
17776
|
+
- content
|
|
17777
|
+
dryRun:
|
|
17778
|
+
type: boolean
|
|
17779
|
+
overwrite:
|
|
17780
|
+
type: boolean
|
|
17781
|
+
required:
|
|
17782
|
+
- pages
|
|
17783
|
+
responses:
|
|
17784
|
+
"200":
|
|
17785
|
+
description: Successful response
|
|
17786
|
+
content:
|
|
17787
|
+
application/json:
|
|
17788
|
+
schema:
|
|
17789
|
+
type: object
|
|
17790
|
+
properties:
|
|
17791
|
+
results:
|
|
17792
|
+
type: array
|
|
17793
|
+
items:
|
|
17794
|
+
type: object
|
|
17795
|
+
properties:
|
|
17796
|
+
slug:
|
|
17797
|
+
type: string
|
|
17798
|
+
action:
|
|
17799
|
+
type: string
|
|
17800
|
+
enum:
|
|
17801
|
+
- written
|
|
17802
|
+
- skipped_exists
|
|
17803
|
+
- invalid
|
|
17804
|
+
warnings:
|
|
17805
|
+
type: array
|
|
17806
|
+
items:
|
|
17807
|
+
type: string
|
|
17808
|
+
error:
|
|
17809
|
+
type: string
|
|
17810
|
+
required:
|
|
17811
|
+
- slug
|
|
17812
|
+
- action
|
|
17813
|
+
- warnings
|
|
17814
|
+
additionalProperties: false
|
|
17815
|
+
written:
|
|
17816
|
+
type: number
|
|
17817
|
+
skipped:
|
|
17818
|
+
type: number
|
|
17819
|
+
invalid:
|
|
17820
|
+
type: number
|
|
17821
|
+
dryRun:
|
|
17822
|
+
type: boolean
|
|
17823
|
+
required:
|
|
17824
|
+
- results
|
|
17825
|
+
- written
|
|
17826
|
+
- skipped
|
|
17827
|
+
- invalid
|
|
17828
|
+
- dryRun
|
|
17829
|
+
additionalProperties: false
|
|
17745
17830
|
/v1/memory/remember:
|
|
17746
17831
|
post:
|
|
17747
17832
|
operationId: memory_remember_post
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Route-level tests for the conversations import route.
|
|
3
|
+
*
|
|
4
|
+
* Covers two behaviors: non-renderable roles are never persisted (the
|
|
5
|
+
* messages store is UI-facing (`ConversationMessage`), so an imported export
|
|
6
|
+
* carrying agent-context `system` rows must land only its `user`/`assistant`
|
|
7
|
+
* turns; the `system` rows are dropped, not persisted), and imported
|
|
8
|
+
* conversations carry a provenance `source` derived from the `sourceKey`
|
|
9
|
+
* prefix (`import:<provider>`, or `import:unknown` when absent).
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -82,3 +86,96 @@ describe("conversations import system-row filtering", () => {
|
|
|
82
86
|
).toBe(false);
|
|
83
87
|
});
|
|
84
88
|
});
|
|
89
|
+
|
|
90
|
+
describe("conversations import provenance source", () => {
|
|
91
|
+
beforeEach(resetTables);
|
|
92
|
+
|
|
93
|
+
test("stamps import:<provider> from a prefixed sourceKey and keeps dedup", async () => {
|
|
94
|
+
// GIVEN an export whose sourceKey carries a provider prefix
|
|
95
|
+
const body = {
|
|
96
|
+
conversations: [
|
|
97
|
+
{
|
|
98
|
+
sourceKey: "chatgpt:abc123",
|
|
99
|
+
title: "ChatGPT import",
|
|
100
|
+
messages: [{ role: "user", content: "hello" }],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// WHEN the conversation is imported
|
|
106
|
+
const result = (await importHandler({
|
|
107
|
+
body,
|
|
108
|
+
} as unknown as RouteHandlerArgs)) as { imported: number };
|
|
109
|
+
expect(result.imported).toBe(1);
|
|
110
|
+
|
|
111
|
+
// THEN the created row records the provider-derived provenance source
|
|
112
|
+
const db = getDb();
|
|
113
|
+
const conv = db.select().from(conversations).all()[0];
|
|
114
|
+
expect(conv.source).toBe("import:chatgpt");
|
|
115
|
+
|
|
116
|
+
// AND re-importing the same sourceKey still dedups (no second row)
|
|
117
|
+
const again = (await importHandler({
|
|
118
|
+
body,
|
|
119
|
+
} as unknown as RouteHandlerArgs)) as { imported: number; skipped: number };
|
|
120
|
+
expect(again.imported).toBe(0);
|
|
121
|
+
expect(again.skipped).toBe(1);
|
|
122
|
+
expect(db.select().from(conversations).all()).toHaveLength(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("normalizes non-canonical prefixes instead of dropping them", async () => {
|
|
126
|
+
// GIVEN sourceKeys whose prefixes carry uppercase or underscore characters
|
|
127
|
+
const body = {
|
|
128
|
+
conversations: [
|
|
129
|
+
{
|
|
130
|
+
sourceKey: "OpenAI:abc",
|
|
131
|
+
title: "Uppercase prefix",
|
|
132
|
+
messages: [{ role: "user", content: "hello" }],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
sourceKey: "chat_gpt:def",
|
|
136
|
+
title: "Underscore prefix",
|
|
137
|
+
messages: [{ role: "user", content: "hello" }],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// WHEN the conversations are imported
|
|
143
|
+
const result = (await importHandler({
|
|
144
|
+
body,
|
|
145
|
+
} as unknown as RouteHandlerArgs)) as { imported: number };
|
|
146
|
+
expect(result.imported).toBe(2);
|
|
147
|
+
|
|
148
|
+
// THEN each prefix is normalized into import:<provider>, not import:unknown
|
|
149
|
+
const db = getDb();
|
|
150
|
+
const sources = db
|
|
151
|
+
.select()
|
|
152
|
+
.from(conversations)
|
|
153
|
+
.all()
|
|
154
|
+
.map((row) => row.source)
|
|
155
|
+
.sort();
|
|
156
|
+
expect(sources).toEqual(["import:chat-gpt", "import:openai"]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("falls back to import:unknown when sourceKey is absent", async () => {
|
|
160
|
+
// GIVEN an export entry with no sourceKey at all
|
|
161
|
+
const body = {
|
|
162
|
+
conversations: [
|
|
163
|
+
{
|
|
164
|
+
title: "Prefixless import",
|
|
165
|
+
messages: [{ role: "user", content: "hello" }],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// WHEN the conversation is imported
|
|
171
|
+
const result = (await importHandler({
|
|
172
|
+
body,
|
|
173
|
+
} as unknown as RouteHandlerArgs)) as { imported: number };
|
|
174
|
+
expect(result.imported).toBe(1);
|
|
175
|
+
|
|
176
|
+
// THEN the created row falls back to the unknown-provider source
|
|
177
|
+
const db = getDb();
|
|
178
|
+
const conv = db.select().from(conversations).all()[0];
|
|
179
|
+
expect(conv.source).toBe("import:unknown");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -147,6 +147,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
147
147
|
"../../../../runtime/auth/route-policy.js",
|
|
148
148
|
"../../../../runtime/background-job-runner.js",
|
|
149
149
|
"../../../../runtime/routes/errors.js",
|
|
150
|
+
"../../../../runtime/routes/parse-body.js",
|
|
150
151
|
"../../../../runtime/routes/types.js",
|
|
151
152
|
"../../../../security/secret-scanner.js",
|
|
152
153
|
"../../../../skills/catalog-cache.js",
|
|
@@ -73,6 +73,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
73
73
|
],
|
|
74
74
|
"image-recovery": ["src/daemon/conversation-error.ts"],
|
|
75
75
|
memory: [
|
|
76
|
+
"src/cli/commands/memory/memory-ingest.ts",
|
|
76
77
|
"src/cli/commands/memory/memory-retrospective.ts",
|
|
77
78
|
"src/cli/commands/memory/memory-v2-compare-render.ts",
|
|
78
79
|
"src/cli/commands/memory/memory-v2.ts",
|