@vellumai/assistant 0.3.27 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/ARCHITECTURE.md +81 -4
  2. package/Dockerfile +2 -2
  3. package/bun.lock +4 -1
  4. package/docs/trusted-contact-access.md +9 -2
  5. package/package.json +6 -3
  6. package/scripts/ipc/generate-swift.ts +9 -5
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +80 -0
  8. package/src/__tests__/agent-loop-thinking.test.ts +1 -1
  9. package/src/__tests__/agent-loop.test.ts +119 -0
  10. package/src/__tests__/approval-routes-http.test.ts +13 -5
  11. package/src/__tests__/asset-materialize-tool.test.ts +2 -0
  12. package/src/__tests__/asset-search-tool.test.ts +2 -0
  13. package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -2
  14. package/src/__tests__/attachments-store.test.ts +2 -0
  15. package/src/__tests__/browser-skill-endstate.test.ts +3 -3
  16. package/src/__tests__/bundled-asset.test.ts +107 -0
  17. package/src/__tests__/call-controller.test.ts +30 -29
  18. package/src/__tests__/call-routes-http.test.ts +34 -32
  19. package/src/__tests__/call-start-guardian-guard.test.ts +2 -0
  20. package/src/__tests__/canonical-guardian-store.test.ts +636 -0
  21. package/src/__tests__/channel-approval-routes.test.ts +174 -1
  22. package/src/__tests__/channel-invite-transport.test.ts +6 -6
  23. package/src/__tests__/channel-reply-delivery.test.ts +19 -0
  24. package/src/__tests__/channel-retry-sweep.test.ts +130 -0
  25. package/src/__tests__/clarification-resolver.test.ts +2 -0
  26. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  27. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  28. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -1
  29. package/src/__tests__/computer-use-session-lifecycle.test.ts +2 -0
  30. package/src/__tests__/computer-use-session-working-dir.test.ts +1 -0
  31. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +2 -0
  32. package/src/__tests__/config-schema.test.ts +5 -5
  33. package/src/__tests__/config-watcher.test.ts +3 -1
  34. package/src/__tests__/connection-policy.test.ts +14 -5
  35. package/src/__tests__/contacts-tools.test.ts +3 -1
  36. package/src/__tests__/contradiction-checker.test.ts +2 -0
  37. package/src/__tests__/conversation-pairing.test.ts +10 -0
  38. package/src/__tests__/conversation-routes.test.ts +1 -1
  39. package/src/__tests__/credential-security-invariants.test.ts +16 -6
  40. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  41. package/src/__tests__/credential-vault.test.ts +5 -4
  42. package/src/__tests__/daemon-lifecycle.test.ts +9 -0
  43. package/src/__tests__/daemon-server-session-init.test.ts +27 -0
  44. package/src/__tests__/elevenlabs-config.test.ts +2 -0
  45. package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
  46. package/src/__tests__/encrypted-store.test.ts +10 -5
  47. package/src/__tests__/followup-tools.test.ts +3 -1
  48. package/src/__tests__/gateway-only-enforcement.test.ts +21 -21
  49. package/src/__tests__/gmail-integration.test.ts +0 -1
  50. package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
  51. package/src/__tests__/guardian-control-plane-policy.test.ts +19 -19
  52. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
  53. package/src/__tests__/guardian-dispatch.test.ts +21 -19
  54. package/src/__tests__/guardian-grant-minting.test.ts +68 -1
  55. package/src/__tests__/guardian-outbound-http.test.ts +12 -9
  56. package/src/__tests__/guardian-routing-invariants.test.ts +1092 -0
  57. package/src/__tests__/handle-user-message-secret-resume.test.ts +1 -0
  58. package/src/__tests__/handlers-slack-config.test.ts +3 -1
  59. package/src/__tests__/handlers-telegram-config.test.ts +3 -1
  60. package/src/__tests__/handlers-twilio-config.test.ts +3 -1
  61. package/src/__tests__/handlers-twitter-config.test.ts +3 -1
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +318 -0
  63. package/src/__tests__/heartbeat-service.test.ts +20 -0
  64. package/src/__tests__/inbound-invite-redemption.test.ts +33 -0
  65. package/src/__tests__/ingress-reconcile.test.ts +3 -1
  66. package/src/__tests__/ingress-routes-http.test.ts +231 -4
  67. package/src/__tests__/intent-routing.test.ts +2 -0
  68. package/src/__tests__/ipc-snapshot.test.ts +13 -0
  69. package/src/__tests__/mcp-cli.test.ts +77 -0
  70. package/src/__tests__/media-generate-image.test.ts +21 -0
  71. package/src/__tests__/media-reuse-story.e2e.test.ts +2 -0
  72. package/src/__tests__/memory-regressions.test.ts +20 -20
  73. package/src/__tests__/non-member-access-request.test.ts +212 -36
  74. package/src/__tests__/notification-decision-fallback.test.ts +63 -3
  75. package/src/__tests__/notification-decision-strategy.test.ts +78 -0
  76. package/src/__tests__/notification-guardian-path.test.ts +15 -15
  77. package/src/__tests__/oauth-connect-handler.test.ts +3 -1
  78. package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
  79. package/src/__tests__/onboarding-starter-tasks.test.ts +4 -4
  80. package/src/__tests__/onboarding-template-contract.test.ts +116 -21
  81. package/src/__tests__/pairing-routes.test.ts +171 -0
  82. package/src/__tests__/playbook-execution.test.ts +3 -1
  83. package/src/__tests__/playbook-tools.test.ts +3 -1
  84. package/src/__tests__/provider-error-scenarios.test.ts +59 -8
  85. package/src/__tests__/proxy-approval-callback.test.ts +2 -0
  86. package/src/__tests__/recording-handler.test.ts +11 -0
  87. package/src/__tests__/recording-intent-handler.test.ts +15 -0
  88. package/src/__tests__/recording-state-machine.test.ts +13 -2
  89. package/src/__tests__/registry.test.ts +7 -3
  90. package/src/__tests__/relay-server.test.ts +148 -28
  91. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -2
  92. package/src/__tests__/runtime-events-sse-parity.test.ts +21 -0
  93. package/src/__tests__/runtime-events-sse.test.ts +4 -2
  94. package/src/__tests__/sandbox-diagnostics.test.ts +2 -0
  95. package/src/__tests__/schedule-tools.test.ts +3 -1
  96. package/src/__tests__/secret-scanner-executor.test.ts +59 -0
  97. package/src/__tests__/secret-scanner.test.ts +8 -0
  98. package/src/__tests__/send-endpoint-busy.test.ts +4 -0
  99. package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
  100. package/src/__tests__/session-abort-tool-results.test.ts +23 -0
  101. package/src/__tests__/session-agent-loop.test.ts +16 -0
  102. package/src/__tests__/session-conflict-gate.test.ts +21 -0
  103. package/src/__tests__/session-load-history-repair.test.ts +27 -17
  104. package/src/__tests__/session-pre-run-repair.test.ts +23 -0
  105. package/src/__tests__/session-profile-injection.test.ts +21 -0
  106. package/src/__tests__/session-provider-retry-repair.test.ts +20 -0
  107. package/src/__tests__/session-queue.test.ts +23 -0
  108. package/src/__tests__/session-runtime-assembly.test.ts +126 -59
  109. package/src/__tests__/session-skill-tools.test.ts +27 -5
  110. package/src/__tests__/session-slash-known.test.ts +23 -0
  111. package/src/__tests__/session-slash-queue.test.ts +23 -0
  112. package/src/__tests__/session-slash-unknown.test.ts +23 -0
  113. package/src/__tests__/session-workspace-cache-state.test.ts +7 -0
  114. package/src/__tests__/session-workspace-injection.test.ts +21 -0
  115. package/src/__tests__/session-workspace-tool-tracking.test.ts +21 -0
  116. package/src/__tests__/shell-credential-ref.test.ts +2 -0
  117. package/src/__tests__/skill-feature-flags-integration.test.ts +6 -6
  118. package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
  119. package/src/__tests__/skill-projection-feature-flag.test.ts +22 -0
  120. package/src/__tests__/skills.test.ts +8 -4
  121. package/src/__tests__/slack-channel-config.test.ts +3 -1
  122. package/src/__tests__/subagent-tools.test.ts +19 -0
  123. package/src/__tests__/swarm-recursion.test.ts +2 -0
  124. package/src/__tests__/swarm-session-integration.test.ts +2 -0
  125. package/src/__tests__/swarm-tool.test.ts +2 -0
  126. package/src/__tests__/system-prompt.test.ts +3 -1
  127. package/src/__tests__/task-compiler.test.ts +3 -1
  128. package/src/__tests__/task-management-tools.test.ts +3 -1
  129. package/src/__tests__/task-tools.test.ts +3 -1
  130. package/src/__tests__/terminal-sandbox.test.ts +13 -12
  131. package/src/__tests__/terminal-tools.test.ts +2 -0
  132. package/src/__tests__/tool-approval-handler.test.ts +15 -15
  133. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -0
  134. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -0
  135. package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +48 -0
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +22 -19
  138. package/src/__tests__/trusted-contact-verification.test.ts +91 -0
  139. package/src/__tests__/twilio-routes-elevenlabs.test.ts +2 -0
  140. package/src/__tests__/twitter-auth-handler.test.ts +3 -1
  141. package/src/__tests__/twitter-cli-routing.test.ts +3 -1
  142. package/src/__tests__/view-image-tool.test.ts +3 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +329 -0
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +7 -5
  145. package/src/__tests__/voice-session-bridge.test.ts +10 -10
  146. package/src/__tests__/work-item-output.test.ts +3 -1
  147. package/src/__tests__/workspace-lifecycle.test.ts +13 -2
  148. package/src/agent/loop.ts +46 -3
  149. package/src/approvals/guardian-decision-primitive.ts +285 -0
  150. package/src/approvals/guardian-request-resolvers.ts +539 -0
  151. package/src/calls/call-controller.ts +26 -23
  152. package/src/calls/guardian-action-sweep.ts +10 -2
  153. package/src/calls/guardian-dispatch.ts +46 -40
  154. package/src/calls/relay-server.ts +358 -24
  155. package/src/calls/types.ts +1 -1
  156. package/src/calls/voice-session-bridge.ts +3 -3
  157. package/src/cli.ts +12 -0
  158. package/src/config/agent-schema.ts +14 -3
  159. package/src/config/calls-schema.ts +6 -6
  160. package/src/config/core-schema.ts +3 -3
  161. package/src/config/feature-flag-registry.json +8 -0
  162. package/src/config/mcp-schema.ts +1 -1
  163. package/src/config/memory-schema.ts +27 -19
  164. package/src/config/schema.ts +21 -21
  165. package/src/config/skills-schema.ts +7 -7
  166. package/src/config/system-prompt.ts +2 -1
  167. package/src/config/templates/BOOTSTRAP.md +47 -31
  168. package/src/config/templates/USER.md +5 -0
  169. package/src/config/update-bulletin-template-path.ts +4 -1
  170. package/src/config/vellum-skills/trusted-contacts/SKILL.md +149 -21
  171. package/src/daemon/handlers/config-inbox.ts +4 -4
  172. package/src/daemon/handlers/guardian-actions.ts +45 -66
  173. package/src/daemon/handlers/sessions.ts +148 -4
  174. package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
  175. package/src/daemon/ipc-contract/messages.ts +16 -0
  176. package/src/daemon/ipc-contract-inventory.json +1 -0
  177. package/src/daemon/lifecycle.ts +22 -16
  178. package/src/daemon/pairing-store.ts +86 -3
  179. package/src/daemon/server.ts +18 -0
  180. package/src/daemon/session-agent-loop-handlers.ts +5 -4
  181. package/src/daemon/session-agent-loop.ts +33 -6
  182. package/src/daemon/session-lifecycle.ts +25 -17
  183. package/src/daemon/session-memory.ts +2 -2
  184. package/src/daemon/session-process.ts +68 -326
  185. package/src/daemon/session-runtime-assembly.ts +119 -25
  186. package/src/daemon/session-tool-setup.ts +3 -2
  187. package/src/daemon/session.ts +4 -3
  188. package/src/home-base/prebuilt/seed.ts +2 -1
  189. package/src/hooks/templates.ts +2 -1
  190. package/src/memory/canonical-guardian-store.ts +586 -0
  191. package/src/memory/channel-guardian-store.ts +2 -0
  192. package/src/memory/conversation-crud.ts +7 -7
  193. package/src/memory/db-init.ts +20 -0
  194. package/src/memory/embedding-local.ts +257 -39
  195. package/src/memory/embedding-runtime-manager.ts +471 -0
  196. package/src/memory/guardian-action-store.ts +7 -60
  197. package/src/memory/guardian-approvals.ts +9 -4
  198. package/src/memory/guardian-bindings.ts +25 -1
  199. package/src/memory/indexer.ts +3 -3
  200. package/src/memory/ingress-invite-store.ts +45 -0
  201. package/src/memory/job-handlers/backfill.ts +16 -9
  202. package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
  203. package/src/memory/migrations/037-voice-invite-columns.ts +16 -0
  204. package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
  205. package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
  206. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
  207. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
  208. package/src/memory/migrations/index.ts +5 -0
  209. package/src/memory/migrations/registry.ts +5 -0
  210. package/src/memory/qdrant-client.ts +31 -22
  211. package/src/memory/schema-migration.ts +1 -0
  212. package/src/memory/schema.ts +56 -0
  213. package/src/notifications/copy-composer.ts +31 -4
  214. package/src/notifications/decision-engine.ts +57 -0
  215. package/src/permissions/defaults.ts +2 -0
  216. package/src/runtime/access-request-helper.ts +173 -0
  217. package/src/runtime/actor-trust-resolver.ts +221 -0
  218. package/src/runtime/channel-guardian-service.ts +12 -4
  219. package/src/runtime/channel-invite-transports/voice.ts +58 -0
  220. package/src/runtime/channel-retry-sweep.ts +18 -6
  221. package/src/runtime/guardian-context-resolver.ts +38 -71
  222. package/src/runtime/guardian-decision-types.ts +6 -0
  223. package/src/runtime/guardian-reply-router.ts +717 -0
  224. package/src/runtime/http-server.ts +8 -0
  225. package/src/runtime/ingress-service.ts +80 -3
  226. package/src/runtime/invite-redemption-service.ts +141 -2
  227. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
  228. package/src/runtime/routes/channel-route-shared.ts +1 -1
  229. package/src/runtime/routes/channel-routes.ts +1 -1
  230. package/src/runtime/routes/conversation-routes.ts +20 -2
  231. package/src/runtime/routes/guardian-action-routes.ts +100 -109
  232. package/src/runtime/routes/guardian-approval-interception.ts +17 -6
  233. package/src/runtime/routes/inbound-message-handler.ts +205 -529
  234. package/src/runtime/routes/ingress-routes.ts +52 -4
  235. package/src/runtime/routes/pairing-routes.ts +3 -0
  236. package/src/runtime/tool-grant-request-helper.ts +195 -0
  237. package/src/tools/executor.ts +13 -1
  238. package/src/tools/guardian-control-plane-policy.ts +2 -2
  239. package/src/tools/sensitive-output-placeholders.ts +203 -0
  240. package/src/tools/tool-approval-handler.ts +53 -10
  241. package/src/tools/types.ts +13 -2
  242. package/src/util/bundled-asset.ts +31 -0
  243. package/src/util/canonicalize-identity.ts +52 -0
  244. package/src/util/logger.ts +20 -8
  245. package/src/util/platform.ts +10 -0
  246. package/src/util/voice-code.ts +29 -0
  247. package/src/daemon/guardian-invite-intent.ts +0 -124
