@vellumai/assistant 0.4.51 → 0.4.53

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 (220) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/architecture/keychain-broker.md +19 -6
  3. package/docs/architecture/memory.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/approval-cascade.test.ts +3 -1
  6. package/src/__tests__/approval-routes-http.test.ts +0 -1
  7. package/src/__tests__/asset-materialize-tool.test.ts +0 -1
  8. package/src/__tests__/asset-search-tool.test.ts +0 -1
  9. package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
  10. package/src/__tests__/attachments-store.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +6 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +3 -0
  13. package/src/__tests__/btw-routes.test.ts +39 -0
  14. package/src/__tests__/call-controller.test.ts +0 -1
  15. package/src/__tests__/call-domain.test.ts +1 -0
  16. package/src/__tests__/call-routes-http.test.ts +1 -2
  17. package/src/__tests__/canonical-guardian-store.test.ts +33 -2
  18. package/src/__tests__/channel-readiness-routes.test.ts +1 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +1 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +6 -2
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +7 -2
  22. package/src/__tests__/config-loader-backfill.test.ts +1 -2
  23. package/src/__tests__/config-schema.test.ts +6 -37
  24. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
  25. package/src/__tests__/credential-broker-server-use.test.ts +16 -16
  26. package/src/__tests__/credential-security-invariants.test.ts +14 -0
  27. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  28. package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
  29. package/src/__tests__/gateway-only-enforcement.test.ts +0 -2
  30. package/src/__tests__/host-shell-tool.test.ts +0 -1
  31. package/src/__tests__/http-user-message-parity.test.ts +19 -0
  32. package/src/__tests__/list-messages-attachments.test.ts +0 -1
  33. package/src/__tests__/log-export-workspace.test.ts +233 -0
  34. package/src/__tests__/managed-proxy-context.test.ts +1 -1
  35. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  36. package/src/__tests__/media-generate-image.test.ts +7 -2
  37. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  38. package/src/__tests__/memory-regressions.test.ts +0 -1
  39. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  40. package/src/__tests__/migration-export-http.test.ts +0 -1
  41. package/src/__tests__/migration-import-commit-http.test.ts +0 -1
  42. package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
  43. package/src/__tests__/migration-validate-http.test.ts +0 -1
  44. package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
  45. package/src/__tests__/oauth-cli.test.ts +1 -10
  46. package/src/__tests__/oauth-store.test.ts +3 -5
  47. package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
  48. package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
  49. package/src/__tests__/onboarding-template-contract.test.ts +1 -2
  50. package/src/__tests__/pricing.test.ts +0 -11
  51. package/src/__tests__/provider-commit-message-generator.test.ts +21 -14
  52. package/src/__tests__/provider-fail-open-selection.test.ts +9 -8
  53. package/src/__tests__/provider-managed-proxy-integration.test.ts +27 -24
  54. package/src/__tests__/provider-registry-ollama.test.ts +8 -2
  55. package/src/__tests__/recording-handler.test.ts +0 -1
  56. package/src/__tests__/relay-server.test.ts +0 -1
  57. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  58. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
  59. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  60. package/src/__tests__/script-proxy-injection-runtime.test.ts +4 -0
  61. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -1
  62. package/src/__tests__/secret-scanner-executor.test.ts +0 -1
  63. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  64. package/src/__tests__/session-abort-tool-results.test.ts +3 -1
  65. package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
  66. package/src/__tests__/session-agent-loop.test.ts +2 -2
  67. package/src/__tests__/session-confirmation-signals.test.ts +3 -1
  68. package/src/__tests__/session-error.test.ts +5 -4
  69. package/src/__tests__/session-history-web-search.test.ts +34 -9
  70. package/src/__tests__/session-pre-run-repair.test.ts +3 -1
  71. package/src/__tests__/session-provider-retry-repair.test.ts +31 -26
  72. package/src/__tests__/session-queue.test.ts +3 -1
  73. package/src/__tests__/session-runtime-assembly.test.ts +118 -0
  74. package/src/__tests__/session-slash-known.test.ts +31 -13
  75. package/src/__tests__/session-slash-queue.test.ts +3 -1
  76. package/src/__tests__/session-slash-unknown.test.ts +3 -1
  77. package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
  78. package/src/__tests__/session-workspace-injection.test.ts +3 -1
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -1
  80. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  81. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  82. package/src/__tests__/skillssh-registry.test.ts +21 -0
  83. package/src/__tests__/slack-share-routes.test.ts +1 -1
  84. package/src/__tests__/swarm-recursion.test.ts +5 -1
  85. package/src/__tests__/swarm-session-integration.test.ts +25 -14
  86. package/src/__tests__/swarm-tool.test.ts +5 -2
  87. package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
  88. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
  89. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  90. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  91. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  92. package/src/__tests__/tool-executor.test.ts +0 -1
  93. package/src/__tests__/trust-store.test.ts +5 -1
  94. package/src/__tests__/twilio-routes.test.ts +2 -2
  95. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  96. package/src/__tests__/voice-quality.test.ts +2 -1
  97. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  98. package/src/__tests__/web-search.test.ts +1 -1
  99. package/src/agent/loop.ts +17 -1
  100. package/src/bundler/app-bundler.ts +40 -24
  101. package/src/calls/call-controller.ts +16 -0
  102. package/src/calls/relay-server.ts +29 -13
  103. package/src/calls/voice-control-protocol.ts +1 -0
  104. package/src/calls/voice-quality.ts +1 -1
  105. package/src/calls/voice-session-bridge.ts +9 -3
  106. package/src/channels/types.ts +16 -0
  107. package/src/cli/commands/bash.ts +173 -0
  108. package/src/cli/commands/doctor.ts +5 -23
  109. package/src/cli/commands/oauth/connections.ts +4 -2
  110. package/src/cli/commands/oauth/providers.ts +1 -13
  111. package/src/cli/program.ts +2 -0
  112. package/src/cli/reference.ts +1 -0
  113. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -1
  114. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
  115. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
  116. package/src/config/bundled-skills/messaging/TOOLS.json +41 -1
  117. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  118. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -1
  119. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -1
  120. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -1
  121. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -1
  122. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -1
  123. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -1
  124. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -1
  125. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -1
  126. package/src/config/bundled-skills/messaging/tools/shared.ts +2 -1
  127. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
  128. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
  129. package/src/config/feature-flag-registry.json +8 -0
  130. package/src/config/loader.ts +7 -135
  131. package/src/config/schema.ts +0 -6
  132. package/src/config/schemas/channels.ts +1 -0
  133. package/src/config/schemas/elevenlabs.ts +2 -2
  134. package/src/contacts/contact-store.ts +21 -25
  135. package/src/contacts/contacts-write.ts +6 -6
  136. package/src/contacts/types.ts +2 -0
  137. package/src/context/token-estimator.ts +35 -2
  138. package/src/context/window-manager.ts +16 -2
  139. package/src/daemon/config-watcher.ts +24 -6
  140. package/src/daemon/context-overflow-reducer.ts +13 -2
  141. package/src/daemon/handlers/config-ingress.ts +25 -8
  142. package/src/daemon/handlers/config-model.ts +21 -15
  143. package/src/daemon/handlers/config-telegram.ts +18 -6
  144. package/src/daemon/handlers/dictation.ts +0 -429
  145. package/src/daemon/handlers/skills.ts +1 -200
  146. package/src/daemon/lifecycle.ts +8 -5
  147. package/src/daemon/message-types/contacts.ts +2 -0
  148. package/src/daemon/message-types/integrations.ts +1 -0
  149. package/src/daemon/message-types/sessions.ts +2 -0
  150. package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
  151. package/src/daemon/server.ts +23 -2
  152. package/src/daemon/session-agent-loop-handlers.ts +1 -1
  153. package/src/daemon/session-agent-loop.ts +27 -79
  154. package/src/daemon/session-error.ts +5 -4
  155. package/src/daemon/session-process.ts +17 -10
  156. package/src/daemon/session-runtime-assembly.ts +50 -0
  157. package/src/daemon/session-slash.ts +32 -20
  158. package/src/daemon/session.ts +1 -0
  159. package/src/events/domain-events.ts +1 -0
  160. package/src/media/app-icon-generator.ts +2 -1
  161. package/src/media/avatar-router.ts +3 -2
  162. package/src/memory/canonical-guardian-store.ts +25 -3
  163. package/src/memory/db-init.ts +12 -0
  164. package/src/memory/embedding-backend.ts +25 -16
  165. package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
  166. package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
  167. package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
  168. package/src/memory/migrations/index.ts +3 -0
  169. package/src/memory/retriever.test.ts +19 -12
  170. package/src/memory/schema/contacts.ts +2 -2
  171. package/src/memory/schema/oauth.ts +0 -1
  172. package/src/oauth/byo-connection.ts +55 -49
  173. package/src/oauth/connect-orchestrator.ts +5 -3
  174. package/src/oauth/connect-types.ts +9 -2
  175. package/src/oauth/manual-token-connection.ts +9 -7
  176. package/src/oauth/oauth-store.ts +2 -8
  177. package/src/oauth/provider-behaviors.ts +10 -0
  178. package/src/oauth/seed-providers.ts +13 -5
  179. package/src/permissions/checker.ts +20 -1
  180. package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
  181. package/src/prompts/system-prompt.ts +2 -11
  182. package/src/prompts/templates/BOOTSTRAP.md +1 -3
  183. package/src/providers/anthropic/client.ts +16 -8
  184. package/src/providers/managed-proxy/constants.ts +1 -1
  185. package/src/providers/registry.ts +21 -15
  186. package/src/providers/types.ts +1 -1
  187. package/src/runtime/auth/route-policy.ts +4 -0
  188. package/src/runtime/channel-invite-transports/telegram.ts +12 -6
  189. package/src/runtime/channel-retry-sweep.ts +6 -0
  190. package/src/runtime/http-types.ts +1 -0
  191. package/src/runtime/middleware/error-handler.ts +1 -2
  192. package/src/runtime/routes/app-management-routes.ts +1 -0
  193. package/src/runtime/routes/btw-routes.ts +20 -1
  194. package/src/runtime/routes/conversation-routes.ts +32 -13
  195. package/src/runtime/routes/inbound-message-handler.ts +10 -2
  196. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  197. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
  198. package/src/runtime/routes/integrations/slack/share.ts +5 -5
  199. package/src/runtime/routes/log-export-routes.ts +122 -10
  200. package/src/runtime/routes/session-query-routes.ts +3 -3
  201. package/src/runtime/routes/settings-routes.ts +53 -0
  202. package/src/runtime/routes/workspace-routes.ts +3 -0
  203. package/src/runtime/verification-templates.ts +1 -1
  204. package/src/security/oauth2.ts +4 -4
  205. package/src/security/secure-keys.ts +24 -3
  206. package/src/security/token-manager.ts +7 -8
  207. package/src/signals/bash.ts +157 -0
  208. package/src/skills/skillssh-registry.ts +6 -1
  209. package/src/swarm/backend-claude-code.ts +6 -6
  210. package/src/swarm/worker-backend.ts +1 -1
  211. package/src/swarm/worker-runner.ts +1 -1
  212. package/src/telegram/bot-username.ts +11 -0
  213. package/src/tools/claude-code/claude-code.ts +4 -4
  214. package/src/tools/credentials/broker.ts +7 -5
  215. package/src/tools/credentials/vault.ts +3 -2
  216. package/src/tools/network/__tests__/web-search.test.ts +18 -86
  217. package/src/tools/network/web-search.ts +9 -15
  218. package/src/util/platform.ts +7 -1
  219. package/src/util/pricing.ts +0 -1
  220. package/src/workspace/provider-commit-message-generator.ts +10 -6
