@vellumai/assistant 0.5.2 → 0.5.4

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.
Files changed (144) hide show
  1. package/ARCHITECTURE.md +109 -0
  2. package/docs/architecture/memory.md +105 -0
  3. package/docs/skills.md +100 -0
  4. package/package.json +1 -1
  5. package/src/__tests__/archive-recall.test.ts +560 -0
  6. package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -0
  7. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  8. package/src/__tests__/conversation-clear-safety.test.ts +259 -0
  9. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  10. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  11. package/src/__tests__/conversation-switch-memory-reduction.test.ts +474 -0
  12. package/src/__tests__/conversation-wipe.test.ts +226 -0
  13. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  14. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  15. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  16. package/src/__tests__/db-schedule-syntax-migration.test.ts +3 -0
  17. package/src/__tests__/inline-command-runner.test.ts +311 -0
  18. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  19. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  20. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  21. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  22. package/src/__tests__/memory-brief-time.test.ts +285 -0
  23. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  24. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  25. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  26. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  27. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  28. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  29. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  30. package/src/__tests__/memory-recall-quality.test.ts +2 -2
  31. package/src/__tests__/memory-reducer-job.test.ts +538 -0
  32. package/src/__tests__/memory-reducer-scheduling.test.ts +473 -0
  33. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  34. package/src/__tests__/memory-reducer-types.test.ts +707 -0
  35. package/src/__tests__/memory-reducer.test.ts +704 -0
  36. package/src/__tests__/memory-regressions.test.ts +30 -8
  37. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  38. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  39. package/src/__tests__/simplified-memory-e2e.test.ts +666 -0
  40. package/src/__tests__/simplified-memory-runtime.test.ts +616 -0
  41. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  42. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  43. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  44. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  45. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  46. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +4 -4
  47. package/src/cli/commands/conversations.ts +18 -0
  48. package/src/config/bundled-skills/app-builder/SKILL.md +8 -8
  49. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  50. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  51. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  52. package/src/config/feature-flag-registry.json +16 -0
  53. package/src/config/raw-config-utils.ts +28 -0
  54. package/src/config/schema.ts +12 -0
  55. package/src/config/schemas/memory-simplified.ts +101 -0
  56. package/src/config/schemas/memory.ts +4 -0
  57. package/src/config/skills.ts +50 -4
  58. package/src/daemon/conversation-agent-loop-handlers.ts +8 -3
  59. package/src/daemon/conversation-agent-loop.ts +71 -1
  60. package/src/daemon/conversation-lifecycle.ts +11 -1
  61. package/src/daemon/conversation-memory.ts +117 -0
  62. package/src/daemon/conversation-runtime-assembly.ts +3 -1
  63. package/src/daemon/conversation-surfaces.ts +31 -8
  64. package/src/daemon/conversation.ts +40 -23
  65. package/src/daemon/handlers/config-embeddings.ts +10 -2
  66. package/src/daemon/handlers/config-model.ts +0 -9
  67. package/src/daemon/handlers/conversations.ts +11 -0
  68. package/src/daemon/handlers/identity.ts +12 -1
  69. package/src/daemon/lifecycle.ts +52 -1
  70. package/src/daemon/message-types/conversations.ts +0 -1
  71. package/src/daemon/server.ts +1 -1
  72. package/src/followups/followup-store.ts +47 -1
  73. package/src/memory/archive-recall.ts +516 -0
  74. package/src/memory/archive-store.ts +400 -0
  75. package/src/memory/brief-formatting.ts +33 -0
  76. package/src/memory/brief-open-loops.ts +266 -0
  77. package/src/memory/brief-time.ts +162 -0
  78. package/src/memory/brief.ts +75 -0
  79. package/src/memory/conversation-crud.ts +455 -101
  80. package/src/memory/conversation-key-store.ts +33 -4
  81. package/src/memory/db-init.ts +16 -0
  82. package/src/memory/indexer.ts +106 -15
  83. package/src/memory/job-handlers/backfill-simplified-memory.ts +462 -0
  84. package/src/memory/job-handlers/conversation-starters.ts +9 -3
  85. package/src/memory/job-handlers/embedding.test.ts +1 -0
  86. package/src/memory/job-handlers/embedding.ts +83 -0
  87. package/src/memory/job-handlers/reduce-conversation-memory.ts +229 -0
  88. package/src/memory/job-utils.ts +1 -1
  89. package/src/memory/jobs-store.ts +8 -0
  90. package/src/memory/jobs-worker.ts +20 -0
  91. package/src/memory/migrations/036-normalize-phone-identities.ts +49 -14
  92. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +9 -1
  93. package/src/memory/migrations/141-rename-verification-table.ts +8 -0
  94. package/src/memory/migrations/142-rename-verification-session-id-column.ts +7 -2
  95. package/src/memory/migrations/174-rename-thread-starters-table.ts +8 -0
  96. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  97. package/src/memory/migrations/186-memory-archive.ts +109 -0
  98. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  99. package/src/memory/migrations/188-schedule-quiet-flag.ts +13 -0
  100. package/src/memory/migrations/index.ts +4 -0
  101. package/src/memory/qdrant-client.ts +23 -4
  102. package/src/memory/reducer-scheduler.ts +242 -0
  103. package/src/memory/reducer-store.ts +271 -0
  104. package/src/memory/reducer-types.ts +106 -0
  105. package/src/memory/reducer.ts +467 -0
  106. package/src/memory/schema/conversations.ts +3 -0
  107. package/src/memory/schema/index.ts +2 -0
  108. package/src/memory/schema/infrastructure.ts +1 -0
  109. package/src/memory/schema/memory-archive.ts +121 -0
  110. package/src/memory/schema/memory-brief.ts +55 -0
  111. package/src/memory/search/semantic.ts +17 -4
  112. package/src/oauth/oauth-store.ts +3 -1
  113. package/src/permissions/checker.ts +89 -6
  114. package/src/permissions/defaults.ts +14 -0
  115. package/src/runtime/auth/route-policy.ts +10 -1
  116. package/src/runtime/routes/conversation-management-routes.ts +94 -2
  117. package/src/runtime/routes/conversation-query-routes.ts +7 -0
  118. package/src/runtime/routes/conversation-routes.ts +52 -5
  119. package/src/runtime/routes/guardian-bootstrap-routes.ts +19 -7
  120. package/src/runtime/routes/identity-routes.ts +2 -35
  121. package/src/runtime/routes/llm-context-normalization.ts +14 -1
  122. package/src/runtime/routes/memory-item-routes.ts +90 -5
  123. package/src/runtime/routes/secret-routes.ts +3 -0
  124. package/src/runtime/routes/surface-action-routes.ts +68 -1
  125. package/src/schedule/schedule-store.ts +28 -0
  126. package/src/schedule/scheduler.ts +6 -2
  127. package/src/skills/inline-command-expansions.ts +204 -0
  128. package/src/skills/inline-command-render.ts +127 -0
  129. package/src/skills/inline-command-runner.ts +242 -0
  130. package/src/skills/transitive-version-hash.ts +88 -0
  131. package/src/tasks/task-store.ts +43 -1
  132. package/src/telemetry/usage-telemetry-reporter.ts +1 -1
  133. package/src/tools/filesystem/edit.ts +6 -1
  134. package/src/tools/filesystem/read.ts +6 -1
  135. package/src/tools/filesystem/write.ts +6 -1
  136. package/src/tools/memory/handlers.ts +129 -1
  137. package/src/tools/permission-checker.ts +8 -1
  138. package/src/tools/schedule/create.ts +3 -0
  139. package/src/tools/schedule/list.ts +5 -1
  140. package/src/tools/schedule/update.ts +6 -0
  141. package/src/tools/skills/load.ts +140 -6
  142. package/src/util/platform.ts +18 -0
  143. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +1 -1
  144. package/src/workspace/migrations/registry.ts +1 -1
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Deterministic compiler for the "Time-Relevant Context" section of the
3
+ * memory brief. Reads active `time_contexts` rows plus due-soon live
4
+ * schedule jobs, sorts them by urgency bucket, and caps the output.
5
+ */
6
+
7
+ import { and, eq, gte, lte } from "drizzle-orm";
8
+
9
+ import { getDueSoonSchedules } from "../schedule/schedule-store.js";
10
+ import type { BriefEntry } from "./brief-formatting.js";
11
+ import { renderBriefSection } from "./brief-formatting.js";
12
+ import type { DrizzleDb } from "./db-connection.js";
13
+ import { timeContexts } from "./schema/memory-brief.js";
14
+
15
+ const MAX_ENTRIES = 3;
16
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
17
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
18
+
19
+ /** Urgency buckets — lower number = higher priority. */
20
+ const enum Bucket {
21
+ HappeningNow = 0,
22
+ Overdue = 1,
23
+ Within24h = 2,
24
+ Within7d = 3,
25
+ }
26
+
27
+ interface Candidate {
28
+ bucket: Bucket;
29
+ /** Epoch ms timestamp used for secondary sort within a bucket. */
30
+ sortKey: number;
31
+ entry: BriefEntry;
32
+ }
33
+
34
+ // ────────────────────────────────────────────────────────────────────
35
+ // Public API
36
+ // ────────────────────────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Compile the time-relevant brief section.
40
+ *
41
+ * @param db Drizzle database instance
42
+ * @param now Current epoch-ms timestamp (injectable for deterministic tests)
43
+ * @returns Markdown string for the section, or `null` if nothing qualifies
44
+ */
45
+ export function compileTimeBrief(
46
+ db: DrizzleDb,
47
+ scopeId: string,
48
+ now: number,
49
+ ): string | null {
50
+ const candidates: Candidate[] = [];
51
+
52
+ collectTimeContexts(db, scopeId, now, candidates);
53
+ collectDueSoonSchedules(now, candidates);
54
+
55
+ // Sort: primary = bucket ascending, secondary = sortKey ascending (sooner first)
56
+ candidates.sort((a, b) => a.bucket - b.bucket || a.sortKey - b.sortKey);
57
+
58
+ const entries = candidates.slice(0, MAX_ENTRIES).map((c) => c.entry);
59
+ return renderBriefSection("Time-Relevant Context", entries, MAX_ENTRIES);
60
+ }
61
+
62
+ // ────────────────────────────────────────────────────────────────────
63
+ // Internal collectors
64
+ // ────────────────────────────────────────────────────────────────────
65
+
66
+ function collectTimeContexts(
67
+ db: DrizzleDb,
68
+ scopeId: string,
69
+ now: number,
70
+ out: Candidate[],
71
+ ): void {
72
+ // Active time contexts: scopeId match AND activeFrom <= now AND activeUntil >= now
73
+ // Uses idx_time_contexts_scope_active_until composite index
74
+ const rows = db
75
+ .select()
76
+ .from(timeContexts)
77
+ .where(
78
+ and(
79
+ eq(timeContexts.scopeId, scopeId),
80
+ lte(timeContexts.activeFrom, now),
81
+ gte(timeContexts.activeUntil, now),
82
+ ),
83
+ )
84
+ .all();
85
+
86
+ for (const row of rows) {
87
+ const remaining = row.activeUntil - now;
88
+ let bucket: Bucket;
89
+
90
+ if (row.activeFrom <= now && row.activeUntil >= now) {
91
+ // Currently active — classify by how much time remains
92
+ if (remaining <= ONE_DAY_MS) {
93
+ bucket = Bucket.HappeningNow;
94
+ } else if (remaining <= SEVEN_DAYS_MS) {
95
+ bucket = Bucket.Within24h;
96
+ } else {
97
+ bucket = Bucket.Within7d;
98
+ }
99
+ } else {
100
+ bucket = Bucket.Within7d;
101
+ }
102
+
103
+ out.push({
104
+ bucket,
105
+ sortKey: row.activeUntil,
106
+ entry: { text: row.summary },
107
+ });
108
+ }
109
+ }
110
+
111
+ function collectDueSoonSchedules(now: number, out: Candidate[]): void {
112
+ const jobs = getDueSoonSchedules(now, SEVEN_DAYS_MS);
113
+
114
+ for (const job of jobs) {
115
+ const delta = job.nextRunAt - now;
116
+ let bucket: Bucket;
117
+
118
+ if (delta <= 0) {
119
+ bucket = Bucket.Overdue;
120
+ } else if (delta <= ONE_DAY_MS) {
121
+ bucket = Bucket.Within24h;
122
+ } else {
123
+ bucket = Bucket.Within7d;
124
+ }
125
+
126
+ const label = formatScheduleLabel(job.name, job.nextRunAt, now);
127
+ out.push({
128
+ bucket,
129
+ sortKey: job.nextRunAt,
130
+ entry: { text: label },
131
+ });
132
+ }
133
+ }
134
+
135
+ // ────────────────────────────────────────────────────────────────────
136
+ // Formatting
137
+ // ────────────────────────────────────────────────────────────────────
138
+
139
+ function formatScheduleLabel(
140
+ name: string,
141
+ nextRunAt: number,
142
+ now: number,
143
+ ): string {
144
+ const delta = nextRunAt - now;
145
+
146
+ if (delta <= 0) {
147
+ return `Scheduled: "${name}" — overdue`;
148
+ }
149
+
150
+ const minutes = Math.round(delta / 60_000);
151
+ if (minutes < 60) {
152
+ return `Scheduled: "${name}" — in ${minutes} minute${minutes === 1 ? "" : "s"}`;
153
+ }
154
+
155
+ const hours = Math.round(delta / 3_600_000);
156
+ if (hours < 24) {
157
+ return `Scheduled: "${name}" — in ${hours} hour${hours === 1 ? "" : "s"}`;
158
+ }
159
+
160
+ const days = Math.round(delta / 86_400_000);
161
+ return `Scheduled: "${name}" — in ${days} day${days === 1 ? "" : "s"}`;
162
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Top-level memory brief composer.
3
+ *
4
+ * Composes the "Time-Relevant Context" and "Open Loops" sections into a
5
+ * single `<memory_brief>` XML-wrapped block. Omits empty sections and
6
+ * returns an empty string when neither section has content.
7
+ */
8
+
9
+ import { renderBriefSection } from "./brief-formatting.js";
10
+ import type { OpenLoopBriefResult } from "./brief-open-loops.js";
11
+ import { compileOpenLoopBrief } from "./brief-open-loops.js";
12
+ import { compileTimeBrief } from "./brief-time.js";
13
+ import type { DrizzleDb } from "./db-connection.js";
14
+
15
+ /** Maximum number of open-loop bullets to include in the brief. */
16
+ const MAX_OPEN_LOOP_ENTRIES = 5;
17
+
18
+ export interface MemoryBriefResult {
19
+ /** Rendered `<memory_brief>` block, or empty string if nothing to show. */
20
+ text: string;
21
+ /** Forwarded from `compileOpenLoopBrief` for downstream tracking. */
22
+ resurfacedLoopId: string | null;
23
+ }
24
+
25
+ /**
26
+ * Compile the full memory brief block.
27
+ *
28
+ * @param db Drizzle database instance
29
+ * @param scopeId Memory scope (e.g. assistant instance ID)
30
+ * @param userMessageId Current user message ID — used for deterministic
31
+ * open-loop resurfacing
32
+ * @param now Current epoch-ms timestamp (injectable for tests)
33
+ * @returns `{ text, resurfacedLoopId }` — `text` is the
34
+ * rendered `<memory_brief>` block or empty string
35
+ */
36
+ export function compileMemoryBrief(
37
+ db: DrizzleDb,
38
+ scopeId: string,
39
+ userMessageId: string,
40
+ now: number = Date.now(),
41
+ ): MemoryBriefResult {
42
+ // Compile individual sections
43
+ const timeSection = compileTimeBrief(db, scopeId, now);
44
+
45
+ const openLoopResult: OpenLoopBriefResult = compileOpenLoopBrief(
46
+ scopeId,
47
+ userMessageId,
48
+ now,
49
+ );
50
+
51
+ // Convert open-loop bullets to a rendered section via the shared helper
52
+ const openLoopEntries = openLoopResult.bullets.map((b) => ({
53
+ text: b.summary,
54
+ }));
55
+ const openLoopSection = renderBriefSection(
56
+ "Open Loops",
57
+ openLoopEntries,
58
+ MAX_OPEN_LOOP_ENTRIES,
59
+ );
60
+
61
+ // Collect non-empty sections
62
+ const sections: string[] = [];
63
+ if (timeSection) sections.push(timeSection);
64
+ if (openLoopSection) sections.push(openLoopSection);
65
+
66
+ // If no sections have content, return empty
67
+ if (sections.length === 0) {
68
+ return { text: "", resurfacedLoopId: openLoopResult.resurfacedLoopId };
69
+ }
70
+
71
+ const body = sections.join("\n\n");
72
+ const text = `<memory_brief>\n${body}\n</memory_brief>`;
73
+
74
+ return { text, resurfacedLoopId: openLoopResult.resurfacedLoopId };
75
+ }