@@ -1,19 +1,19 @@
1
1
  import { describe, expect,test } from 'bun:test';
2
2
 
3
3
  import { buildChannelAwarenessSection } from '../config/system-prompt.js';
4
- import type { ChannelCapabilities, ChannelTurnContextParams, GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
4
+ import type { ChannelCapabilities, ChannelTurnContextParams, InboundActorContext } from '../daemon/session-runtime-assembly.js';
5
5
  import {
6
6
  applyRuntimeInjections,
7
7
  buildChannelTurnContextBlock,
8
8
  injectChannelCapabilityContext,
9
9
  injectChannelTurnContext,
10
- injectGuardianContext,
10
+ injectInboundActorContext,
11
11
  injectTemporalContext,
12
12
  resolveChannelCapabilities,
13
13
  sanitizePttActivationKey,
14
14
  stripChannelCapabilityContext,
15
15
  stripChannelTurnContext,
16
- stripGuardianContext,
16
+ stripInboundActorContext,
17
17
  stripTemporalContext,
18
18
  } from '../daemon/session-runtime-assembly.js';
19
19
  import type { Message } from '../providers/types.js';
@@ -26,6 +26,15 @@ describe('resolveChannelCapabilities', () => {
26
26
  test('defaults to vellum when no source channel is provided', () => {
27
27
  const caps = resolveChannelCapabilities();
28
28
  expect(caps.channel).toBe('vellum');
29
+ // Without a sourceInterface, desktop UI capabilities are false
30
+ expect(caps.dashboardCapable).toBe(false);
31
+ expect(caps.supportsDynamicUi).toBe(false);
32
+ expect(caps.supportsVoiceInput).toBe(false);
33
+ });
34
+
35
+ test('vellum channel with macos interface has full desktop capabilities', () => {
36
+ const caps = resolveChannelCapabilities(undefined, 'macos');
37
+ expect(caps.channel).toBe('vellum');
29
38
  expect(caps.dashboardCapable).toBe(true);
30
39
  expect(caps.supportsDynamicUi).toBe(true);
31
40
  expect(caps.supportsVoiceInput).toBe(true);
@@ -34,41 +43,42 @@ describe('resolveChannelCapabilities', () => {
34
43
  test('defaults to vellum for null source channel', () => {
35
44
  const caps = resolveChannelCapabilities(null);
36
45
  expect(caps.channel).toBe('vellum');
37
- expect(caps.dashboardCapable).toBe(true);
46
+ expect(caps.dashboardCapable).toBe(false);
38
47
  });
39
48
 
40
49
  test('normalises "dashboard" to "vellum"', () => {
41
50
  const caps = resolveChannelCapabilities('dashboard');
42
51
  expect(caps.channel).toBe('vellum');
43
- expect(caps.dashboardCapable).toBe(true);
44
- expect(caps.supportsDynamicUi).toBe(true);
45
- expect(caps.supportsVoiceInput).toBe(true);
52
+ // Without macos interface, capabilities are false
53
+ expect(caps.dashboardCapable).toBe(false);
54
+ expect(caps.supportsDynamicUi).toBe(false);
55
+ expect(caps.supportsVoiceInput).toBe(false);
46
56
  });
47
57
 
48
58
  test('normalises "http-api" to "vellum"', () => {
49
59
  const caps = resolveChannelCapabilities('http-api');
50
60
  expect(caps.channel).toBe('vellum');
51
- expect(caps.dashboardCapable).toBe(true);
52
- expect(caps.supportsDynamicUi).toBe(true);
53
- expect(caps.supportsVoiceInput).toBe(true);
61
+ expect(caps.dashboardCapable).toBe(false);
62
+ expect(caps.supportsDynamicUi).toBe(false);
63
+ expect(caps.supportsVoiceInput).toBe(false);
54
64
  });
55
65
 
56
66
  test('normalises "mac" to "vellum"', () => {
57
67
  const caps = resolveChannelCapabilities('mac');
58
68
  expect(caps.channel).toBe('vellum');
59
- expect(caps.dashboardCapable).toBe(true);
69
+ expect(caps.dashboardCapable).toBe(false);
60
70
  });
61
71
 
62
72
  test('normalises "macos" to "vellum"', () => {
63
73
  const caps = resolveChannelCapabilities('macos');
64
74
  expect(caps.channel).toBe('vellum');
65
- expect(caps.dashboardCapable).toBe(true);
75
+ expect(caps.dashboardCapable).toBe(false);
66
76
  });
67
77
 
68
78
  test('normalises "ios" to "vellum"', () => {
69
79
  const caps = resolveChannelCapabilities('ios');
70
80
  expect(caps.channel).toBe('vellum');
71
- expect(caps.dashboardCapable).toBe(true);
81
+ expect(caps.dashboardCapable).toBe(false);
72
82
  });
73
83
 
74
84
  test('resolves "telegram" as non-dashboard-capable', () => {
@@ -342,8 +352,8 @@ describe('buildChannelAwarenessSection', () => {
342
352
  // ---------------------------------------------------------------------------
343
353
 
344
354
  describe('trust-gating via channel capabilities', () => {
345
- test('vellum channel does not add constraint rules', () => {
346
- const caps = resolveChannelCapabilities('vellum');
355
+ test('vellum channel with macos interface does not add constraint rules', () => {
356
+ const caps = resolveChannelCapabilities('vellum', 'macos');
347
357
  const message: Message = {
348
358
  role: 'user',
349
359
  content: [{ type: 'text', text: 'Enable my microphone' }],
@@ -534,90 +544,146 @@ describe('applyRuntimeInjections with temporalContext', () => {
534
544
  });
535
545
 
536
546
  // ---------------------------------------------------------------------------
537
- // guardian_context
547
+ // inbound_actor_context
538
548
  // ---------------------------------------------------------------------------
539
549
 
540
- describe('injectGuardianContext', () => {
550
+ describe('injectInboundActorContext', () => {
541
551
  const baseUserMessage: Message = {
542
552
  role: 'user',
543
553
  content: [{ type: 'text', text: 'Can you text me updates?' }],
544
554
  };
545
555
 
546
- test('prepends guardian_context block to user message', () => {
547
- const ctx: GuardianRuntimeContext = {
556
+ test('prepends inbound_actor_context block to user message', () => {
557
+ const ctx: InboundActorContext = {
548
558
  sourceChannel: 'sms',
549
- actorRole: 'guardian',
550
- guardianExternalUserId: 'guardian-user-1',
551
- guardianChatId: '+15550001111',
552
- requesterIdentifier: '+15550001111',
553
- requesterExternalUserId: 'guardian-user-1',
554
- requesterChatId: '+15550001111',
559
+ canonicalActorIdentity: 'guardian-user-1',
560
+ actorIdentifier: '+15550001111',
561
+ actorDisplayName: 'Guardian Name',
562
+ actorSenderDisplayName: 'Guardian Name',
563
+ actorMemberDisplayName: 'Guardian Name',
564
+ trustClass: 'guardian',
565
+ guardianIdentity: 'guardian-user-1',
555
566
  };
556
567
 
557
- const result = injectGuardianContext(baseUserMessage, ctx);
568
+ const result = injectInboundActorContext(baseUserMessage, ctx);
558
569
  expect(result.content.length).toBe(2);
559
570
  const injected = result.content[0];
560
571
  expect(injected.type).toBe('text');
561
572
  const text = (injected as { type: 'text'; text: string }).text;
562
- expect(text).toContain('<guardian_context>');
563
- expect(text).toContain('actor_role: guardian');
573
+ expect(text).toContain('<inbound_actor_context>');
574
+ expect(text).toContain('trust_class: guardian');
564
575
  expect(text).toContain('source_channel: sms');
565
- expect(text).toContain('</guardian_context>');
576
+ expect(text).toContain('canonical_actor_identity: guardian-user-1');
577
+ expect(text).toContain('actor_display_name: Guardian Name');
578
+ expect(text).toContain('actor_sender_display_name: Guardian Name');
579
+ expect(text).toContain('actor_member_display_name: Guardian Name');
580
+ expect(text).toContain('</inbound_actor_context>');
581
+ });
582
+
583
+ test('adds nickname guidance when member and sender display names differ', () => {
584
+ const ctx: InboundActorContext = {
585
+ sourceChannel: 'telegram',
586
+ canonicalActorIdentity: 'trusted-user-1',
587
+ actorIdentifier: '@jeff_handle',
588
+ actorDisplayName: 'Jeff',
589
+ actorSenderDisplayName: 'Jeffrey',
590
+ actorMemberDisplayName: 'Jeff',
591
+ trustClass: 'trusted_contact',
592
+ guardianIdentity: 'guardian-user-1',
593
+ memberStatus: 'active',
594
+ memberPolicy: 'allow',
595
+ };
596
+
597
+ const result = injectInboundActorContext(baseUserMessage, ctx);
598
+ const text = (result.content[0] as { type: 'text'; text: string }).text;
599
+ expect(text).toContain('actor_display_name: Jeff');
600
+ expect(text).toContain('actor_sender_display_name: Jeffrey');
601
+ expect(text).toContain('actor_member_display_name: Jeff');
602
+ expect(text).toContain('name_preference_note: actor_member_display_name is the guardian-preferred nickname');
566
603
  });
567
604
 
568
- test('includes behavioral guidance for non-guardian actors', () => {
569
- const ctx: GuardianRuntimeContext = {
605
+ test('includes behavioral guidance for trusted_contact actors', () => {
606
+ const ctx: InboundActorContext = {
570
607
  sourceChannel: 'telegram',
571
- actorRole: 'non-guardian',
572
- guardianExternalUserId: 'guardian-user-1',
573
- guardianChatId: 'chat-1',
574
- requesterIdentifier: '@someone',
575
- requesterExternalUserId: 'other-user-1',
576
- requesterChatId: 'chat-2',
608
+ canonicalActorIdentity: 'other-user-1',
609
+ actorIdentifier: '@someone',
610
+ trustClass: 'trusted_contact',
611
+ guardianIdentity: 'guardian-user-1',
612
+ memberStatus: 'active',
613
+ memberPolicy: 'default',
577
614
  };
578
615
 
579
- const result = injectGuardianContext(baseUserMessage, ctx);
616
+ const result = injectInboundActorContext(baseUserMessage, ctx);
580
617
  const text = (result.content[0] as { type: 'text'; text: string }).text;
581
618
  expect(text).toContain('non-guardian account');
582
619
  expect(text).toContain('Do not explain the verification system');
620
+ expect(text).toContain('member_status: active');
621
+ expect(text).toContain('member_policy: default');
622
+ });
623
+
624
+ test('includes behavioral guidance for unknown actors', () => {
625
+ const ctx: InboundActorContext = {
626
+ sourceChannel: 'telegram',
627
+ canonicalActorIdentity: null,
628
+ trustClass: 'unknown',
629
+ denialReason: 'no_identity',
630
+ };
631
+
632
+ const result = injectInboundActorContext(baseUserMessage, ctx);
633
+ const text = (result.content[0] as { type: 'text'; text: string }).text;
634
+ expect(text).toContain('non-guardian account');
635
+ expect(text).toContain('Do not explain the verification system');
636
+ expect(text).toContain('denial_reason: no_identity');
583
637
  });
584
638
 
585
639
  test('omits non-guardian behavioral guidance for guardian actors', () => {
586
- const ctx: GuardianRuntimeContext = {
640
+ const ctx: InboundActorContext = {
587
641
  sourceChannel: 'telegram',
588
- actorRole: 'guardian',
589
- guardianExternalUserId: 'guardian-user-1',
590
- guardianChatId: 'chat-1',
591
- requesterIdentifier: '@guardian',
592
- requesterExternalUserId: 'guardian-user-1',
593
- requesterChatId: 'chat-1',
642
+ canonicalActorIdentity: 'guardian-user-1',
643
+ actorIdentifier: '@guardian',
644
+ trustClass: 'guardian',
645
+ guardianIdentity: 'guardian-user-1',
594
646
  };
595
647
 
596
- const result = injectGuardianContext(baseUserMessage, ctx);
648
+ const result = injectInboundActorContext(baseUserMessage, ctx);
597
649
  const text = (result.content[0] as { type: 'text'; text: string }).text;
598
650
  expect(text).not.toContain('non-guardian account');
599
651
  });
652
+
653
+ test('omits member_status and member_policy when not provided', () => {
654
+ const ctx: InboundActorContext = {
655
+ sourceChannel: 'sms',
656
+ canonicalActorIdentity: 'user-1',
657
+ trustClass: 'unknown',
658
+ denialReason: 'no_binding',
659
+ };
660
+
661
+ const result = injectInboundActorContext(baseUserMessage, ctx);
662
+ const text = (result.content[0] as { type: 'text'; text: string }).text;
663
+ expect(text).not.toContain('member_status');
664
+ expect(text).not.toContain('member_policy');
665
+ });
600
666
  });
601
667
 
602
- describe('stripGuardianContext', () => {
603
- test('strips guardian_context blocks from user messages', () => {
668
+ describe('stripInboundActorContext', () => {
669
+ test('strips inbound_actor_context blocks from user messages', () => {
604
670
  const messages: Message[] = [
605
671
  {
606
672
  role: 'user',
607
673
  content: [
608
- { type: 'text', text: '<guardian_context>\nactor_role: guardian\n</guardian_context>' },
674
+ { type: 'text', text: '<inbound_actor_context>\ntrust_class: guardian\n</inbound_actor_context>' },
609
675
  { type: 'text', text: 'Hello' },
610
676
  ],
611
677
  },
612
678
  ];
613
- const result = stripGuardianContext(messages);
679
+ const result = stripInboundActorContext(messages);
614
680
  expect(result).toHaveLength(1);
615
681
  expect(result[0].content).toHaveLength(1);
616
682
  expect((result[0].content[0] as { type: 'text'; text: string }).text).toBe('Hello');
617
683
  });
618
684
  });
619
685
 
620
- describe('applyRuntimeInjections with guardianContext', () => {
686
+ describe('applyRuntimeInjections with inboundActorContext', () => {
621
687
  const baseMessages: Message[] = [
622
688
  {
623
689
  role: 'user',
@@ -625,20 +691,21 @@ describe('applyRuntimeInjections with guardianContext', () => {
625
691
  },
626
692
  ];
627
693
 
628
- test('injects guardian context when provided', () => {
694
+ test('injects inbound actor context when provided', () => {
629
695
  const result = applyRuntimeInjections(baseMessages, {
630
- guardianContext: {
696
+ inboundActorContext: {
631
697
  sourceChannel: 'sms',
632
- actorRole: 'non-guardian',
633
- guardianExternalUserId: 'guardian-1',
634
- requesterExternalUserId: 'requester-1',
635
- requesterIdentifier: '+15550002222',
636
- requesterChatId: '+15550002222',
698
+ canonicalActorIdentity: 'requester-1',
699
+ actorIdentifier: '+15550002222',
700
+ trustClass: 'trusted_contact',
701
+ guardianIdentity: 'guardian-1',
702
+ memberStatus: 'active',
703
+ memberPolicy: 'default',
637
704
  },
638
705
  });
639
706
  expect(result).toHaveLength(1);
640
707
  expect(result[0].content).toHaveLength(2);
641
- expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<guardian_context>');
708
+ expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<inbound_actor_context>');
642
709
  });
643
710
  });
644
711
 
@@ -33,6 +33,7 @@ let mockVersionHashErrors: Set<string> = new Set();
33
33
 
34
34
  mock.module('../config/skills.js', () => ({
35
35
  loadSkillCatalog: () => mockCatalog,
36
+ checkSkillRequirements: () => ({ eligible: true, missing: {} }),
36
37
  }));
37
38
 
38
39
  mock.module('../skills/active-skill-tools.js', () => {
@@ -204,6 +205,27 @@ mock.module('../util/logger.js', () => ({
204
205
  }),
205
206
  }));
206
207
 
208
+ mock.module('../config/loader.js', () => ({
209
+ getConfig: () => ({
210
+ skills: { entries: {}, allowBundled: null },
211
+ assistantFeatureFlagValues: {},
212
+ }),
213
+ loadConfig: () => ({
214
+ skills: { entries: {}, allowBundled: null },
215
+ assistantFeatureFlagValues: {},
216
+ }),
217
+ invalidateConfigCache: () => {},
218
+ }));
219
+
220
+ mock.module('../config/assistant-feature-flags.js', () => ({
221
+ isAssistantFeatureFlagEnabled: () => true,
222
+ loadDefaultsRegistry: () => ({}),
223
+ }));
224
+
225
+ mock.module('../config/skill-state.js', () => ({
226
+ skillFlagKey: (skillId: string) => `feature_flags.${skillId}.enabled`,
227
+ }));
228
+
207
229
  // ---------------------------------------------------------------------------
208
230
  // Import module under test (after mocks)
209
231
  // ---------------------------------------------------------------------------
@@ -1673,7 +1695,7 @@ describe('bundled skill: browser', () => {
1673
1695
  sessionState = new Map<string, string>();
1674
1696
  });
1675
1697
 
1676
- test('browser skill activation via loaded_skill marker projects all 10 tool definitions', () => {
1698
+ test('browser skill activation via loaded_skill marker projects all 14 tool definitions', () => {
1677
1699
  mockCatalog = [makeSkill('browser', '/path/to/bundled-skills/browser')];
1678
1700
  mockManifests = { browser: makeManifest([...BROWSER_TOOL_NAMES]) };
1679
1701
 
@@ -1683,7 +1705,7 @@ describe('bundled skill: browser', () => {
1683
1705
 
1684
1706
  const result = projectSkillTools(history, { previouslyActiveSkillIds: sessionState });
1685
1707
 
1686
- expect(result.toolDefinitions).toHaveLength(10);
1708
+ expect(result.toolDefinitions).toHaveLength(14);
1687
1709
  expect(result.toolDefinitions.map((d) => d.name)).toEqual([...BROWSER_TOOL_NAMES]);
1688
1710
  expect(result.allowedToolNames).toEqual(new Set(BROWSER_TOOL_NAMES));
1689
1711
  });
@@ -1717,7 +1739,7 @@ describe('bundled skill: browser', () => {
1717
1739
 
1718
1740
  const tools = mockRegisteredTools.get('browser');
1719
1741
  expect(tools).toBeDefined();
1720
- expect(tools!.length).toBe(10);
1742
+ expect(tools!.length).toBe(14);
1721
1743
 
1722
1744
  for (const tool of tools!) {
1723
1745
  expect(tool.origin).toBe('skill');
@@ -2410,8 +2432,8 @@ describe('browser skill migration harness', () => {
2410
2432
  expect(id1).not.toBe(id2);
2411
2433
  });
2412
2434
 
2413
- test('BROWSER_TOOL_NAMES contains all 10 browser tools', () => {
2414
- expect(BROWSER_TOOL_NAMES).toHaveLength(10);
2435
+ test('BROWSER_TOOL_NAMES contains all 14 browser tools', () => {
2436
+ expect(BROWSER_TOOL_NAMES).toHaveLength(14);
2415
2437
  expect(BROWSER_TOOL_NAMES).toContain('browser_navigate');
2416
2438
  expect(BROWSER_TOOL_NAMES).toContain('browser_fill_credential');
2417
2439
  });
@@ -30,6 +30,8 @@ mock.module('../providers/registry.js', () => ({
30
30
 
31
31
  mock.module('../config/loader.js', () => ({
32
32
  getConfig: () => ({
33
+ ui: {},
34
+
33
35
  provider: 'mock-provider',
34
36
  maxTokens: 4096,
35
37
  thinking: false,
@@ -62,6 +64,13 @@ mock.module('../security/secret-allowlist.js', () => ({
62
64
  }));
63
65
 
64
66
  mock.module('../memory/conversation-store.js', () => ({
67
+ getConversationThreadType: () => 'default',
68
+ setConversationOriginChannelIfUnset: () => {},
69
+ updateConversationContextWindow: () => {},
70
+ deleteMessageById: () => {},
71
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
72
+ getConversationOriginInterface: () => null,
73
+ getConversationOriginChannel: () => null,
65
74
  getMessages: () => [],
66
75
  getConversation: () => ({
67
76
  id: 'conv-1',
@@ -165,6 +174,20 @@ mock.module('../agent/loop.js', () => ({
165
174
  }
166
175
  },
167
176
  }));
177
+ mock.module('../memory/canonical-guardian-store.js', () => ({
178
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
179
+ listCanonicalGuardianRequests: () => [],
180
+ createCanonicalGuardianRequest: () => ({ id: 'mock-cg-id', code: 'MOCK', status: 'pending' }),
181
+ getCanonicalGuardianRequest: () => null,
182
+ getCanonicalGuardianRequestByCode: () => null,
183
+ updateCanonicalGuardianRequest: () => {},
184
+ resolveCanonicalGuardianRequest: () => {},
185
+ createCanonicalGuardianDelivery: () => ({ id: 'mock-cgd-id' }),
186
+ listCanonicalGuardianDeliveries: () => [],
187
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
188
+ updateCanonicalGuardianDelivery: () => {},
189
+ generateCanonicalRequestCode: () => 'MOCK-CODE',
190
+ }));
168
191
 
169
192
  // ---------------------------------------------------------------------------
170
193
  // Import Session AFTER mocks are registered.
@@ -30,6 +30,8 @@ mock.module('../providers/registry.js', () => ({
30
30
 
31
31
  mock.module('../config/loader.js', () => ({
32
32
  getConfig: () => ({
33
+ ui: {},
34
+
33
35
  provider: 'mock-provider',
34
36
  maxTokens: 4096,
35
37
  thinking: false,
@@ -62,6 +64,13 @@ mock.module('../security/secret-allowlist.js', () => ({
62
64
  }));
63
65
 
64
66
  mock.module('../memory/conversation-store.js', () => ({
67
+ getConversationThreadType: () => 'default',
68
+ setConversationOriginChannelIfUnset: () => {},
69
+ updateConversationContextWindow: () => {},
70
+ deleteMessageById: () => {},
71
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
72
+ getConversationOriginInterface: () => null,
73
+ getConversationOriginChannel: () => null,
65
74
  getMessages: () => [],
66
75
  getConversation: () => ({
67
76
  id: 'conv-1',
@@ -165,6 +174,20 @@ mock.module('../agent/loop.js', () => ({
165
174
  }
166
175
  },
167
176
  }));
177
+ mock.module('../memory/canonical-guardian-store.js', () => ({
178
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
179
+ listCanonicalGuardianRequests: () => [],
180
+ createCanonicalGuardianRequest: () => ({ id: 'mock-cg-id', code: 'MOCK', status: 'pending' }),
181
+ getCanonicalGuardianRequest: () => null,
182
+ getCanonicalGuardianRequestByCode: () => null,
183
+ updateCanonicalGuardianRequest: () => {},
184
+ resolveCanonicalGuardianRequest: () => {},
185
+ createCanonicalGuardianDelivery: () => ({ id: 'mock-cgd-id' }),
186
+ listCanonicalGuardianDeliveries: () => [],
187
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
188
+ updateCanonicalGuardianDelivery: () => {},
189
+ generateCanonicalRequestCode: () => 'MOCK-CODE',
190
+ }));
168
191
 
169
192
  // ---------------------------------------------------------------------------
170
193
  // Import Session AFTER mocks are registered.
@@ -30,6 +30,8 @@ mock.module('../providers/registry.js', () => ({
30
30
 
31
31
  mock.module('../config/loader.js', () => ({
32
32
  getConfig: () => ({
33
+ ui: {},
34
+
33
35
  provider: 'mock-provider',
34
36
  maxTokens: 4096,
35
37
  thinking: false,
@@ -64,6 +66,13 @@ mock.module('../security/secret-allowlist.js', () => ({
64
66
  const addMessageCalls: Array<{ convId: string; role: string; content: string }> = [];
65
67
 
66
68
  mock.module('../memory/conversation-store.js', () => ({
69
+ getConversationThreadType: () => 'default',
70
+ setConversationOriginChannelIfUnset: () => {},
71
+ updateConversationContextWindow: () => {},
72
+ deleteMessageById: () => {},
73
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
74
+ getConversationOriginInterface: () => null,
75
+ getConversationOriginChannel: () => null,
67
76
  getMessages: () => [],
68
77
  getConversation: () => ({
69
78
  id: 'conv-1',
@@ -177,6 +186,20 @@ mock.module('../agent/loop.js', () => ({
177
186
  }
178
187
  },
179
188
  }));
189
+ mock.module('../memory/canonical-guardian-store.js', () => ({
190
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
191
+ listCanonicalGuardianRequests: () => [],
192
+ createCanonicalGuardianRequest: () => ({ id: 'mock-cg-id', code: 'MOCK', status: 'pending' }),
193
+ getCanonicalGuardianRequest: () => null,
194
+ getCanonicalGuardianRequestByCode: () => null,
195
+ updateCanonicalGuardianRequest: () => {},
196
+ resolveCanonicalGuardianRequest: () => {},
197
+ createCanonicalGuardianDelivery: () => ({ id: 'mock-cgd-id' }),
198
+ listCanonicalGuardianDeliveries: () => [],
199
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
200
+ updateCanonicalGuardianDelivery: () => {},
201
+ generateCanonicalRequestCode: () => 'MOCK-CODE',
202
+ }));
180
203
 
181
204
  // ---------------------------------------------------------------------------
182
205
  // Import Session AFTER mocks are registered.
@@ -23,6 +23,8 @@ mock.module('../providers/registry.js', () => ({
23
23
 
24
24
  mock.module('../config/loader.js', () => ({
25
25
  getConfig: () => ({
26
+ ui: {},
27
+
26
28
  provider: 'mock-provider',
27
29
  maxTokens: 4096,
28
30
  thinking: false,
@@ -76,6 +78,11 @@ mock.module('../security/secret-allowlist.js', () => ({
76
78
  }));
77
79
 
78
80
  mock.module('../memory/conversation-store.js', () => ({
81
+ getConversationThreadType: () => 'default',
82
+ setConversationOriginChannelIfUnset: () => {},
83
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
84
+ getConversationOriginInterface: () => null,
85
+ getConversationOriginChannel: () => null,
79
86
  getMessages: () => [],
80
87
  getConversation: () => ({
81
88
  id: 'conv-1',
@@ -37,6 +37,8 @@ mock.module('../providers/registry.js', () => ({
37
37
 
38
38
  mock.module('../config/loader.js', () => ({
39
39
  getConfig: () => ({
40
+ ui: {},
41
+
40
42
  provider: 'mock-provider',
41
43
  maxTokens: 4096,
42
44
  thinking: false,
@@ -67,6 +69,11 @@ mock.module('../permissions/trust-store.js', () => ({ addRule: () => {}, findHig
67
69
  mock.module('../security/secret-allowlist.js', () => ({ resetAllowlist: () => {} }));
68
70
 
69
71
  mock.module('../memory/conversation-store.js', () => ({
72
+ getConversationThreadType: () => 'default',
73
+ setConversationOriginChannelIfUnset: () => {},
74
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
75
+ getConversationOriginInterface: () => null,
76
+ getConversationOriginChannel: () => null,
70
77
  getMessages: () => [],
71
78
  getConversation: () => ({
72
79
  id: 'conv-1', contextSummary: null, contextCompactedMessageCount: 0,
@@ -134,6 +141,20 @@ mock.module('../agent/loop.js', () => ({
134
141
  }
135
142
  },
136
143
  }));
144
+ mock.module('../memory/canonical-guardian-store.js', () => ({
145
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
146
+ listCanonicalGuardianRequests: () => [],
147
+ createCanonicalGuardianRequest: () => ({ id: 'mock-cg-id', code: 'MOCK', status: 'pending' }),
148
+ getCanonicalGuardianRequest: () => null,
149
+ getCanonicalGuardianRequestByCode: () => null,
150
+ updateCanonicalGuardianRequest: () => {},
151
+ resolveCanonicalGuardianRequest: () => {},
152
+ createCanonicalGuardianDelivery: () => ({ id: 'mock-cgd-id' }),
153
+ listCanonicalGuardianDeliveries: () => [],
154
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
155
+ updateCanonicalGuardianDelivery: () => {},
156
+ generateCanonicalRequestCode: () => 'MOCK-CODE',
157
+ }));
137
158
 
138
159
  import { Session } from '../daemon/session.js';
139
160
 
@@ -36,6 +36,8 @@ mock.module('../providers/registry.js', () => ({
36
36
 
37
37
  mock.module('../config/loader.js', () => ({
38
38
  getConfig: () => ({
39
+ ui: {},
40
+
39
41
  provider: 'mock-provider',
40
42
  maxTokens: 4096,
41
43
  thinking: false,
@@ -66,6 +68,11 @@ mock.module('../permissions/trust-store.js', () => ({ addRule: () => {}, findHig
66
68
  mock.module('../security/secret-allowlist.js', () => ({ resetAllowlist: () => {} }));
67
69
 
68
70
  mock.module('../memory/conversation-store.js', () => ({
71
+ getConversationThreadType: () => 'default',
72
+ setConversationOriginChannelIfUnset: () => {},
73
+ provenanceFromGuardianContext: () => ({ source: 'user', guardianContext: undefined }),
74
+ getConversationOriginInterface: () => null,
75
+ getConversationOriginChannel: () => null,
69
76
  getMessages: () => [],
70
77
  getConversation: () => ({
71
78
  id: 'conv-1', contextSummary: null, contextCompactedMessageCount: 0,
@@ -124,6 +131,20 @@ mock.module('../agent/loop.js', () => ({
124
131
  }
125
132
  },
126
133
  }));
134
+ mock.module('../memory/canonical-guardian-store.js', () => ({
135
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
136
+ listCanonicalGuardianRequests: () => [],
137
+ createCanonicalGuardianRequest: () => ({ id: 'mock-cg-id', code: 'MOCK', status: 'pending' }),
138
+ getCanonicalGuardianRequest: () => null,
139
+ getCanonicalGuardianRequestByCode: () => null,
140
+ updateCanonicalGuardianRequest: () => {},
141
+ resolveCanonicalGuardianRequest: () => {},
142
+ createCanonicalGuardianDelivery: () => ({ id: 'mock-cgd-id' }),
143
+ listCanonicalGuardianDeliveries: () => [],
144
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
145
+ updateCanonicalGuardianDelivery: () => {},
146
+ generateCanonicalRequestCode: () => 'MOCK-CODE',
147
+ }));
127
148
 
128
149
  import { Session } from '../daemon/session.js';
129
150
 
@@ -20,6 +20,8 @@ mock.module('../tools/registry.js', () => ({
20
20
  // Mock config
21
21
  mock.module('../config/loader.js', () => ({
22
22
  getConfig: () => ({
23
+ ui: {},
24
+
23
25
  timeouts: { shellDefaultTimeoutSec: 120, shellMaxTimeoutSec: 600 },
24
26
  sandbox: { enabled: false, backend: 'none' },
25
27
  secretDetection: { allowOneTimeSend: false },