@@ -9,12 +9,7 @@ import {
9
9
  saveRawConfig,
10
10
  } from "../../config/loader.js";
11
11
  import { resolveSkillStates, skillFlagKey } from "../../config/skill-state.js";
12
- import {
13
- ensureSkillIcon,
14
- loadSkillBySelector,
15
- loadSkillCatalog,
16
- type SkillSummary,
17
- } from "../../config/skills.js";
12
+ import { loadSkillCatalog, type SkillSummary } from "../../config/skills.js";
18
13
  import {
19
14
  createTimeout,
20
15
  extractText,
@@ -36,20 +31,6 @@ import {
36
31
  validateManagedSkillId,
37
32
  } from "../../skills/managed-store.js";
38
33
  import { getWorkspaceSkillsDir } from "../../util/platform.js";
39
- import type {
40
- SkillDetailRequest,
41
- SkillsCheckUpdatesRequest,
42
- SkillsConfigureRequest,
43
- SkillsCreateRequest,
44
- SkillsDisableRequest,
45
- SkillsDraftRequest,
46
- SkillsEnableRequest,
47
- SkillsInspectRequest,
48
- SkillsInstallRequest,
49
- SkillsSearchRequest,
50
- SkillsUninstallRequest,
51
- SkillsUpdateRequest,
52
- } from "../message-protocol.js";
53
34
  import {
54
35
  CONFIG_RELOAD_DEBOUNCE_MS,
55
36
  ensureSkillEntry,
@@ -782,183 +763,3 @@ export async function createSkill(
782
763
  return { success: false, error: message };
783
764
  }
784
765
  }
785
-
786
- // ─── HTTP handlers (thin wrappers) ──────────────────────────────────────────
787
-
788
- export function handleSkillsList(ctx: HandlerContext): void {
789
- const skills = listSkills(ctx);
790
- ctx.send({ type: "skills_list_response", skills });
791
- }
792
-
793
- export function handleSkillsEnable(
794
- msg: SkillsEnableRequest,
795
- ctx: HandlerContext,
796
- ): void {
797
- const result = enableSkill(msg.name, ctx);
798
- ctx.send({
799
- type: "skills_operation_response",
800
- operation: "enable",
801
- ...result,
802
- });
803
- }
804
-
805
- export function handleSkillsDisable(
806
- msg: SkillsDisableRequest,
807
- ctx: HandlerContext,
808
- ): void {
809
- const result = disableSkill(msg.name, ctx);
810
- ctx.send({
811
- type: "skills_operation_response",
812
- operation: "disable",
813
- ...result,
814
- });
815
- }
816
-
817
- export function handleSkillsConfigure(
818
- msg: SkillsConfigureRequest,
819
- ctx: HandlerContext,
820
- ): void {
821
- const result = configureSkill(
822
- msg.name,
823
- { env: msg.env, apiKey: msg.apiKey, config: msg.config },
824
- ctx,
825
- );
826
- ctx.send({
827
- type: "skills_operation_response",
828
- operation: "configure",
829
- ...result,
830
- });
831
- }
832
-
833
- export async function handleSkillsInstall(
834
- msg: SkillsInstallRequest,
835
- ctx: HandlerContext,
836
- ): Promise<void> {
837
- const result = await installSkill(
838
- { slug: msg.slug, version: msg.version },
839
- ctx,
840
- );
841
- ctx.send({
842
- type: "skills_operation_response",
843
- operation: "install",
844
- ...result,
845
- });
846
- }
847
-
848
- export async function handleSkillsUninstall(
849
- msg: SkillsUninstallRequest,
850
- ctx: HandlerContext,
851
- ): Promise<void> {
852
- const result = await uninstallSkill(msg.name, ctx);
853
- ctx.send({
854
- type: "skills_operation_response",
855
- operation: "uninstall",
856
- ...result,
857
- });
858
- }
859
-
860
- export async function handleSkillsUpdate(
861
- msg: SkillsUpdateRequest,
862
- ctx: HandlerContext,
863
- ): Promise<void> {
864
- const result = await updateSkill(msg.name, ctx);
865
- ctx.send({
866
- type: "skills_operation_response",
867
- operation: "update",
868
- ...result,
869
- });
870
- }
871
-
872
- export async function handleSkillsCheckUpdates(
873
- _msg: SkillsCheckUpdatesRequest,
874
- ctx: HandlerContext,
875
- ): Promise<void> {
876
- const result = await checkSkillUpdates(ctx);
877
- ctx.send({
878
- type: "skills_operation_response",
879
- operation: "check_updates",
880
- ...result,
881
- });
882
- }
883
-
884
- export async function handleSkillsSearch(
885
- msg: SkillsSearchRequest,
886
- ctx: HandlerContext,
887
- ): Promise<void> {
888
- const result = await searchSkills(msg.query, ctx);
889
- ctx.send({
890
- type: "skills_operation_response",
891
- operation: "search",
892
- ...result,
893
- });
894
- }
895
-
896
- export async function handleSkillsInspect(
897
- msg: SkillsInspectRequest,
898
- ctx: HandlerContext,
899
- ): Promise<void> {
900
- const result = await inspectSkill(msg.slug, ctx);
901
- ctx.send({
902
- type: "skills_inspect_response",
903
- ...result,
904
- });
905
- }
906
-
907
- export async function handleSkillDetail(
908
- msg: SkillDetailRequest,
909
- ctx: HandlerContext,
910
- ): Promise<void> {
911
- const result = loadSkillBySelector(msg.skillId);
912
- if (result.skill) {
913
- const icon = await ensureSkillIcon(
914
- result.skill.directoryPath,
915
- result.skill.displayName,
916
- result.skill.description,
917
- );
918
- ctx.send({
919
- type: "skill_detail_response",
920
- skillId: result.skill.id,
921
- body: result.skill.body,
922
- ...(icon ? { icon } : {}),
923
- });
924
- } else {
925
- ctx.send({
926
- type: "skill_detail_response",
927
- skillId: msg.skillId,
928
- body: "",
929
- error: result.error ?? "Skill not found",
930
- });
931
- }
932
- }
933
-
934
- export async function handleSkillsDraft(
935
- msg: SkillsDraftRequest,
936
- ctx: HandlerContext,
937
- ): Promise<void> {
938
- const result = await draftSkill({ sourceText: msg.sourceText }, ctx);
939
- ctx.send({ type: "skills_draft_response", ...result });
940
- }
941
-
942
- export async function handleSkillsCreate(
943
- msg: SkillsCreateRequest,
944
- ctx: HandlerContext,
945
- ): Promise<void> {
946
- const result = await createSkill(
947
- {
948
- skillId: msg.skillId,
949
- name: msg.name,
950
- description: msg.description,
951
- emoji: msg.emoji,
952
- bodyMarkdown: msg.bodyMarkdown,
953
- userInvocable: msg.userInvocable,
954
- disableModelInvocation: msg.disableModelInvocation,
955
- overwrite: msg.overwrite,
956
- },
957
- ctx,
958
- );
959
- ctx.send({
960
- type: "skills_operation_response",
961
- operation: "create",
962
- ...result,
963
- });
964
- }
@@ -177,15 +177,17 @@ export async function runDaemon(): Promise<void> {
177
177
  }
178
178
  log.info("Daemon startup: DB initialized");
179
179
 
180
- // Expire any pending canonical guardian requests left over from before
181
- // this process started. Their in-memory pending-interaction session
182
- // references are gone, so they can never be completed. The agent loop
183
- // will re-request tool approvals on the next turn.
180
+ // Expire pending interaction-bound canonical guardian requests left over
181
+ // from before this process started. Their in-memory pending-interaction
182
+ // session references are gone, so they can never be completed. Only
183
+ // interaction-bound kinds (tool_approval, pending_question) are expired;
184
+ // persistent kinds (access_request, tool_grant_request) remain valid
185
+ // across restarts.
184
186
  const expiredCount = expireAllPendingCanonicalRequests();
185
187
  if (expiredCount > 0) {
186
188
  log.info(
187
189
  { event: "startup_expired_stale_requests", expiredCount },
188
- `Expired ${expiredCount} stale pending canonical request(s) from previous process`,
190
+ `Expired ${expiredCount} stale interaction-bound canonical request(s) from previous process`,
189
191
  );
190
192
  }
191
193
 
@@ -421,6 +423,7 @@ export async function runDaemon(): Promise<void> {
421
423
  scheduleId: schedule.id,
422
424
  name: schedule.name,
423
425
  },
426
+ dedupeKey: `schedule:complete:${schedule.id}:${Date.now()}`,
424
427
  });
