@vellumai/assistant 0.12.2-staging.7 → 0.12.2
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/docs/architecture/memory.md +2 -11
- package/docs/desktop-browser-cli.md +2 -4
- package/node_modules/@vellumai/environments/src/shell.test.ts +0 -21
- package/node_modules/@vellumai/environments/src/shell.ts +0 -24
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +2 -8
- package/openapi.yaml +2 -6
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +0 -124
- package/src/__tests__/approval-interception-trust-gates.test.ts +0 -40
- package/src/__tests__/channel-approval.test.ts +14 -9
- package/src/__tests__/conversation-agent-loop.test.ts +0 -25
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -138
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +0 -169
- package/src/__tests__/terminal-tools.test.ts +0 -8
- package/src/__tests__/unicode.test.ts +0 -36
- package/src/agent/loop.ts +0 -19
- package/src/api/index.ts +0 -6
- package/src/approvals/approval-primitive.ts +2 -5
- package/src/approvals/scoped-approval-grants.ts +2 -6
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -43
- package/src/daemon/conversation-agent-loop.ts +0 -2
- package/src/daemon/conversation-tool-setup.ts +1 -57
- package/src/daemon/conversation.ts +0 -17
- package/src/daemon/daemon-control.ts +6 -2
- package/src/daemon/orphan-reaper.ts +3 -4
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-conversation-ops.ts +15 -38
- package/src/desktop/desktop-automation-lease.test.ts +0 -143
- package/src/desktop/desktop-automation-lease.ts +3 -39
- package/src/messaging/provider-message-metadata.ts +3 -3
- package/src/notifications/__tests__/copy-composer.test.ts +0 -70
- package/src/notifications/copy-composer.ts +3 -11
- package/src/oauth/seed-providers.ts +30 -0
- package/src/persistence/conversation-plugin-facade.ts +0 -13
- package/src/persistence/schema/index.ts +0 -1
- package/src/persistence/steps.ts +0 -2
- package/src/plugin-api/conversation-turn.ts +7 -31
- package/src/plugin-api/index.ts +1 -9
- package/src/plugins/defaults/memory/AGENTS.md +2 -14
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +0 -43
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +0 -46
- package/src/plugins/defaults/memory/buffer-format.ts +0 -40
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +2 -1
- package/src/plugins/defaults/memory/context-search/format.ts +1 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +1 -2
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +2 -1
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +42 -1
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +3 -4
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +181 -55
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +99 -407
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -10
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +7 -107
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +86 -307
- package/src/plugins/defaults/memory/substrate/page-index.ts +1 -2
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +49 -89
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +1 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +0 -5
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +2 -5
- package/src/plugins/defaults/memory/v2/reranker.ts +1 -2
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +1 -81
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +0 -87
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +0 -21
- package/src/plugins/defaults/memory/v3/card.ts +1 -2
- package/src/plugins/defaults/memory/v3/injector.ts +178 -212
- package/src/plugins/defaults/memory/v3/orchestrate.ts +22 -86
- package/src/plugins/defaults/memory/v3/pool-select.ts +7 -10
- package/src/plugins/defaults/memory/v3/sections.ts +1 -2
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +1 -10
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +46 -1
- package/src/runtime/AGENTS.md +1 -1
- package/src/runtime/__tests__/agent-wake.test.ts +1 -86
- package/src/runtime/agent-wake.ts +4 -20
- package/src/runtime/guardian-action-service.ts +17 -2
- package/src/runtime/guardian-reply-router.ts +8 -1
- package/src/runtime/routes/channel-route-shared.ts +9 -1
- package/src/runtime/routes/desktop-setup-routes.test.ts +2 -2
- package/src/runtime/routes/desktop-setup-routes.ts +3 -7
- package/src/runtime/routes/guardian-approval-interception.ts +0 -24
- package/src/runtime/routes/inbound-message-handler.ts +3 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +1 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -7
- package/src/schedule/run-script.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +6 -12
- package/src/tools/shared/filesystem/file-ops-service.ts +31 -1
- package/src/tools/shared/shell-output.test.ts +0 -10
- package/src/tools/shared/shell-output.ts +2 -14
- package/src/tools/skills/sandbox-runner.ts +2 -13
- package/src/tools/skills/scaffold-managed.ts +1 -2
- package/src/tools/terminal/__tests__/safe-env.test.ts +0 -29
- package/src/tools/terminal/safe-env.ts +1 -30
- package/src/tools/terminal/sanitized-bash.ts +2 -15
- package/src/tools/terminal/shell.test.ts +0 -29
- package/src/tools/terminal/shell.ts +7 -13
- package/src/util/host-process.test.ts +1 -17
- package/src/util/host-process.ts +0 -24
- package/src/util/unicode.ts +0 -29
- package/src/__tests__/db-conversation-tool-surface.test.ts +0 -144
- package/src/api/events/desktop-activity-changed.ts +0 -10
- package/src/persistence/conversation-tool-surface.ts +0 -86
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.test.ts +0 -78
- package/src/persistence/migrations/378-create-conversation-tool-surfaces.ts +0 -29
- package/src/persistence/schema/conversation-tool-surfaces.ts +0 -22
- package/src/plugin-api/plugin-channel-turn-trust.test.ts +0 -133
- package/src/plugin-api/plugin-channel-turn-trust.ts +0 -71
- package/src/plugins/defaults/memory/__tests__/buffer-file.test.ts +0 -320
- package/src/plugins/defaults/memory/__tests__/fixtures/buffer-appender.ts +0 -17
- package/src/plugins/defaults/memory/__tests__/memory-run-evidence.test.ts +0 -161
- package/src/plugins/defaults/memory/buffer-file.ts +0 -354
- package/src/plugins/defaults/memory/memory-run-evidence.ts +0 -213
- package/src/plugins/defaults/memory/substrate/consolidation-tool-surface.ts +0 -34
- package/src/tools/terminal/shell-launch.test.ts +0 -162
package/src/plugin-api/index.ts
CHANGED
|
@@ -286,11 +286,6 @@ export {
|
|
|
286
286
|
// writes files under the workspace (e.g. its own `plugins/<name>/data/`
|
|
287
287
|
// directory) resolves them against this instead of hardcoding a base path.
|
|
288
288
|
export { getWorkspaceDir } from "../util/platform.js";
|
|
289
|
-
// `String.prototype.slice` that never cuts a UTF-16 surrogate pair in half.
|
|
290
|
-
// Any text a plugin truncates by character budget and hands to a model must
|
|
291
|
-
// go through this: an orphaned half is invalid UTF-16 that strict provider
|
|
292
|
-
// parsers reject.
|
|
293
|
-
export { safeStringSlice } from "../util/unicode.js";
|
|
294
289
|
// Declarative help for the top-level `assistant` CLI commands that have adopted
|
|
295
290
|
// the static-help split. Plugins (e.g. the memory capability indexer) read this
|
|
296
291
|
// to embed CLI command capabilities without importing the CLI action graph.
|
|
@@ -364,8 +359,7 @@ export {
|
|
|
364
359
|
stringifyMessageContent,
|
|
365
360
|
} from "../persistence/message-content.js";
|
|
366
361
|
// Conversation history — reads and writes on the host conversation store
|
|
367
|
-
// (rows, message history, processing state,
|
|
368
|
-
// disk-view paths) plus the lexical
|
|
362
|
+
// (rows, message history, processing state, disk-view paths) plus the lexical
|
|
369
363
|
// message-search surface. Every operation takes explicit parameters; nothing
|
|
370
364
|
// is resolved from config. Async because the facade loads the DB store graph
|
|
371
365
|
// lazily on first call.
|
|
@@ -377,7 +371,6 @@ export {
|
|
|
377
371
|
getConversation,
|
|
378
372
|
getConversationDirPath,
|
|
379
373
|
getConversationProcessingStartedAt,
|
|
380
|
-
getConversationToolSurface,
|
|
381
374
|
getMessages,
|
|
382
375
|
hasLexicalTokens,
|
|
383
376
|
isConversationProcessing,
|
|
@@ -445,7 +438,6 @@ export type {
|
|
|
445
438
|
RunConversationTurnResult,
|
|
446
439
|
} from "./conversation-turn.js";
|
|
447
440
|
export { runConversationTurn } from "./conversation-turn.js";
|
|
448
|
-
export { PluginTurnNotAdmittedError } from "./plugin-channel-turn-trust.js";
|
|
449
441
|
// Live voice — drive a single client's real-time voice session (STT → agent
|
|
450
442
|
// turn → TTS, with server-VAD turn-taking, pauses, and barge-in) over a
|
|
451
443
|
// transport the plugin owns. The plugin brings only a `send` callback (e.g.
|
|
@@ -68,13 +68,6 @@ Everything else under the plugin root is **spine**:
|
|
|
68
68
|
the writer plus the one matcher every reader uses. It lives at the root
|
|
69
69
|
precisely so `substrate/`, `graph/`, and `graph-topology/` can all reach it
|
|
70
70
|
without a tier importing spine. Do not add a second matcher anywhere),
|
|
71
|
-
`buffer-file` (the two writers of `memory/buffer.md`: the append every
|
|
72
|
-
`remember()`-shaped path uses and the consume the consolidation job runs
|
|
73
|
-
after a pass. Do not write the buffer from anywhere else, and never
|
|
74
|
-
rewrite it from a read that is not inside `consumeBufferEntries`'s
|
|
75
|
-
synchronous critical section), `memory-run-evidence` (readers of what a
|
|
76
|
-
background memory run durably produced from its persisted messages; every
|
|
77
|
-
job that gates a state transition on a verified write uses them),
|
|
78
71
|
`segmenter`, `message-media`, `worker`, `worker-control`,
|
|
79
72
|
`memory-recall-log-store`, `activation-session-store` (the onboarding
|
|
80
73
|
activation rail — **not** a memory tier despite the name),
|
|
@@ -414,13 +407,8 @@ per distinct matched section, at most `memory.v3.finderSectionsPerPage` in
|
|
|
414
407
|
surfacing order (needle, dense, reply, span) plus its entity and rare-term
|
|
415
408
|
lines, and selecting a line selects that section; the selection log keeps
|
|
416
409
|
one row per slug, so the pool row is where the per-section verdicts live. A
|
|
417
|
-
turn whose selector never judged a pool
|
|
418
|
-
|
|
419
|
-
and the pool as the selector was given it, stable-prefix cards chosen and
|
|
420
|
-
finder lines not, when the selector's provider failed and the orchestrator
|
|
421
|
-
kept that prefix unjudged. That turn writes no `memory_v3_selections` rows:
|
|
422
|
-
the hot set's frecency and the learned-edge graph read that table as
|
|
423
|
-
judgments, and an unjudged page is not one. A
|
|
410
|
+
turn whose selector never judged a pool (the injection gate hard-skipped it,
|
|
411
|
+
or nothing was pooled) persists an empty pool with `selector_ran = 0`, and a
|
|
424
412
|
turn that logged no selections is still reachable by its stamped
|
|
425
413
|
`message_id`, so the inspector shows negative verdicts too. The pool row and
|
|
426
414
|
the turn's `memory_v3_selections` rows are
|
|
@@ -10,13 +10,10 @@
|
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
-
bufferEntryText,
|
|
14
13
|
formatBufferTimestamp,
|
|
15
14
|
formatRememberEntry,
|
|
16
15
|
isBufferEntryStart,
|
|
17
|
-
joinBufferEntries,
|
|
18
16
|
matchBufferEntryStart,
|
|
19
|
-
splitBufferContent,
|
|
20
17
|
splitBufferEntries,
|
|
21
18
|
} from "../buffer-format.js";
|
|
22
19
|
|
|
@@ -205,43 +202,3 @@ describe("splitBufferEntries", () => {
|
|
|
205
202
|
expect(splitBufferEntries([])).toEqual([]);
|
|
206
203
|
});
|
|
207
204
|
});
|
|
208
|
-
|
|
209
|
-
describe("splitBufferContent / joinBufferEntries", () => {
|
|
210
|
-
const content =
|
|
211
|
-
formatRememberEntry("Alice prefers dark mode", new Date(2026, 0, 1, 9, 0)) +
|
|
212
|
-
formatRememberEntry(
|
|
213
|
-
"Bob's plan:\n- [ ] step one\n\nend",
|
|
214
|
-
new Date(2026, 0, 1, 9, 1),
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
test("round-trips newline-terminated content byte for byte", () => {
|
|
218
|
-
expect(joinBufferEntries(splitBufferContent(content))).toBe(content);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test("an entry's text is the same whether or not it is last in the file", () => {
|
|
222
|
-
const [first] = splitBufferContent(content);
|
|
223
|
-
const [alone] = splitBufferContent(
|
|
224
|
-
formatRememberEntry(
|
|
225
|
-
"Alice prefers dark mode",
|
|
226
|
-
new Date(2026, 0, 1, 9, 0),
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
expect(bufferEntryText(alone!)).toBe(bufferEntryText(first!));
|
|
230
|
-
expect(bufferEntryText(alone!)).toBe(
|
|
231
|
-
"- [Jan 1, 9:00 AM] Alice prefers dark mode",
|
|
232
|
-
);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("empty content has no entries and joins back to empty", () => {
|
|
236
|
-
expect(splitBufferContent("")).toEqual([]);
|
|
237
|
-
expect(joinBufferEntries([])).toBe("");
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("content without a terminating newline keeps its last line intact", () => {
|
|
241
|
-
const entries = splitBufferContent("- [Jan 1, 9:00 AM] partial appe");
|
|
242
|
-
expect(entries).toHaveLength(1);
|
|
243
|
-
expect(bufferEntryText(entries[0]!)).toBe(
|
|
244
|
-
"- [Jan 1, 9:00 AM] partial appe",
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
});
|
|
@@ -117,17 +117,6 @@ let loadedConversations: Record<string, { processing: boolean }> = {};
|
|
|
117
117
|
// the stale-flag override's age check. Absent ids read as null (stampless).
|
|
118
118
|
let processingStartedAtById: Record<string, number | null> = {};
|
|
119
119
|
|
|
120
|
-
// The source conversation's recorded wire tool surface (what its last live
|
|
121
|
-
// turn sent to the provider). `null` = no live turn has recorded one yet.
|
|
122
|
-
let mockSourceToolSurface: Array<Record<string, unknown>> | null = null;
|
|
123
|
-
let toolSurfaceReads: string[] = [];
|
|
124
|
-
mock.module("../../../../persistence/conversation-tool-surface.js", () => ({
|
|
125
|
-
getConversationToolSurface: (conversationId: string) => {
|
|
126
|
-
toolSurfaceReads.push(conversationId);
|
|
127
|
-
return mockSourceToolSurface;
|
|
128
|
-
},
|
|
129
|
-
}));
|
|
130
|
-
|
|
131
120
|
const watchdogEvents: Array<{
|
|
132
121
|
checkName: string;
|
|
133
122
|
value?: number | null;
|
|
@@ -520,8 +509,6 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
520
509
|
resolveUserSlugCalls = [];
|
|
521
510
|
mockV3TierActive = true;
|
|
522
511
|
mockSkillImprovementActive = false;
|
|
523
|
-
mockSourceToolSurface = null;
|
|
524
|
-
toolSurfaceReads = [];
|
|
525
512
|
});
|
|
526
513
|
|
|
527
514
|
test("first-run happy path: no state row, no prior retrospective, both pointer fields set on success", async () => {
|
|
@@ -1855,39 +1842,6 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
1855
1842
|
});
|
|
1856
1843
|
});
|
|
1857
1844
|
|
|
1858
|
-
test("replays the source's recorded wire tool surface verbatim on the fork wake", async () => {
|
|
1859
|
-
mockSourceToolSurface = [
|
|
1860
|
-
{ name: "bash", description: "Run", input_schema: {} },
|
|
1861
|
-
{ name: "remember", description: "Save", input_schema: {} },
|
|
1862
|
-
{ name: "bell_jingle", description: "Ring", input_schema: {} },
|
|
1863
|
-
{ type: "web_search_20250305", name: "web_search", max_uses: 5 },
|
|
1864
|
-
];
|
|
1865
|
-
|
|
1866
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1867
|
-
|
|
1868
|
-
expect(toolSurfaceReads).toEqual(["src-conv-1"]);
|
|
1869
|
-
expect(wakeCalls).toHaveLength(1);
|
|
1870
|
-
// The exact recorded array rides to the wake; the fork sends it in place
|
|
1871
|
-
// of the surface it would resolve for itself.
|
|
1872
|
-
expect(wakeCalls[0]!.opts.wireToolDefinitions).toEqual(
|
|
1873
|
-
mockSourceToolSurface,
|
|
1874
|
-
);
|
|
1875
|
-
// The execution-side pin still rides alongside it.
|
|
1876
|
-
expect(wakeCalls[0]!.opts.toolGateMode).toBe("execution");
|
|
1877
|
-
expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
|
|
1878
|
-
});
|
|
1879
|
-
|
|
1880
|
-
test("no recorded source surface → no wireToolDefinitions; the pin alone shapes the wire", async () => {
|
|
1881
|
-
mockSourceToolSurface = null;
|
|
1882
|
-
|
|
1883
|
-
await memoryRetrospectiveJob(makeJob(), stubConfig);
|
|
1884
|
-
|
|
1885
|
-
expect(toolSurfaceReads).toEqual(["src-conv-1"]);
|
|
1886
|
-
expect(wakeCalls).toHaveLength(1);
|
|
1887
|
-
expect("wireToolDefinitions" in wakeCalls[0]!.opts).toBe(false);
|
|
1888
|
-
expect(wakeCalls[0]!.opts.toolContextPin).toBeDefined();
|
|
1889
|
-
});
|
|
1890
|
-
|
|
1891
1845
|
test("execution mode is unconditional → toolContextPin rides even without a resolved profile", async () => {
|
|
1892
1846
|
await memoryRetrospectiveJob(
|
|
1893
1847
|
makeJob(),
|
|
@@ -154,46 +154,6 @@ export function splitBufferEntries(
|
|
|
154
154
|
return groups;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
/**
|
|
158
|
-
* Split a whole buffer FILE into entries. The file is newline-terminated
|
|
159
|
-
* (every append ends in `\n`), so the terminator ends the last line rather
|
|
160
|
-
* than opening an empty one: an entry's lines are the same whether it is
|
|
161
|
-
* currently last in the file or has since been followed by more entries.
|
|
162
|
-
* That stability is what lets a consumer match an entry it snapshotted
|
|
163
|
-
* earlier against the live file by exact text.
|
|
164
|
-
*/
|
|
165
|
-
export function splitBufferContent(content: string): BufferEntryLines[] {
|
|
166
|
-
if (content.length === 0) {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
|
-
const lines = content.split("\n");
|
|
170
|
-
if (lines[lines.length - 1] === "") {
|
|
171
|
-
lines.pop();
|
|
172
|
-
}
|
|
173
|
-
return splitBufferEntries(lines);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* One entry's verbatim text: its lines joined, without a terminating
|
|
178
|
-
* newline. The identity a consumer matches on.
|
|
179
|
-
*/
|
|
180
|
-
export function bufferEntryText(entry: BufferEntryLines): string {
|
|
181
|
-
return entry.lines.join("\n");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Inverse of {@link splitBufferContent}: entries back to newline-terminated
|
|
186
|
-
* file content, or the empty string when there are none.
|
|
187
|
-
*/
|
|
188
|
-
export function joinBufferEntries(
|
|
189
|
-
entries: readonly BufferEntryLines[],
|
|
190
|
-
): string {
|
|
191
|
-
if (entries.length === 0) {
|
|
192
|
-
return "";
|
|
193
|
-
}
|
|
194
|
-
return `${entries.map(bufferEntryText).join("\n")}\n`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
157
|
/** One entry's verbatim lines, as returned by {@link splitBufferEntries}. */
|
|
198
158
|
export interface BufferEntryLines {
|
|
199
159
|
/** `null` for the headless group before the first entry opening. */
|
|
@@ -4,9 +4,10 @@ import type {
|
|
|
4
4
|
ProviderResponse,
|
|
5
5
|
ToolUseContent,
|
|
6
6
|
} from "@vellumai/plugin-api";
|
|
7
|
-
import { getConfiguredProvider
|
|
7
|
+
import { getConfiguredProvider } from "@vellumai/plugin-api";
|
|
8
8
|
|
|
9
9
|
import { redactSecrets } from "../../../../security/secret-scanner.js";
|
|
10
|
+
import { safeStringSlice } from "../host-utils.js";
|
|
10
11
|
import {
|
|
11
12
|
buildRecallAgentPromptBundle,
|
|
12
13
|
FINISH_RECALL_TOOL_DEFINITION,
|
|
@@ -26,9 +26,8 @@
|
|
|
26
26
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
27
27
|
import { extname, join } from "node:path";
|
|
28
28
|
|
|
29
|
-
import { safeStringSlice } from "@vellumai/plugin-api";
|
|
30
|
-
|
|
31
29
|
import { embedWithRetry } from "../../../../../persistence/embeddings/embed.js";
|
|
30
|
+
import { safeStringSlice } from "../../host-utils.js";
|
|
32
31
|
import { getLogger } from "../../logging.js";
|
|
33
32
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
34
33
|
import { getEdgeIndex } from "../../substrate/edge-index.js";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { extname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
3
|
|
|
4
|
-
import { safeStringSlice } from "
|
|
5
|
-
|
|
4
|
+
import { safeStringSlice } from "../../host-utils.js";
|
|
6
5
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
7
6
|
import type {
|
|
8
7
|
RecallEvidence,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// semantic retrieval.
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
-
import { CLI_COMMAND_HELP
|
|
8
|
+
import { CLI_COMMAND_HELP } from "@vellumai/plugin-api";
|
|
9
9
|
import { and, eq, like, sql } from "drizzle-orm";
|
|
10
10
|
|
|
11
11
|
import { isAssistantFeatureFlagEnabled } from "../../../../config/assistant-feature-flags.js";
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getCachedCatalogSync,
|
|
27
27
|
getCatalog,
|
|
28
28
|
} from "../../../../skills/catalog-cache.js";
|
|
29
|
+
import { safeStringSlice } from "../host-utils.js";
|
|
29
30
|
import { getLogger } from "../logging.js";
|
|
30
31
|
import { memoryDbOrNull } from "../memory-db.js";
|
|
31
32
|
import type { SkillCapabilityInput } from "../substrate/skill-content.js";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// into concept pages.
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
+
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
|
|
11
12
|
import {
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
} from "../../../../config/memory-v3-gate.js";
|
|
15
16
|
import type { AssistantConfig } from "../../../../config/types.js";
|
|
16
17
|
import { enqueueMemoryJob } from "../../../../persistence/jobs-store.js";
|
|
17
|
-
import { appendBufferAndArchive } from "../buffer-file.js";
|
|
18
18
|
import { formatRememberEntry } from "../buffer-format.js";
|
|
19
19
|
import { getWorkspaceDir } from "../paths.js";
|
|
20
20
|
import type { GraphStats } from "./store.js";
|
|
@@ -99,6 +99,47 @@ export function handleRemember(
|
|
|
99
99
|
return { success: true, message };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Append `entry` to `<rootDir>/buffer.md` and `<rootDir>/archive/<today>.md`,
|
|
104
|
+
* creating the archive directory and seeding the archive header if missing.
|
|
105
|
+
*
|
|
106
|
+
* Returns the absolute paths of both files so callers can fan out follow-up
|
|
107
|
+
* work.
|
|
108
|
+
*
|
|
109
|
+
* Exported so background jobs (`sweep`, future LLM-driven extractors) can
|
|
110
|
+
* append to `memory/buffer.md` + `memory/archive/<today>.md` with exactly the
|
|
111
|
+
* same format `remember()` produces, keeping the two write paths
|
|
112
|
+
* byte-compatible for downstream consumers (consolidation, search).
|
|
113
|
+
*/
|
|
114
|
+
export function appendBufferAndArchive(args: {
|
|
115
|
+
rootDir: string;
|
|
116
|
+
entry: string;
|
|
117
|
+
now: Date;
|
|
118
|
+
}): { bufferPath: string; archivePath: string } {
|
|
119
|
+
const { rootDir, entry, now } = args;
|
|
120
|
+
const archiveDir = join(rootDir, "archive");
|
|
121
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
122
|
+
|
|
123
|
+
const bufferPath = join(rootDir, "buffer.md");
|
|
124
|
+
appendFileSync(bufferPath, entry, "utf-8");
|
|
125
|
+
|
|
126
|
+
const yyyy = now.getFullYear();
|
|
127
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
128
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
129
|
+
const archivePath = join(archiveDir, `${yyyy}-${mm}-${dd}.md`);
|
|
130
|
+
if (!existsSync(archivePath)) {
|
|
131
|
+
const month = now.toLocaleString("en-US", { month: "short" });
|
|
132
|
+
appendFileSync(
|
|
133
|
+
archivePath,
|
|
134
|
+
`# ${month} ${now.getDate()}, ${yyyy}\n\n`,
|
|
135
|
+
"utf-8",
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
appendFileSync(archivePath, entry, "utf-8");
|
|
139
|
+
|
|
140
|
+
return { bufferPath, archivePath };
|
|
141
|
+
}
|
|
142
|
+
|
|
102
143
|
// ---------------------------------------------------------------------------
|
|
103
144
|
// Shared helpers for delete / update / list
|
|
104
145
|
// ---------------------------------------------------------------------------
|
|
@@ -25,6 +25,7 @@ import type { SqliteRetryOptions } from "../../../util/sqlite-retry.js";
|
|
|
25
25
|
import * as hostSqliteRetry from "../../../util/sqlite-retry.js";
|
|
26
26
|
import * as hostStripCommentLines from "../../../util/strip-comment-lines.js";
|
|
27
27
|
import * as hostTruncate from "../../../util/truncate.js";
|
|
28
|
+
import * as hostUnicode from "../../../util/unicode.js";
|
|
28
29
|
import * as hostWorkerCompute from "../../../util/worker-compute.js";
|
|
29
30
|
import * as hostWorkerMemory from "../../../util/worker-memory.js";
|
|
30
31
|
|
|
@@ -76,6 +77,15 @@ export function truncate(str: string, maxLen: number, suffix?: string): string {
|
|
|
76
77
|
: hostTruncate.truncate(str, maxLen, suffix);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/** `String.prototype.slice` that never cuts a UTF-16 surrogate pair in half. */
|
|
81
|
+
export function safeStringSlice(
|
|
82
|
+
str: string,
|
|
83
|
+
start?: number,
|
|
84
|
+
end?: number,
|
|
85
|
+
): string {
|
|
86
|
+
return hostUnicode.safeStringSlice(str, start, end);
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
export function workerMemoryEnv(): Record<string, string | undefined> {
|
|
80
90
|
return hostWorkerMemory.workerMemoryEnv();
|
|
81
91
|
}
|
|
@@ -374,10 +374,9 @@ const memoryV2StaticInjector: Injector = {
|
|
|
374
374
|
if (mode !== "full") {
|
|
375
375
|
return null;
|
|
376
376
|
}
|
|
377
|
-
// The consolidation
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
// pass and hand the agent entries that are not its to file.
|
|
377
|
+
// The consolidation agent reads and rewrites memory/buffer.md through
|
|
378
|
+
// file tools; injecting the buffer section here would duplicate the
|
|
379
|
+
// entire backlog into its context (and go stale as it edits the file).
|
|
381
380
|
const content = readGatedMemoryV2Static(ctx.trust, {
|
|
382
381
|
excludeBuffer: ctx.callSite === "memoryV2Consolidation",
|
|
383
382
|
});
|