@vellumai/assistant 0.4.49 → 0.4.50

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 (239) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/memory.md +180 -119
  4. package/package.json +2 -2
  5. package/src/__tests__/agent-loop.test.ts +3 -1
  6. package/src/__tests__/anthropic-provider.test.ts +114 -23
  7. package/src/__tests__/approval-cascade.test.ts +1 -15
  8. package/src/__tests__/approval-routes-http.test.ts +2 -0
  9. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  10. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  11. package/src/__tests__/checker.test.ts +13 -0
  12. package/src/__tests__/config-schema.test.ts +1 -68
  13. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  14. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  15. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  16. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  17. package/src/__tests__/credential-vault-unit.test.ts +4 -0
  18. package/src/__tests__/credential-vault.test.ts +13 -1
  19. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  20. package/src/__tests__/date-context.test.ts +93 -77
  21. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  22. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  23. package/src/__tests__/history-repair.test.ts +245 -0
  24. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  25. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  26. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  27. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  28. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  29. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  30. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  31. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  32. package/src/__tests__/memory-regressions.test.ts +477 -2841
  33. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  34. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  35. package/src/__tests__/mime-builder.test.ts +28 -0
  36. package/src/__tests__/native-web-search.test.ts +1 -0
  37. package/src/__tests__/oauth-cli.test.ts +572 -5
  38. package/src/__tests__/oauth-store.test.ts +120 -6
  39. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  40. package/src/__tests__/registry.test.ts +0 -1
  41. package/src/__tests__/relay-server.test.ts +46 -1
  42. package/src/__tests__/schedule-tools.test.ts +32 -0
  43. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  44. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  45. package/src/__tests__/secure-keys.test.ts +7 -2
  46. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  47. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  48. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  49. package/src/__tests__/session-agent-loop.test.ts +19 -15
  50. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  51. package/src/__tests__/session-error.test.ts +124 -2
  52. package/src/__tests__/session-history-web-search.test.ts +918 -0
  53. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  54. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  55. package/src/__tests__/session-queue.test.ts +37 -27
  56. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  57. package/src/__tests__/session-slash-known.test.ts +1 -15
  58. package/src/__tests__/session-slash-queue.test.ts +1 -15
  59. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  60. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  61. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  62. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  63. package/src/__tests__/skills-install-extract.test.ts +93 -0
  64. package/src/__tests__/skillssh-registry.test.ts +451 -0
  65. package/src/__tests__/trust-store.test.ts +15 -0
  66. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  67. package/src/agent/ax-tree-compaction.test.ts +51 -0
  68. package/src/agent/loop.ts +39 -12
  69. package/src/approvals/AGENTS.md +1 -1
  70. package/src/approvals/guardian-request-resolvers.ts +14 -2
  71. package/src/bundler/compiler-tools.ts +66 -2
  72. package/src/calls/call-domain.ts +132 -0
  73. package/src/calls/call-store.ts +6 -0
  74. package/src/calls/relay-server.ts +43 -5
  75. package/src/calls/relay-setup-router.ts +17 -1
  76. package/src/calls/twilio-config.ts +1 -1
  77. package/src/calls/types.ts +3 -1
  78. package/src/cli/commands/doctor.ts +4 -3
  79. package/src/cli/commands/mcp.ts +46 -59
  80. package/src/cli/commands/memory.ts +16 -165
  81. package/src/cli/commands/oauth/apps.ts +31 -2
  82. package/src/cli/commands/oauth/connections.ts +431 -97
  83. package/src/cli/commands/oauth/providers.ts +15 -1
  84. package/src/cli/commands/sessions.ts +5 -2
  85. package/src/cli/commands/skills.ts +173 -1
  86. package/src/cli/http-client.ts +0 -20
  87. package/src/cli/main-screen.tsx +2 -2
  88. package/src/cli/program.ts +5 -6
  89. package/src/cli.ts +4 -10
  90. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  91. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  92. package/src/config/bundled-tool-registry.ts +2 -5
  93. package/src/config/schema.ts +1 -12
  94. package/src/config/schemas/memory-lifecycle.ts +0 -9
  95. package/src/config/schemas/memory-processing.ts +0 -180
  96. package/src/config/schemas/memory-retrieval.ts +32 -104
  97. package/src/config/schemas/memory.ts +0 -10
  98. package/src/config/types.ts +0 -4
  99. package/src/context/window-manager.ts +4 -1
  100. package/src/daemon/config-watcher.ts +61 -3
  101. package/src/daemon/daemon-control.ts +1 -1
  102. package/src/daemon/date-context.ts +114 -31
  103. package/src/daemon/handlers/sessions.ts +18 -13
  104. package/src/daemon/handlers/skills.ts +20 -1
  105. package/src/daemon/history-repair.ts +72 -8
  106. package/src/daemon/host-cu-proxy.ts +55 -26
  107. package/src/daemon/lifecycle.ts +31 -3
  108. package/src/daemon/mcp-reload-service.ts +2 -2
  109. package/src/daemon/message-types/computer-use.ts +1 -12
  110. package/src/daemon/message-types/memory.ts +4 -16
  111. package/src/daemon/message-types/messages.ts +1 -0
  112. package/src/daemon/message-types/sessions.ts +4 -0
  113. package/src/daemon/server.ts +12 -1
  114. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  115. package/src/daemon/session-agent-loop.ts +334 -48
  116. package/src/daemon/session-error.ts +89 -6
  117. package/src/daemon/session-history.ts +17 -7
  118. package/src/daemon/session-media-retry.ts +6 -2
  119. package/src/daemon/session-memory.ts +69 -149
  120. package/src/daemon/session-process.ts +10 -1
  121. package/src/daemon/session-runtime-assembly.ts +49 -19
  122. package/src/daemon/session-surfaces.ts +4 -1
  123. package/src/daemon/session-tool-setup.ts +7 -1
  124. package/src/daemon/session.ts +12 -2
  125. package/src/instrument.ts +61 -1
  126. package/src/memory/admin.ts +2 -191
  127. package/src/memory/canonical-guardian-store.ts +38 -2
  128. package/src/memory/conversation-crud.ts +0 -33
  129. package/src/memory/conversation-queries.ts +22 -3
  130. package/src/memory/db-init.ts +28 -0
  131. package/src/memory/embedding-backend.ts +84 -8
  132. package/src/memory/embedding-types.ts +9 -1
  133. package/src/memory/indexer.ts +7 -46
  134. package/src/memory/items-extractor.ts +274 -76
  135. package/src/memory/job-handlers/backfill.ts +2 -127
  136. package/src/memory/job-handlers/cleanup.ts +2 -16
  137. package/src/memory/job-handlers/extraction.ts +2 -138
  138. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  139. package/src/memory/job-handlers/summarization.ts +3 -148
  140. package/src/memory/job-utils.ts +21 -59
  141. package/src/memory/jobs-store.ts +1 -159
  142. package/src/memory/jobs-worker.ts +9 -52
  143. package/src/memory/migrations/104-core-indexes.ts +3 -3
  144. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  145. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  146. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  147. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  148. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  149. package/src/memory/migrations/154-drop-fts.ts +20 -0
  150. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  151. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  152. package/src/memory/migrations/index.ts +7 -0
  153. package/src/memory/qdrant-client.ts +148 -51
  154. package/src/memory/raw-query.ts +1 -1
  155. package/src/memory/retriever.test.ts +294 -273
  156. package/src/memory/retriever.ts +421 -645
  157. package/src/memory/schema/calls.ts +2 -0
  158. package/src/memory/schema/memory-core.ts +3 -48
  159. package/src/memory/schema/oauth.ts +2 -0
  160. package/src/memory/search/formatting.ts +263 -176
  161. package/src/memory/search/lexical.ts +1 -254
  162. package/src/memory/search/ranking.ts +0 -455
  163. package/src/memory/search/semantic.ts +100 -14
  164. package/src/memory/search/staleness.ts +47 -0
  165. package/src/memory/search/tier-classifier.ts +21 -0
  166. package/src/memory/search/types.ts +15 -77
  167. package/src/memory/task-memory-cleanup.ts +4 -6
  168. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  169. package/src/oauth/byo-connection.test.ts +8 -1
  170. package/src/oauth/oauth-store.ts +113 -27
  171. package/src/oauth/seed-providers.ts +6 -0
  172. package/src/oauth/token-persistence.ts +11 -3
  173. package/src/permissions/defaults.ts +1 -0
  174. package/src/permissions/trust-store.ts +23 -1
  175. package/src/playbooks/playbook-compiler.ts +1 -1
  176. package/src/prompts/system-prompt.ts +18 -2
  177. package/src/providers/anthropic/client.ts +56 -126
  178. package/src/providers/types.ts +7 -1
  179. package/src/runtime/AGENTS.md +9 -0
  180. package/src/runtime/auth/route-policy.ts +6 -3
  181. package/src/runtime/guardian-reply-router.ts +24 -22
  182. package/src/runtime/http-server.ts +2 -2
  183. package/src/runtime/invite-redemption-service.ts +19 -1
  184. package/src/runtime/invite-service.ts +25 -0
  185. package/src/runtime/pending-interactions.ts +2 -2
  186. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  187. package/src/runtime/routes/conversation-routes.ts +9 -1
  188. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  189. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  190. package/src/runtime/routes/memory-item-routes.ts +503 -0
  191. package/src/runtime/routes/session-management-routes.ts +3 -3
  192. package/src/runtime/routes/settings-routes.ts +2 -2
  193. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  194. package/src/runtime/routes/workspace-routes.ts +2 -1
  195. package/src/security/keychain-broker-client.ts +17 -4
  196. package/src/security/secure-keys.ts +25 -3
  197. package/src/security/token-manager.ts +36 -36
  198. package/src/skills/catalog-install.ts +74 -18
  199. package/src/skills/skillssh-registry.ts +503 -0
  200. package/src/tools/assets/search.ts +5 -1
  201. package/src/tools/computer-use/definitions.ts +0 -10
  202. package/src/tools/computer-use/registry.ts +1 -1
  203. package/src/tools/credentials/vault.ts +1 -3
  204. package/src/tools/memory/definitions.ts +4 -13
  205. package/src/tools/memory/handlers.test.ts +83 -103
  206. package/src/tools/memory/handlers.ts +50 -85
  207. package/src/tools/schedule/create.ts +8 -1
  208. package/src/tools/schedule/update.ts +8 -1
  209. package/src/tools/skills/load.ts +25 -2
  210. package/src/__tests__/clarification-resolver.test.ts +0 -193
  211. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  212. package/src/__tests__/conflict-policy.test.ts +0 -269
  213. package/src/__tests__/conflict-store.test.ts +0 -372
  214. package/src/__tests__/contradiction-checker.test.ts +0 -361
  215. package/src/__tests__/entity-extractor.test.ts +0 -211
  216. package/src/__tests__/entity-search.test.ts +0 -1117
  217. package/src/__tests__/profile-compiler.test.ts +0 -392
  218. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  219. package/src/__tests__/session-profile-injection.test.ts +0 -557
  220. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  221. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  222. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  223. package/src/daemon/session-conflict-gate.ts +0 -167
  224. package/src/daemon/session-dynamic-profile.ts +0 -77
  225. package/src/memory/clarification-resolver.ts +0 -417
  226. package/src/memory/conflict-intent.ts +0 -205
  227. package/src/memory/conflict-policy.ts +0 -127
  228. package/src/memory/conflict-store.ts +0 -410
  229. package/src/memory/contradiction-checker.ts +0 -508
  230. package/src/memory/entity-extractor.ts +0 -535
  231. package/src/memory/format-recall.ts +0 -47
  232. package/src/memory/fts-reconciler.ts +0 -165
  233. package/src/memory/job-handlers/conflict.ts +0 -200
  234. package/src/memory/profile-compiler.ts +0 -195
  235. package/src/memory/recall-cache.ts +0 -117
  236. package/src/memory/search/entity.ts +0 -535
  237. package/src/memory/search/query-expansion.test.ts +0 -70
  238. package/src/memory/search/query-expansion.ts +0 -118
  239. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -1,22 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const MemoryRerankingConfigSchema = z.object({
4
- enabled: z
5
- .boolean({ error: "memory.retrieval.reranking.enabled must be a boolean" })
6
- .default(false),
7
- modelIntent: z
8
- .enum(["latency-optimized", "quality-optimized", "vision-optimized"], {
9
- error:
10
- "memory.retrieval.reranking.modelIntent must be a valid model intent",
11
- })
12
- .default("latency-optimized"),
13
- topK: z
14
- .number({ error: "memory.retrieval.reranking.topK must be a number" })
15
- .int("memory.retrieval.reranking.topK must be an integer")
16
- .positive("memory.retrieval.reranking.topK must be a positive integer")
17
- .default(20),
18
- });
19
-
20
3
  export const MemoryDynamicBudgetConfigSchema = z.object({
21
4
  enabled: z
22
5
  .boolean({
@@ -55,49 +38,6 @@ export const MemoryDynamicBudgetConfigSchema = z.object({
55
38
  .default(10000),
56
39
  });
57
40
 
58
- export const MemoryEarlyTerminationConfigSchema = z.object({
59
- enabled: z
60
- .boolean({
61
- error: "memory.retrieval.earlyTermination.enabled must be a boolean",
62
- })
63
- .default(true),
64
- minCandidates: z
65
- .number({
66
- error: "memory.retrieval.earlyTermination.minCandidates must be a number",
67
- })
68
- .int("memory.retrieval.earlyTermination.minCandidates must be an integer")
69
- .positive(
70
- "memory.retrieval.earlyTermination.minCandidates must be a positive integer",
71
- )
72
- .default(20),
73
- minHighConfidence: z
74
- .number({
75
- error:
76
- "memory.retrieval.earlyTermination.minHighConfidence must be a number",
77
- })
78
- .int(
79
- "memory.retrieval.earlyTermination.minHighConfidence must be an integer",
80
- )
81
- .positive(
82
- "memory.retrieval.earlyTermination.minHighConfidence must be a positive integer",
83
- )
84
- .default(10),
85
- confidenceThreshold: z
86
- .number({
87
- error:
88
- "memory.retrieval.earlyTermination.confidenceThreshold must be a number",
89
- })
90
- .min(
91
- 0,
92
- "memory.retrieval.earlyTermination.confidenceThreshold must be >= 0",
93
- )
94
- .max(
95
- 1,
96
- "memory.retrieval.earlyTermination.confidenceThreshold must be <= 1",
97
- )
98
- .default(0.7),
99
- });
100
-
101
41
  /**
102
42
  * Per-kind freshness windows (in days). Items older than their window
103
43
  * (based on lastSeenAt) are down-ranked unless recently reinforced.
@@ -109,12 +49,13 @@ const MemoryFreshnessConfigSchema = z.object({
109
49
  .default(true),
110
50
  maxAgeDays: z
111
51
  .object({
112
- fact: z
52
+ identity: z
113
53
  .number({
114
- error: "memory.retrieval.freshness.maxAgeDays.fact must be a number",
54
+ error:
55
+ "memory.retrieval.freshness.maxAgeDays.identity must be a number",
115
56
  })
116
57
  .nonnegative(
117
- "memory.retrieval.freshness.maxAgeDays.fact must be non-negative",
58
+ "memory.retrieval.freshness.maxAgeDays.identity must be non-negative",
118
59
  )
119
60
  .default(0),
120
61
  preference: z
@@ -126,34 +67,50 @@ const MemoryFreshnessConfigSchema = z.object({
126
67
  "memory.retrieval.freshness.maxAgeDays.preference must be non-negative",
127
68
  )
128
69
  .default(0),
129
- behavior: z
70
+ project: z
130
71
  .number({
131
72
  error:
132
- "memory.retrieval.freshness.maxAgeDays.behavior must be a number",
73
+ "memory.retrieval.freshness.maxAgeDays.project must be a number",
133
74
  })
134
75
  .nonnegative(
135
- "memory.retrieval.freshness.maxAgeDays.behavior must be non-negative",
76
+ "memory.retrieval.freshness.maxAgeDays.project must be non-negative",
136
77
  )
137
- .default(90),
138
- event: z
78
+ .default(30),
79
+ decision: z
139
80
  .number({
140
- error: "memory.retrieval.freshness.maxAgeDays.event must be a number",
81
+ error:
82
+ "memory.retrieval.freshness.maxAgeDays.decision must be a number",
141
83
  })
142
84
  .nonnegative(
143
- "memory.retrieval.freshness.maxAgeDays.event must be non-negative",
85
+ "memory.retrieval.freshness.maxAgeDays.decision must be non-negative",
144
86
  )
145
87
  .default(30),
146
- opinion: z
88
+ constraint: z
147
89
  .number({
148
90
  error:
149
- "memory.retrieval.freshness.maxAgeDays.opinion must be a number",
91
+ "memory.retrieval.freshness.maxAgeDays.constraint must be a number",
92
+ })
93
+ .nonnegative(
94
+ "memory.retrieval.freshness.maxAgeDays.constraint must be non-negative",
95
+ )
96
+ .default(90),
97
+ event: z
98
+ .number({
99
+ error: "memory.retrieval.freshness.maxAgeDays.event must be a number",
150
100
  })
151
101
  .nonnegative(
152
- "memory.retrieval.freshness.maxAgeDays.opinion must be non-negative",
102
+ "memory.retrieval.freshness.maxAgeDays.event must be non-negative",
153
103
  )
154
- .default(60),
104
+ .default(30),
155
105
  })
156
- .default({ fact: 0, preference: 0, behavior: 90, event: 30, opinion: 60 }),
106
+ .default({
107
+ identity: 0,
108
+ preference: 0,
109
+ project: 30,
110
+ decision: 30,
111
+ constraint: 90,
112
+ event: 30,
113
+ }),
157
114
  staleDecay: z
158
115
  .number({ error: "memory.retrieval.freshness.staleDecay must be a number" })
159
116
  .min(0, "memory.retrieval.freshness.staleDecay must be >= 0")
@@ -171,36 +128,11 @@ const MemoryFreshnessConfigSchema = z.object({
171
128
  });
172
129
 
173
130
  export const MemoryRetrievalConfigSchema = z.object({
174
- lexicalTopK: z
175
- .number({ error: "memory.retrieval.lexicalTopK must be a number" })
176
- .int("memory.retrieval.lexicalTopK must be an integer")
177
- .positive("memory.retrieval.lexicalTopK must be a positive integer")
178
- .default(80),
179
- semanticTopK: z
180
- .number({ error: "memory.retrieval.semanticTopK must be a number" })
181
- .int("memory.retrieval.semanticTopK must be an integer")
182
- .positive("memory.retrieval.semanticTopK must be a positive integer")
183
- .default(40),
184
131
  maxInjectTokens: z
185
132
  .number({ error: "memory.retrieval.maxInjectTokens must be a number" })
186
133
  .int("memory.retrieval.maxInjectTokens must be an integer")
187
134
  .positive("memory.retrieval.maxInjectTokens must be a positive integer")
188
135
  .default(10000),
189
- injectionFormat: z
190
- .enum(["markdown", "structured_v1"], {
191
- error:
192
- 'memory.retrieval.injectionFormat must be "markdown" or "structured_v1"',
193
- })
194
- .default("markdown"),
195
- injectionStrategy: z
196
- .enum(["prepend_user_block", "separate_context_message"], {
197
- error:
198
- 'memory.retrieval.injectionStrategy must be "prepend_user_block" or "separate_context_message"',
199
- })
200
- .default("prepend_user_block"),
201
- reranking: MemoryRerankingConfigSchema.default(
202
- MemoryRerankingConfigSchema.parse({}),
203
- ),
204
136
  freshness: MemoryFreshnessConfigSchema.default(
205
137
  MemoryFreshnessConfigSchema.parse({}),
206
138
  ),
@@ -213,10 +145,6 @@ export const MemoryRetrievalConfigSchema = z.object({
213
145
  dynamicBudget: MemoryDynamicBudgetConfigSchema.default(
214
146
  MemoryDynamicBudgetConfigSchema.parse({}),
215
147
  ),
216
- earlyTermination: MemoryEarlyTerminationConfigSchema.default(
217
- MemoryEarlyTerminationConfigSchema.parse({}),
218
- ),
219
148
  });
220
149
 
221
- export type MemoryRerankingConfig = z.infer<typeof MemoryRerankingConfigSchema>;
222
150
  export type MemoryRetrievalConfig = z.infer<typeof MemoryRetrievalConfigSchema>;
@@ -6,10 +6,7 @@ import {
6
6
  MemoryRetentionConfigSchema,
7
7
  } from "./memory-lifecycle.js";
8
8
  import {
9
- MemoryConflictsConfigSchema,
10
- MemoryEntityConfigSchema,
11
9
  MemoryExtractionConfigSchema,
12
- MemoryProfileConfigSchema,
13
10
  MemorySummarizationConfigSchema,
14
11
  } from "./memory-processing.js";
15
12
  import { MemoryRetrievalConfigSchema } from "./memory-retrieval.js";
@@ -46,13 +43,6 @@ export const MemoryConfigSchema = z.object({
46
43
  summarization: MemorySummarizationConfigSchema.default(
47
44
  MemorySummarizationConfigSchema.parse({}),
48
45
  ),
49
- entity: MemoryEntityConfigSchema.default(MemoryEntityConfigSchema.parse({})),
50
- conflicts: MemoryConflictsConfigSchema.default(
51
- MemoryConflictsConfigSchema.parse({}),
52
- ),
53
- profile: MemoryProfileConfigSchema.default(
54
- MemoryProfileConfigSchema.parse({}),
55
- ),
56
46
  });
57
47
 
58
48
  export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
@@ -12,13 +12,9 @@ export type {
12
12
  IngressConfig,
13
13
  LogFileConfig,
14
14
  MemoryConfig,
15
- MemoryConflictsConfig,
16
15
  MemoryEmbeddingsConfig,
17
- MemoryEntityConfig,
18
16
  MemoryExtractionConfig,
19
17
  MemoryJobsConfig,
20
- MemoryProfileConfig,
21
- MemoryRerankingConfig,
22
18
  MemoryRetentionConfig,
23
19
  MemoryRetrievalConfig,
24
20
  MemorySegmentationConfig,
@@ -671,7 +671,10 @@ function countPersistedMessages(messages: Message[]): number {
671
671
  function isToolResultOnly(message: Message): boolean {
672
672
  return (
673
673
  message.content.length > 0 &&
674
- message.content.every((block) => block.type === "tool_result")
674
+ message.content.every(
675
+ (block) =>
676
+ block.type === "tool_result" || block.type === "web_search_tool_result",
677
+ )
675
678
  );
676
679
  }
677
680
 
@@ -3,7 +3,13 @@
3
3
  * Watches workspace files (config, prompts), protected directory
4
4
  * (trust rules, secret allowlist), and skills directories for changes.
5
5
  */
6
- import { existsSync, type FSWatcher, readdirSync, watch } from "node:fs";
6
+ import {
7
+ existsSync,
8
+ type FSWatcher,
9
+ mkdirSync,
10
+ readdirSync,
11
+ watch,
12
+ } from "node:fs";
7
13
  import { join } from "node:path";
8
14
 
9
15
  import { getConfig, invalidateConfigCache } from "../config/loader.js";
@@ -98,8 +104,14 @@ export class ConfigWatcher {
98
104
  * Start all file watchers. `onSessionEvict` is called when watched
99
105
  * files change and sessions need to be evicted for reload.
100
106
  * `onIdentityChanged` is called when IDENTITY.md changes on disk.
107
+ * `onMcpReload` is called when the MCP section of config.json changes
108
+ * or when a signal file appears in the workspace `signals/` directory.
101
109
  */
102
- start(onSessionEvict: () => void, onIdentityChanged?: () => void): void {
110
+ start(
111
+ onSessionEvict: () => void,
112
+ onIdentityChanged?: () => void,
113
+ onMcpReload?: () => void,
114
+ ): void {
103
115
  const workspaceDir = getWorkspaceDir();
104
116
  const protectedDir = join(getRootDir(), "protected");
105
117
 
@@ -107,8 +119,17 @@ export class ConfigWatcher {
107
119
  "config.json": () => {
108
120
  if (this.suppressReload) return;
109
121
  try {
122
+ const prevConfig = getConfig();
123
+ const prevMcpFingerprint = JSON.stringify(prevConfig.mcp ?? {});
110
124
  const changed = this.refreshConfigFromSources();
111
- if (changed) onSessionEvict();
125
+ if (changed) {
126
+ onSessionEvict();
127
+ const newConfig = getConfig();
128
+ const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
129
+ if (newMcpFingerprint !== prevMcpFingerprint) {
130
+ onMcpReload?.();
131
+ }
132
+ }
112
133
  } catch (err) {
113
134
  log.error(
114
135
  { err, configPath: join(workspaceDir, "config.json") },
@@ -185,6 +206,7 @@ export class ConfigWatcher {
185
206
  );
186
207
  }
187
208
 
209
+ this.startSignalsWatcher(onMcpReload);
188
210
  this.startSkillsWatchers(onSessionEvict);
189
211
  }
190
212
 
@@ -196,6 +218,42 @@ export class ConfigWatcher {
196
218
  this.watchers = [];
197
219
  }
198
220
 
221
+ private startSignalsWatcher(onMcpReload?: () => void): void {
222
+ const signalsDir = join(getWorkspaceDir(), "signals");
223
+ try {
224
+ if (!existsSync(signalsDir)) {
225
+ mkdirSync(signalsDir, { recursive: true });
226
+ }
227
+ } catch {
228
+ // If we can't create it, watching will also fail — handled below.
229
+ }
230
+
231
+ const signalHandlers: Record<string, () => void> = {
232
+ "mcp-reload": () => {
233
+ onMcpReload?.();
234
+ },
235
+ };
236
+
237
+ try {
238
+ const watcher = watch(signalsDir, (_eventType, filename) => {
239
+ if (!filename) return;
240
+ const file = String(filename);
241
+ if (!signalHandlers[file]) return;
242
+ this.debounceTimers.schedule(`signal:${file}`, () => {
243
+ log.info({ file }, "Signal file detected");
244
+ signalHandlers[file]();
245
+ });
246
+ });
247
+ this.watchers.push(watcher);
248
+ log.info({ dir: signalsDir }, "Watching signals directory");
249
+ } catch (err) {
250
+ log.warn(
251
+ { err, dir: signalsDir },
252
+ "Failed to watch signals directory. Signal-based reload will be unavailable.",
253
+ );
254
+ }
255
+ }
256
+
199
257
  private startSkillsWatchers(onSessionEvict: () => void): void {
200
258
  const skillsDir = getWorkspaceSkillsDir();
201
259
  if (!existsSync(skillsDir)) return;
@@ -143,7 +143,7 @@ function healthCheckHost(host: string): string {
143
143
  /** Hit the daemon's HTTP /healthz endpoint. Returns true if it responds
144
144
  * with HTTP 200 within the timeout — false on connection refused, timeout,
145
145
  * or any other error. */
146
- async function isHttpHealthy(): Promise<boolean> {
146
+ export async function isHttpHealthy(): Promise<boolean> {
147
147
  const host = healthCheckHost(getRuntimeHttpHost());
148
148
  const port = getRuntimeHttpPort();
149
149
  try {
@@ -33,10 +33,6 @@ const WEEKDAY_NAMES = [
33
33
  "Friday",
34
34
  "Saturday",
35
35
  ] as const;
36
- const TIMEZONE_SUBJECT_LINE_RE = /^\s*-\s*time\s*zone\s*:\s*(.+)$/i;
37
- const TIMEZONE_SUBJECT_COMPACT_RE = /^\s*-\s*timezone\s*:\s*(.+)$/i;
38
- const TIMEZONE_TOKEN_RE =
39
- /\b(?:[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)+|(?:UTC|GMT)(?:[+-]\d{1,2}(?::?\d{2})?)?)\b/gi;
40
36
  const UTC_GMT_OFFSET_TOKEN_RE = /^(?:UTC|GMT)([+-])(\d{1,2})(?::?(\d{2}))?$/i;
41
37
 
42
38
  function normalizeOffsetToken(offsetToken: string): string {
@@ -111,6 +107,17 @@ function canonicalizeTimeZone(timeZone: string): string | null {
111
107
  return null;
112
108
  }
113
109
  }
110
+ // Check abbreviation mapping before Intl (many abbreviations are not recognized by Intl)
111
+ const abbrIana = TIMEZONE_ABBREVIATIONS[trimmed.toUpperCase()];
112
+ if (abbrIana) {
113
+ try {
114
+ return new Intl.DateTimeFormat("en-US", {
115
+ timeZone: abbrIana,
116
+ }).resolvedOptions().timeZone;
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
114
121
  try {
115
122
  return new Intl.DateTimeFormat("en-US", {
116
123
  timeZone: trimmed,
@@ -120,47 +127,123 @@ function canonicalizeTimeZone(timeZone: string): string | null {
120
127
  }
121
128
  }
122
129
 
123
- function extractTimeZoneCandidates(text: string): string[] {
124
- const matches = (text.match(TIMEZONE_TOKEN_RE) ?? [])
125
- .map((token) => token.trim())
126
- .filter((token) => token.length > 0);
127
- const ianaTokens = matches.filter((token) => token.includes("/"));
128
- const offsetTokens = matches.filter((token) => !token.includes("/"));
129
- return [...ianaTokens, ...offsetTokens];
130
- }
130
+ /**
131
+ * Common timezone abbreviation → IANA identifier mapping.
132
+ * Used as a fallback when `Intl.DateTimeFormat` does not recognize the abbreviation.
133
+ */
134
+ const TIMEZONE_ABBREVIATIONS: Record<string, string> = {
135
+ // North America
136
+ PST: "America/Los_Angeles",
137
+ PDT: "America/Los_Angeles",
138
+ MST: "America/Denver",
139
+ MDT: "America/Denver",
140
+ CST: "America/Chicago",
141
+ CDT: "America/Chicago",
142
+ EST: "America/New_York",
143
+ EDT: "America/New_York",
144
+ AKST: "America/Anchorage",
145
+ AKDT: "America/Anchorage",
146
+ HST: "Pacific/Honolulu",
147
+ AST: "America/Puerto_Rico",
148
+ NST: "America/St_Johns",
149
+ NDT: "America/St_Johns",
150
+ // Europe
151
+ BST: "Europe/London",
152
+ CET: "Europe/Paris",
153
+ CEST: "Europe/Paris",
154
+ EET: "Europe/Athens",
155
+ EEST: "Europe/Athens",
156
+ WEST: "Europe/Lisbon",
157
+ MSK: "Europe/Moscow",
158
+ // Asia / Oceania
159
+ JST: "Asia/Tokyo",
160
+ KST: "Asia/Seoul",
161
+ HKT: "Asia/Hong_Kong",
162
+ SGT: "Asia/Singapore",
163
+ WIB: "Asia/Jakarta",
164
+ PHT: "Asia/Manila",
165
+ PKT: "Asia/Karachi",
166
+ NPT: "Asia/Kathmandu",
167
+ AEST: "Australia/Sydney",
168
+ AEDT: "Australia/Sydney",
169
+ ACST: "Australia/Adelaide",
170
+ ACDT: "Australia/Adelaide",
171
+ AWST: "Australia/Perth",
172
+ NZST: "Pacific/Auckland",
173
+ NZDT: "Pacific/Auckland",
174
+ // South America
175
+ BRT: "America/Sao_Paulo",
176
+ };
131
177
 
132
178
  /**
133
- * Extract a valid user timezone from compiled `<dynamic-user-profile>` text.
179
+ * Regex matching IANA timezone identifiers (e.g. "America/New_York"),
180
+ * UTC/GMT offset tokens (e.g. "UTC+5", "GMT-8:30"), and common
181
+ * timezone abbreviations (e.g. "PST", "EST", "JST").
134
182
  *
135
- * Prefers explicit `timezone:` profile lines, then falls back to scanning the
136
- * full profile body for valid IANA timezone identifiers.
183
+ * Abbreviation alternation is built from `TIMEZONE_ABBREVIATIONS` keys.
137
184
  */
138
- export function extractUserTimeZoneFromDynamicProfile(
139
- profileText: string,
185
+ const TIMEZONE_ABBR_ALTERNATION = Object.keys(TIMEZONE_ABBREVIATIONS).join("|");
186
+ const TIMEZONE_TOKEN_RE = new RegExp(
187
+ `\\b(?:[A-Za-z][A-Za-z0-9_+-]*(?:/[A-Za-z0-9_+-]+)+|(?:UTC|GMT)(?:[+-]\\d{1,2}(?::?\\d{2})?)?|(?:${TIMEZONE_ABBR_ALTERNATION}))\\b`,
188
+ "gi",
189
+ );
190
+
191
+ /**
192
+ * Extract the user's timezone from V2 memory recall injected text.
193
+ *
194
+ * Scans the `<user_identity>` section (if present) for lines containing
195
+ * "timezone" and tries to resolve an IANA identifier. Falls back to
196
+ * scanning the full text body.
197
+ */
198
+ export function extractUserTimeZoneFromRecall(
199
+ injectedText: string,
140
200
  ): string | null {
141
- const trimmed = profileText.trim();
142
- if (trimmed.length === 0) return null;
201
+ if (!injectedText || injectedText.trim().length === 0) return null;
143
202
 
144
- const candidateTexts: string[] = [];
145
- for (const line of trimmed.split("\n")) {
146
- const match =
147
- line.match(TIMEZONE_SUBJECT_LINE_RE) ??
148
- line.match(TIMEZONE_SUBJECT_COMPACT_RE);
149
- if (match) {
150
- candidateTexts.push(match[1]);
203
+ // Prefer lines inside <user_identity> that mention "timezone"
204
+ const identityMatch = injectedText.match(
205
+ /<user_identity>([\s\S]*?)<\/user_identity>/,
206
+ );
207
+ if (identityMatch) {
208
+ const identityBlock = identityMatch[1];
209
+ for (const line of identityBlock.split("\n")) {
210
+ if (/time\s*zone/i.test(line)) {
211
+ for (const token of extractTimeZoneCandidates(line)) {
212
+ const canonical = canonicalizeTimeZone(token);
213
+ if (canonical) return canonical;
214
+ }
215
+ }
151
216
  }
152
- }
153
- candidateTexts.push(trimmed);
154
-
155
- for (const text of candidateTexts) {
156
- for (const token of extractTimeZoneCandidates(text)) {
217
+ // Scan full identity block for any timezone token
218
+ for (const token of extractTimeZoneCandidates(identityBlock)) {
157
219
  const canonical = canonicalizeTimeZone(token);
158
220
  if (canonical) return canonical;
159
221
  }
160
222
  }
223
+
224
+ // Fallback: scan entire injected text for timezone tokens in
225
+ // lines that mention "timezone"
226
+ for (const line of injectedText.split("\n")) {
227
+ if (/time\s*zone/i.test(line)) {
228
+ for (const token of extractTimeZoneCandidates(line)) {
229
+ const canonical = canonicalizeTimeZone(token);
230
+ if (canonical) return canonical;
231
+ }
232
+ }
233
+ }
234
+
161
235
  return null;
162
236
  }
163
237
 
238
+ function extractTimeZoneCandidates(text: string): string[] {
239
+ const matches = (text.match(TIMEZONE_TOKEN_RE) ?? [])
240
+ .map((token) => token.trim())
241
+ .filter((token) => token.length > 0);
242
+ const ianaTokens = matches.filter((token) => token.includes("/"));
243
+ const offsetTokens = matches.filter((token) => !token.includes("/"));
244
+ return [...ianaTokens, ...offsetTokens];
245
+ }
246
+
164
247
  /**
165
248
  * Get the local date parts for a given instant in the specified timezone.
166
249
  */
@@ -421,8 +421,11 @@ export async function handleSessionCreate(
421
421
  pendingInteractions.resolve(requestId);
422
422
  });
423
423
  session.setHostFileProxy(fileProxy);
424
- const cuProxy = new HostCuProxy(sendEvent);
424
+ const cuProxy = new HostCuProxy(sendEvent, (requestId) => {
425
+ pendingInteractions.resolve(requestId);
426
+ });
425
427
  session.setHostCuProxy(cuProxy);
428
+ session.addPreactivatedSkillId("computer-use");
426
429
  }
427
430
  session.updateClient(sendEvent, false);
428
431
  session
@@ -575,23 +578,24 @@ export function handleCancel(msg: CancelRequest, ctx: HandlerContext): void {
575
578
  }
576
579
 
577
580
  /**
578
- * Undo the last message in a session. Returns the removed count, or null if session not found.
581
+ * Undo the last message in a session. Returns the removed count, or null if
582
+ * the conversation does not exist. Restores evicted sessions from the database.
579
583
  */
580
- export function undoLastMessage(
584
+ export async function undoLastMessage(
581
585
  sessionId: string,
582
586
  ctx: HandlerContext,
583
- ): { removedCount: number } | null {
584
- const session = ctx.sessions.get(sessionId);
585
- if (!session) {
587
+ ): Promise<{ removedCount: number } | null> {
588
+ if (!getConversation(sessionId)) {
586
589
  return null;
587
590
  }
591
+ const session = await ctx.getOrCreateSession(sessionId);
588
592
  ctx.touchSession(sessionId);
589
593
  const removedCount = session.undo();
590
594
  return { removedCount };
591
595
  }
592
596
 
593
- export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
594
- const result = undoLastMessage(msg.sessionId, ctx);
597
+ export async function handleUndo(msg: UndoRequest, ctx: HandlerContext): Promise<void> {
598
+ const result = await undoLastMessage(msg.sessionId, ctx);
595
599
  if (!result) {
596
600
  ctx.send({ type: "error", message: "No active session" });
597
601
  return;
@@ -606,17 +610,18 @@ export function handleUndo(msg: UndoRequest, ctx: HandlerContext): void {
606
610
  /**
607
611
  * Regenerate the last assistant response for a session. The caller provides
608
612
  * a `sendEvent` callback for delivering streaming events via HTTP/SSE.
609
- * Returns null if the session is not found. Throws on regeneration errors.
613
+ * Returns null if the conversation does not exist. Restores evicted sessions
614
+ * from the database when needed. Throws on regeneration errors.
610
615
  */
611
616
  export async function regenerateResponse(
612
617
  sessionId: string,
613
618
  ctx: HandlerContext,
614
619
  sendEvent: (event: ServerMessage) => void,
615
620
  ): Promise<{ requestId: string } | null> {
616
- const session = ctx.sessions.get(sessionId);
617
- if (!session) {
621
+ if (!getConversation(sessionId)) {
618
622
  return null;
619
623
  }
624
+ const session = await ctx.getOrCreateSession(sessionId);
620
625
  ctx.touchSession(sessionId);
621
626
  session.updateClient(sendEvent, false);
622
627
  const requestId = uuid();
@@ -647,11 +652,11 @@ export async function handleRegenerate(
647
652
  msg: RegenerateRequest,
648
653
  ctx: HandlerContext,
649
654
  ): Promise<void> {
650
- const session = ctx.sessions.get(msg.sessionId);
651
- if (!session) {
655
+ if (!getConversation(msg.sessionId)) {
652
656
  ctx.send({ type: "error", message: "No active session" });
653
657
  return;
654
658
  }
659
+ const session = await ctx.getOrCreateSession(msg.sessionId);
655
660
 
656
661
  const regenerateChannel =
657
662
  parseChannelId(session.getTurnChannelContext()?.assistantMessageChannel) ??
@@ -250,12 +250,20 @@ export interface SkillListItem {
250
250
  provenance: SkillProvenance;
251
251
  }
252
252
 
253
+ /** Sorting rank for provenance-based ordering: first-party first, local last. */
254
+ function provenanceSortRank(p: SkillProvenance): number {
255
+ if (p.kind === "first-party") return 0;
256
+ if (p.kind === "third-party" && p.provider) return 1;
257
+ if (p.kind === "third-party") return 2;
258
+ return 3; // local
259
+ }
260
+
253
261
  export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
254
262
  const config = getConfig();
255
263
  const catalog = loadSkillCatalog();
256
264
  const resolved = resolveSkillStates(catalog, config);
257
265
 
258
- return resolved.map((r) => ({
266
+ const items = resolved.map((r) => ({
259
267
  id: r.summary.id,
260
268
  name: r.summary.displayName,
261
269
  description: r.summary.description,
@@ -272,6 +280,17 @@ export function listSkills(_ctx: SkillOperationContext): SkillListItem[] {
272
280
  userInvocable: r.summary.userInvocable,
273
281
  provenance: resolveProvenance(r.summary),
274
282
  }));
283
+
284
+ // Sort: first-party > third-party with provider > third-party without > local,
285
+ // alphabetical by name within each tier.
286
+ items.sort((a, b) => {
287
+ const rankDiff =
288
+ provenanceSortRank(a.provenance) - provenanceSortRank(b.provenance);
289
+ if (rankDiff !== 0) return rankDiff;
290
+ return a.name.localeCompare(b.name);
291
+ });
292
+
293
+ return items;
275
294
  }
276
295
 
277
296
  export function enableSkill(