425
428
  },
426
429
  (notification) => {
@@ -58,6 +58,8 @@ export interface ContactChannelPayload {
58
58
  verifiedAt?: number;
59
59
  verifiedVia?: string;
60
60
  lastSeenAt?: number;
61
+ interactionCount?: number;
62
+ lastInteraction?: number;
61
63
  revokedReason?: string;
62
64
  blockedReason?: string;
63
65
  }
@@ -120,6 +120,7 @@ export interface TelegramConfigResponse {
120
120
  type: "telegram_config_response";
121
121
  success: boolean;
122
122
  hasBotToken: boolean;
123
+ botId?: string;
123
124
  botUsername?: string;
124
125
  connected: boolean;
125
126
  hasWebhookSecret: boolean;
@@ -24,6 +24,8 @@ export interface SessionTransportMetadata {
24
24
  hints?: string[];
25
25
  /** Optional concise UX brief for this channel. */
26
26
  uxBrief?: string;
27
+ /** Chat type from the gateway (e.g. "private", "group", "supergroup", "channel"). */
28
+ chatType?: string;
27
29
  }
28
30
 
29
31
  export interface SessionCreateRequest {
@@ -0,0 +1,75 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { parseActualTokensFromError } from "./session-agent-loop.js";
4
+
5
+ describe("parseActualTokensFromError", () => {
6
+ test("returns null for null input", () => {
7
+ expect(parseActualTokensFromError(null)).toBeNull();
8
+ });
9
+
10
+ test("returns null for empty string", () => {
11
+ expect(parseActualTokensFromError("")).toBeNull();
12
+ });
13
+
14
+ test("returns null for unrelated error message", () => {
15
+ expect(
16
+ parseActualTokensFromError("something went wrong"),
17
+ ).toBeNull();
18
+ });
19
+
20
+ test("parses Anthropic-style error: prompt is too long: N tokens > M maximum", () => {
21
+ expect(
22
+ parseActualTokensFromError(
23
+ "prompt is too long: 242201 tokens > 200000 maximum",
24
+ ),
25
+ ).toBe(242201);
26
+ });
27
+
28
+ test("parses wrapped ProviderError from Anthropic", () => {
29
+ expect(
30
+ parseActualTokensFromError(
31
+ "Anthropic API error (400): prompt is too long: 242201 tokens > 200000 maximum",
32
+ ),
33
+ ).toBe(242201);
34
+ });
35
+
36
+ test("parses OpenAI-style error: too many input tokens: N > M", () => {
37
+ expect(
38
+ parseActualTokensFromError("too many input tokens: 150000 > 128000"),
39
+ ).toBe(150000);
40
+ });
41
+
42
+ test("handles comma-separated numbers", () => {
43
+ expect(
44
+ parseActualTokensFromError(
45
+ "prompt is too long: 242,201 tokens > 200,000 maximum",
46
+ ),
47
+ ).toBe(242201);
48
+ });
49
+
50
+ test("handles comma-separated numbers in fallback path", () => {
51
+ expect(
52
+ parseActualTokensFromError("too many input tokens: 150,000 > 128,000"),
53
+ ).toBe(150000);
54
+ });
55
+
56
+ test("parses singular 'token' (without s)", () => {
57
+ expect(
58
+ parseActualTokensFromError("prompt is too long: 1 token > 0 maximum"),
59
+ ).toBe(1);
60
+ });
61
+
62
+ test("handles >= comparator", () => {
63
+ expect(
64
+ parseActualTokensFromError(
65
+ "prompt is too long: 242201 tokens ≥ 200000 maximum",
66
+ ),
67
+ ).toBe(242201);
68
+ });
69
+
70
+ test("returns null when no numeric pattern matches", () => {
71
+ expect(
72
+ parseActualTokensFromError("context window exceeded"),
73
+ ).toBeNull();
74
+ });
75
+ });
@@ -538,6 +538,22 @@ export class DaemonServer {
538
538
  );
539
539
  newSession.updateClient(sendToClient, true);
540
540
  await newSession.loadFromDb();
541
+ // Restore trust/auth context and assistant ID from stored options so
542
+ // that evicted sessions rehydrated by undo/regenerate don't run with
543
+ // unscoped history. Without this, an untrusted actor could operate
544
+ // on the full conversation after eviction.
545
+ if (storedOptions?.assistantId) {
546
+ newSession.setAssistantId(storedOptions.assistantId);
547
+ }
548
+ if (storedOptions?.trustContext) {
549
+ newSession.setTrustContext(storedOptions.trustContext);
550
+ }
551
+ if (storedOptions?.authContext) {
552
+ newSession.setAuthContext(storedOptions.authContext);
553
+ }
554
+ if (storedOptions?.trustContext || storedOptions?.authContext) {
555
+ await newSession.ensureActorScopedHistory();
556
+ }
541
557
  this.applyTransportMetadata(newSession, storedOptions);
542
558
  this.sessions.set(conversationId, newSession);
543
559
  return newSession;
@@ -639,7 +655,12 @@ export class DaemonServer {
639
655
  session.setAuthContext(options?.authContext ?? null);
640
656
  await session.ensureActorScopedHistory();
641
657
  session.setChannelCapabilities(
642
- resolveChannelCapabilities(sourceChannel, sourceInterface),
658
+ resolveChannelCapabilities(
659
+ sourceChannel,
660
+ sourceInterface,
661
+ null,
662
+ options?.transport?.chatType,
663
+ ),
643
664
  );
644
665
  // Only create the host bash proxy for desktop client interfaces that can
645
666
  // execute commands on the user's machine. Non-desktop sessions (CLI,
@@ -778,7 +799,7 @@ export class DaemonServer {
778
799
  sourceInterface,
779
800
  );
780
801
 
781
- const slashResult = resolveSlash(content);
802
+ const slashResult = await resolveSlash(content);
782
803
 
783
804
  if (slashResult.kind === "unknown") {
784
805
  const serverTurnCtx = session.getTurnChannelContext();
@@ -865,7 +865,7 @@ export async function dispatchAgentEvent(
865
865
  type: "tool_result",
866
866
  toolName: "",
867
867
  result: "",
868
- isError: false,
868
+ isError: event.isError,
869
869
  sessionId: deps.ctx.conversationId,
870
870
  toolUseId: event.toolUseId,
871
871
  });
@@ -133,7 +133,7 @@ const log = getLogger("session-agent-loop");
133
133
  *
134
134
  * Returns the actual token count or null if it cannot be parsed.
135
135
  */
136
- function parseActualTokensFromError(
136
+ export function parseActualTokensFromError(
137
137
  errorMessage: string | null,
138
138
  ): number | null {
139
139
  if (!errorMessage) return null;
@@ -710,10 +710,11 @@ export async function runAgentLoopImpl(
710
710
  const preflightBudget = Math.floor(providerMaxTokens * (1 - safetyMargin));
711
711
  let reducerState: ReducerState | undefined;
712
712
 
713
+ const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
713
714
  const preflightTokens = estimatePromptTokens(
714
715
  runMessages,
715
716
  ctx.systemPrompt,
716
- { providerName: ctx.provider.name },
717
+ { providerName: ctx.provider.name, toolTokenBudget },
717
718
  );
718
719
 
719
720
  if (overflowRecovery.enabled && preflightTokens > preflightBudget) {
@@ -747,6 +748,7 @@ export async function runAgentLoopImpl(
747
748
  systemPrompt: ctx.systemPrompt,
748
749
  contextWindow: config.contextWindow,
749
750
  targetTokens: preflightBudget,
751
+ toolTokenBudget,
750
752
  },
751
753
  reducerState,
752
754
  (msgs, signal, opts) =>
@@ -863,7 +865,7 @@ export async function runAgentLoopImpl(
863
865
  const estimated = estimatePromptTokens(
864
866
  checkpoint.history,
865
867
  ctx.systemPrompt,
866
- { providerName: ctx.provider.name },
868
+ { providerName: ctx.provider.name, toolTokenBudget },
867
869
  );
868
870
  if (estimated > midLoopThreshold) {
869
871
  rlog.warn(
@@ -993,6 +995,23 @@ export async function runAgentLoopImpl(
993
995
  );
994
996
  }
995
997
 
998
+ // If mid-loop compaction exhausted all attempts but the agent loop
999
+ // still yielded (yieldedForBudget is true), the turn is incomplete.
1000
+ // Escalate to the convergence loop's more aggressive reducer tiers
1001
+ // (tool-result truncation, media stubbing, injection downgrade)
1002
+ // instead of silently treating an incomplete turn as done.
1003
+ if (yieldedForBudget && !abortController.signal.aborted) {
1004
+ rlog.warn(
1005
+ {
1006
+ phase: "mid-loop-compact",
1007
+ midLoopCompactAttempts,
1008
+ maxAttempts: overflowRecovery.maxAttempts,
1009
+ },
1010
+ "Mid-loop compaction exhausted all attempts — escalating to convergence loop",
1011
+ );
1012
+ state.contextTooLargeDetected = true;
1013
+ }
1014
+
996
1015
  // One-shot ordering error retry
997
1016
  if (
998
1017
  state.orderingErrorDetected &&
@@ -1045,6 +1064,7 @@ export async function runAgentLoopImpl(
1045
1064
  ),
1046
1065
  });
1047
1066
  preRepairMessages = updatedHistory;
1067
+ preRunHistoryLength = updatedHistory.length;
1048
1068
  }
1049
1069
  if (!reducerState) {
1050
1070
  reducerState = createInitialReducerState();
@@ -1061,7 +1081,7 @@ export async function runAgentLoopImpl(
1061
1081
  const estimatedTokensAtOverflow = estimatePromptTokens(
1062
1082
  ctx.messages,
1063
1083
  ctx.systemPrompt,
1064
- { providerName: ctx.provider.name },
1084
+ { providerName: ctx.provider.name, toolTokenBudget },
1065
1085
  );
1066
1086
  let correctedTarget = preflightBudget;
1067
1087
  if (actualTokens && estimatedTokensAtOverflow > 0) {
@@ -1113,6 +1133,7 @@ export async function runAgentLoopImpl(
1113
1133
  systemPrompt: ctx.systemPrompt,
1114
1134
  contextWindow: config.contextWindow,
1115
1135
  targetTokens: correctedTarget,
1136
+ toolTokenBudget,
1116
1137
  },
1117
1138
  reducerState,
1118
1139
  (msgs, signal, opts) =>
@@ -1124,12 +1145,6 @@ export async function runAgentLoopImpl(
1124
1145
  ctx.messages = step.messages;
1125
1146
  currentInjectionMode = step.state.injectionMode;
1126
1147
 
1127
- // If the reducer is now exhausted without compacting, break out
1128
- // so the overflow policy path can attempt emergency compaction.
1129
- if (reducerState.exhausted && !step.compactionResult?.compacted) {
1130
- break;
1131
- }
1132
-
1133
1148
  if (step.compactionResult?.compacted) {
1134
1149
  ctx.contextCompactedMessageCount +=
1135
1150
  step.compactionResult.compactedPersistedMessages;
@@ -1183,77 +1198,10 @@ export async function runAgentLoopImpl(
1183
1198
  );
1184
1199
  }
1185
1200
 
1186
- // When all reducer tiers are exhausted but the context is still too
1187
- // large, attempt one last emergency compaction before consulting the
1188
- // overflow policy. This covers the case where progress was made
1189
- // (messages grew) and the normal tiers couldn't compact enough.
1190
- if (state.contextTooLargeDetected && reducerState.exhausted) {
1191
- const emergencyCompact = await ctx.contextWindowManager.maybeCompact(
1192
- ctx.messages,
1193
- abortController.signal,
1194
- {
1195
- lastCompactedAt: ctx.contextCompactedAt ?? undefined,
1196
- force: true,
1197
- minKeepRecentUserTurns: 0,
1198
- targetInputTokensOverride: correctedTarget,
1199
- },
1200
- );
1201
- if (emergencyCompact.compacted) {
1202
- ctx.messages = emergencyCompact.messages;
1203
- ctx.contextCompactedMessageCount +=
1204
- emergencyCompact.compactedPersistedMessages;
1205
- ctx.contextCompactedAt = Date.now();
1206
- updateConversationContextWindow(
1207
- ctx.conversationId,
1208
- emergencyCompact.summaryText,
1209
- ctx.contextCompactedMessageCount,
1210
- );
1211
- onEvent({
1212
- type: "context_compacted",
1213
- previousEstimatedInputTokens:
1214
- emergencyCompact.previousEstimatedInputTokens,
1215
- estimatedInputTokens: emergencyCompact.estimatedInputTokens,
1216
- maxInputTokens: emergencyCompact.maxInputTokens,
1217
- thresholdTokens: emergencyCompact.thresholdTokens,
1218
- compactedMessages: emergencyCompact.compactedMessages,
1219
- summaryCalls: emergencyCompact.summaryCalls,
1220
- summaryInputTokens: emergencyCompact.summaryInputTokens,
1221
- summaryOutputTokens: emergencyCompact.summaryOutputTokens,
1222
- summaryModel: emergencyCompact.summaryModel,
1223
- });
1224
- emitUsage(
1225
- ctx,
1226
- emergencyCompact.summaryInputTokens,
1227
- emergencyCompact.summaryOutputTokens,
1228
- emergencyCompact.summaryModel,
1229
- onEvent,
1230
- "context_compactor",
1231
- reqId,
1232
- emergencyCompact.summaryCacheCreationInputTokens ?? 0,
1233
- emergencyCompact.summaryCacheReadInputTokens ?? 0,
1234
- collapseRawResponses(emergencyCompact.summaryRawResponses),
1235
- );
1236
-
1237
- runMessages = applyRuntimeInjections(ctx.messages, {
1238
- ...injectionOpts,
1239
- mode: currentInjectionMode,
1240
- });
1241
- preRepairMessages = runMessages;
1242
- preRunHistoryLength = runMessages.length;
1243
- state.contextTooLargeDetected = false;
1244
-
1245
- updatedHistory = await ctx.agentLoop.run(
1246
- runMessages,
1247
- eventHandler,
1248
- abortController.signal,
1249
- reqId,
1250
- onCheckpoint,
1251
- );
1252
- }
1253
- }
1254
-
1255
1201
  // All reducer tiers exhausted but provider still rejects —
1256
1202
  // consult the overflow policy for latest-turn compression.
1203
+ // Emergency compaction is deferred to the policy-gated paths below
1204
+ // so that `request_user_approval` sessions collect consent first.
1257
1205
  if (state.contextTooLargeDetected) {
1258
1206
  const action = resolveOverflowAction({
1259
1207
  overflowRecovery,
@@ -218,7 +218,7 @@ function classifyCore(
218
218
  return {
219
219
  code: "PROVIDER_WEB_SEARCH",
220
220
  userMessage:
221
- "An internal error occurred with web search. Retrying...",
221
+ "An internal error occurred with web search. Please try again.",
222
222
  retryable: true,
223
223
  errorCategory: "web_search_ordering",
224
224
  };
@@ -226,7 +226,7 @@ function classifyCore(
226
226
  if (isOrderingError(message)) {
227
227
  return {
228
228
  code: "PROVIDER_ORDERING",
229
- userMessage: "An internal error occurred. Retrying...",
229
+ userMessage: "An internal error occurred. Please try again.",
230
230
  retryable: true,
231
231
  errorCategory: "tool_ordering",
232
232
  };
@@ -308,7 +308,8 @@ function classifyByMessage(
308
308
  if (isWebSearchOrderingError(message)) {
309
309
  return {
310
310
  code: "PROVIDER_WEB_SEARCH",
311
- userMessage: "An internal error occurred with web search. Retrying...",
311
+ userMessage:
312
+ "An internal error occurred with web search. Please try again.",
312
313
  retryable: true,
313
314
  errorCategory: "web_search_ordering",
314
315
  };
@@ -318,7 +319,7 @@ function classifyByMessage(
318
319
  if (isOrderingError(message)) {
319
320
  return {
320
321
  code: "PROVIDER_ORDERING",
321
- userMessage: "An internal error occurred. Retrying...",
322
+ userMessage: "An internal error occurred. Please try again.",
322
323
  retryable: true,
323
324
  errorCategory: "tool_ordering",
324
325
  };