@elevasis/core 0.7.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/test-utils/index.d.ts +3122 -0
- package/dist/test-utils/index.js +386 -0
- package/package.json +6 -1
- package/src/README.md +39 -36
- package/src/__tests__/publish.test.ts +18 -13
- package/src/__tests__/{template-foundations-compatibility.test.ts → template-core-compatibility.test.ts} +99 -99
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +1135 -1131
- package/src/_gen/__tests__/scaffold-contracts.test.ts +47 -36
- package/src/_gen/scaffold-contracts.ts +45 -45
- package/src/auth/multi-tenancy/credentials/README.md +38 -38
- package/src/auth/multi-tenancy/credentials/index.ts +6 -6
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +39 -39
- package/src/auth/multi-tenancy/credentials/server/service.ts +60 -60
- package/src/auth/multi-tenancy/index.ts +17 -17
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +107 -107
- package/src/auth/multi-tenancy/invitations/index.ts +37 -37
- package/src/auth/multi-tenancy/invitations/invitation.ts +86 -86
- package/src/auth/multi-tenancy/invitations/server/index.ts +25 -25
- package/src/auth/multi-tenancy/invitations/server/transforms.ts +24 -24
- package/src/auth/multi-tenancy/invitations/server/workos.ts +24 -24
- package/src/auth/multi-tenancy/invitations/supabase.ts +50 -50
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +126 -126
- package/src/auth/multi-tenancy/memberships/index.ts +21 -21
- package/src/auth/multi-tenancy/memberships/membership.ts +138 -138
- package/src/auth/multi-tenancy/memberships/server/index.ts +15 -15
- package/src/auth/multi-tenancy/memberships/server/transforms.ts +32 -32
- package/src/auth/multi-tenancy/memberships/server/workos.ts +21 -21
- package/src/auth/multi-tenancy/memberships/supabase.ts +46 -46
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +128 -128
- package/src/auth/multi-tenancy/organizations/index.ts +23 -23
- package/src/auth/multi-tenancy/organizations/organization.ts +24 -24
- package/src/auth/multi-tenancy/organizations/server/index.ts +10 -10
- package/src/auth/multi-tenancy/organizations/server/transforms.ts +35 -35
- package/src/auth/multi-tenancy/organizations/server/workos.ts +20 -20
- package/src/auth/multi-tenancy/types.ts +83 -83
- package/src/auth/multi-tenancy/users/api-schemas.ts +194 -194
- package/src/auth/multi-tenancy/users/index.ts +27 -27
- package/src/auth/multi-tenancy/users/server/index.ts +19 -19
- package/src/auth/multi-tenancy/users/server/transforms.ts +21 -21
- package/src/auth/multi-tenancy/users/server/workos.ts +16 -16
- package/src/auth/multi-tenancy/users/user.ts +65 -65
- package/src/business/README.md +52 -52
- package/src/business/__tests__/entities-published.test.ts +33 -33
- package/src/business/acquisition/api-schemas.ts +759 -759
- package/src/business/acquisition/index.ts +109 -109
- package/src/business/acquisition/types.ts +402 -402
- package/src/business/base-entities.test.ts +481 -481
- package/src/business/base-entities.ts +241 -241
- package/src/business/entities-published.ts +24 -24
- package/src/business/index.ts +15 -15
- package/src/business/pdf/browser/pdfmake-browser.ts +229 -229
- package/src/business/pdf/index.ts +10 -10
- package/src/business/pdf/server/index.ts +21 -21
- package/src/business/pdf/server/themes/default.ts +8 -8
- package/src/business/pdf/server/themes/index.ts +9 -9
- package/src/business/pdf/server/themes/types.ts +8 -8
- package/src/business/pdf/types.ts +272 -272
- package/src/business/projects/index.ts +2 -2
- package/src/business/projects/sse-events.ts +21 -21
- package/src/business/projects/types.ts +89 -89
- package/src/business/sales/api-schemas.ts +75 -75
- package/src/business/seo/__tests__/linking.test.ts +549 -549
- package/src/business/seo/__tests__/types.test.ts +404 -404
- package/src/business/seo/index.ts +2 -2
- package/src/business/seo/linking.ts +281 -281
- package/src/business/seo/types.ts +199 -199
- package/src/commands/queue/index.ts +3 -3
- package/src/commands/queue/schemas.test.ts +593 -593
- package/src/commands/queue/schemas.ts +125 -125
- package/src/commands/queue/sse-events.ts +61 -61
- package/src/commands/queue/types/action.ts +52 -52
- package/src/commands/queue/types/checkpoint.ts +44 -44
- package/src/commands/queue/types/index.ts +7 -7
- package/src/commands/queue/types/task.ts +116 -116
- package/src/commands/queue/types.ts +14 -14
- package/src/content/distribution-metadata.ts +61 -61
- package/src/content/index.ts +10 -10
- package/src/deployments/index.ts +22 -22
- package/src/execution/core/__tests__/archived-logs.test.ts +72 -72
- package/src/execution/core/index.ts +11 -11
- package/src/execution/core/runner-types.ts +80 -80
- package/src/execution/core/server/environment.ts +31 -31
- package/src/execution/core/sse-executions.ts +119 -119
- package/src/execution/core/types.ts +29 -29
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +4 -4
- package/src/execution/engine/__tests__/timeout.test.ts +565 -565
- package/src/execution/engine/agent/__tests__/errors.test.ts +508 -508
- package/src/execution/engine/agent/actions/__tests__/processor.test.ts +531 -531
- package/src/execution/engine/agent/actions/executor.ts +205 -205
- package/src/execution/engine/agent/actions/navigate-knowledge-executor.ts +230 -230
- package/src/execution/engine/agent/actions/processor.ts +116 -116
- package/src/execution/engine/agent/actions/types.ts +70 -70
- package/src/execution/engine/agent/core/agent.ts +810 -810
- package/src/execution/engine/agent/core/types.ts +155 -155
- package/src/execution/engine/agent/errors.ts +251 -251
- package/src/execution/engine/agent/index.ts +78 -78
- package/src/execution/engine/agent/knowledge-map/types.ts +106 -106
- package/src/execution/engine/agent/knowledge-map/utils.ts +101 -101
- package/src/execution/engine/agent/memory/__tests__/manager.test.ts +754 -754
- package/src/execution/engine/agent/memory/domains.ts +99 -99
- package/src/execution/engine/agent/memory/manager.ts +365 -365
- package/src/execution/engine/agent/memory/processor.ts +66 -66
- package/src/execution/engine/agent/memory/types.ts +90 -90
- package/src/execution/engine/agent/memory/utils.ts +134 -134
- package/src/execution/engine/agent/observability/logging.ts +467 -467
- package/src/execution/engine/agent/observability/types.ts +64 -64
- package/src/execution/engine/agent/reasoning/adapters/agent-adapter-helpers.ts +349 -349
- package/src/execution/engine/agent/reasoning/processor.ts +92 -92
- package/src/execution/engine/agent/reasoning/prompt-sections/base-actions.ts +134 -134
- package/src/execution/engine/agent/reasoning/prompt-sections/completion.ts +49 -49
- package/src/execution/engine/agent/reasoning/prompt-sections/knowledge-map.ts +93 -93
- package/src/execution/engine/agent/reasoning/prompt-sections/memory.ts +65 -65
- package/src/execution/engine/agent/reasoning/prompt-sections/tools.ts +44 -44
- package/src/execution/engine/agent/reasoning/request-builder.ts +169 -169
- package/src/execution/engine/agent/reasoning/types.ts +18 -18
- package/src/execution/engine/base/errors.ts +118 -118
- package/src/execution/engine/base/index.ts +2 -2
- package/src/execution/engine/base/logging.ts +31 -31
- package/src/execution/engine/base/serialization.ts +324 -324
- package/src/execution/engine/base/types.ts +126 -126
- package/src/execution/engine/base/utils.ts +41 -41
- package/src/execution/engine/index.ts +434 -434
- package/src/execution/engine/interface/index.ts +1 -1
- package/src/execution/engine/interface/types.ts +62 -62
- package/src/execution/engine/llm/__tests__/model-info.test.ts +50 -50
- package/src/execution/engine/llm/__tests__/model-validation.test.ts +321 -321
- package/src/execution/engine/llm/__tests__/response-schema-validator.test.ts +115 -115
- package/src/execution/engine/llm/adapters/__tests__/adapter-factory.test.ts +375 -375
- package/src/execution/engine/llm/adapters/__tests__/anthropic-adapter.test.ts +463 -463
- package/src/execution/engine/llm/adapters/__tests__/anthropic.integration.test.ts +177 -177
- package/src/execution/engine/llm/adapters/__tests__/google-adapter.test.ts +722 -722
- package/src/execution/engine/llm/adapters/__tests__/google.integration.test.ts +376 -376
- package/src/execution/engine/llm/adapters/__tests__/openai-adapter.test.ts +551 -551
- package/src/execution/engine/llm/adapters/__tests__/openrouter-adapter.test.ts +563 -563
- package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +105 -105
- package/src/execution/engine/llm/adapters/__tests__/universal-adapter.test.ts +537 -537
- package/src/execution/engine/llm/adapters/circuit-breaker.ts +147 -147
- package/src/execution/engine/llm/adapters/index.ts +17 -17
- package/src/execution/engine/llm/adapters/mock-adapter.ts +116 -116
- package/src/execution/engine/llm/adapters/server/adapter-factory.ts +130 -130
- package/src/execution/engine/llm/adapters/server/anthropic.ts +137 -137
- package/src/execution/engine/llm/adapters/server/google.ts +283 -283
- package/src/execution/engine/llm/adapters/server/index.ts +12 -12
- package/src/execution/engine/llm/adapters/server/openai.ts +206 -206
- package/src/execution/engine/llm/adapters/server/openrouter.ts +235 -235
- package/src/execution/engine/llm/adapters/universal-adapter.ts +230 -230
- package/src/execution/engine/llm/errors.ts +186 -186
- package/src/execution/engine/llm/model-info.ts +332 -332
- package/src/execution/engine/llm/response-schema-validator.ts +113 -113
- package/src/execution/engine/llm/types.ts +86 -86
- package/src/execution/engine/test-utils/index.ts +6 -6
- package/src/execution/engine/test-utils/mocks.ts +56 -56
- package/src/execution/engine/tools/integration/base-integration-adapter.ts +50 -50
- package/src/execution/engine/tools/integration/index.ts +53 -53
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-adapter.ts +73 -73
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/anymailfinder-tools.ts +209 -209
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-company-email/index.ts +82 -82
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-decision-maker-email/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/find-person-email/index.ts +89 -89
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/fetch/verify-email/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/anymailfinder/index.ts +16 -16
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +293 -293
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +100 -100
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-tools.ts +217 -217
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/get-dataset-items/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/run-actor/index.ts +218 -218
- package/src/execution/engine/tools/integration/server/adapters/apify/fetch/start-actor/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/apify/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +361 -361
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-adapter.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/attio-tools.ts +594 -594
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-attribute/index.ts +214 -214
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-note/index.ts +152 -152
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/create-record/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-note/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/delete-record/index.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.ts +118 -118
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-attributes/index.ts +165 -165
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-notes/index.ts +96 -96
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-objects/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.ts +156 -156
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-attribute/index.ts +220 -220
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/update-record/index.ts +140 -140
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/utils/types.ts +146 -146
- package/src/execution/engine/tools/integration/server/adapters/attio/index.ts +31 -31
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-adapter.ts +1189 -1189
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/google-sheets-tools.ts +641 -641
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/index.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/activate-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/__tests__/index.test.ts +289 -289
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/add-to-campaign/index.ts +154 -154
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/__tests__/index.test.ts +325 -325
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-add-leads/index.ts +153 -153
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/bulk-delete-leads/index.ts +84 -84
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-campaign/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/create-inbox-test/index.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/delete-campaign/index.ts +85 -85
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-account-health/index.ts +91 -91
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign/index.ts +92 -92
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/__tests__/index.test.ts +195 -195
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-campaign-analytics/index.ts +113 -113
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-daily-campaign-analytics/index.ts +104 -104
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-emails/index.ts +155 -155
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/__tests__/index.test.ts +196 -196
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/get-step-analytics/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/__tests__/index.test.ts +189 -189
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-campaigns/index.ts +87 -87
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/list-leads/index.ts +112 -112
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/patch-lead/index.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/pause-campaign/index.ts +86 -86
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/remove-from-subsequence/index.ts +98 -98
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/send-reply/index.ts +126 -126
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/__tests__/index.test.ts +193 -193
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-campaign/index.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/__tests__/index.test.ts +621 -621
- package/src/execution/engine/tools/integration/server/adapters/instantly/fetch/update-interest-status/index.ts +125 -125
- package/src/execution/engine/tools/integration/server/adapters/instantly/index.ts +29 -29
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-adapter.ts +178 -178
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1473 -1473
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/check-credits/index.ts +59 -59
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/fetch/verify-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/index.ts +17 -17
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-adapter.ts +80 -80
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/get-email/index.ts +102 -102
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +134 -134
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +75 -75
- package/src/execution/engine/tools/integration/server/adapters/resend/index.ts +27 -27
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-adapter.ts +108 -108
- package/src/execution/engine/tools/integration/server/adapters/resend/resend-tools.ts +132 -132
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/create-envelope/index.ts +274 -274
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/download-document/index.ts +230 -230
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/get-envelope/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/signature-api/fetch/void-envelope/index.ts +90 -90
- package/src/execution/engine/tools/integration/server/adapters/stripe/fetch/utils/types.ts +210 -210
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-adapter.ts +517 -517
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +309 -309
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/domain-search/index.ts +133 -133
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-finder/index.ts +122 -122
- package/src/execution/engine/tools/integration/server/adapters/tomba/fetch/email-verifier/index.ts +111 -111
- package/src/execution/engine/tools/integration/server/adapters/tomba/index.ts +11 -11
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-adapter.ts +78 -78
- package/src/execution/engine/tools/integration/server/adapters/tomba/tomba-tools.ts +222 -222
- package/src/execution/engine/tools/integration/server/index.ts +61 -61
- package/src/execution/engine/tools/integration/service.ts +161 -161
- package/src/execution/engine/tools/integration/tool.ts +253 -253
- package/src/execution/engine/tools/integration/types/anymailfinder.ts +74 -74
- package/src/execution/engine/tools/integration/types/apify.ts +92 -92
- package/src/execution/engine/tools/integration/types/index.ts +19 -19
- package/src/execution/engine/tools/integration/types/instantly.ts +557 -557
- package/src/execution/engine/tools/integration/types/millionverifier.ts +56 -56
- package/src/execution/engine/tools/integration/types/stripe.ts +162 -162
- package/src/execution/engine/tools/integration/types/tomba.ts +94 -94
- package/src/execution/engine/tools/lead-service-types.ts +884 -884
- package/src/execution/engine/tools/llm/index.ts +11 -11
- package/src/execution/engine/tools/llm/server/index.ts +8 -8
- package/src/execution/engine/tools/llm/server/llm-call-tool.ts +118 -118
- package/src/execution/engine/tools/platform/__tests__/pdf.test.ts +441 -441
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +248 -248
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +319 -319
- package/src/execution/engine/tools/platform/acquisition/index.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +148 -148
- package/src/execution/engine/tools/platform/acquisition/types.ts +260 -260
- package/src/execution/engine/tools/platform/email/index.ts +122 -122
- package/src/execution/engine/tools/platform/email/types.ts +96 -96
- package/src/execution/engine/tools/platform/index.ts +157 -157
- package/src/execution/engine/tools/platform/notification.ts +81 -81
- package/src/execution/engine/tools/platform/pdf/index.ts +110 -110
- package/src/execution/engine/tools/platform/pdf/types.ts +77 -77
- package/src/execution/engine/tools/platform/scheduler.ts +87 -87
- package/src/execution/engine/tools/platform/storage/index.ts +370 -370
- package/src/execution/engine/tools/platform/types.ts +148 -148
- package/src/execution/engine/tools/registry.ts +700 -700
- package/src/execution/engine/tools/tool-maps.ts +786 -786
- package/src/execution/engine/tools/types.ts +233 -233
- package/src/execution/engine/workflow/__tests__/errors.test.ts +139 -139
- package/src/execution/engine/workflow/errors.ts +63 -63
- package/src/execution/engine/workflow/helpers/index.ts +11 -11
- package/src/execution/engine/workflow/helpers/server/index.ts +8 -8
- package/src/execution/engine/workflow/helpers/server/llm-call.ts +93 -93
- package/src/execution/engine/workflow/index.ts +19 -19
- package/src/execution/engine/workflow/log-truncate.ts +26 -26
- package/src/execution/engine/workflow/logging.ts +191 -191
- package/src/execution/engine/workflow/types.ts +182 -182
- package/src/execution/engine/workflow/utils.ts +280 -280
- package/src/execution/engine/workflow/workflow.ts +168 -168
- package/src/execution/index.ts +3 -3
- package/src/execution/scheduler/__tests__/api-schemas.test.ts +733 -733
- package/src/execution/scheduler/__tests__/utils.test.ts +1009 -1009
- package/src/execution/scheduler/api-schemas.ts +296 -296
- package/src/execution/scheduler/index.ts +50 -50
- package/src/execution/scheduler/schemas.ts +264 -264
- package/src/execution/scheduler/types.ts +111 -111
- package/src/execution/scheduler/utils.ts +364 -364
- package/src/forms/index.ts +7 -7
- package/src/forms/schemas.ts +69 -69
- package/src/forms/types.ts +70 -70
- package/src/index.ts +71 -60
- package/src/integrations/credentials/__tests__/schemas.test.ts +82 -82
- package/src/integrations/credentials/__tests__/utils.test.ts +144 -144
- package/src/integrations/credentials/api-schemas.ts +143 -143
- package/src/integrations/credentials/index.ts +32 -32
- package/src/integrations/credentials/schemas.ts +164 -164
- package/src/integrations/credentials/utils.ts +59 -59
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +59 -59
- package/src/integrations/oauth/api-schemas.ts +92 -92
- package/src/integrations/oauth/index.ts +19 -19
- package/src/integrations/oauth/provider-registry.ts +61 -61
- package/src/integrations/oauth/server/__tests__/refresh-concurrent.test.ts +183 -183
- package/src/integrations/oauth/server/__tests__/refresh.test.ts +577 -577
- package/src/integrations/oauth/server/credentials.ts +39 -39
- package/src/integrations/oauth/server/refresh.ts +214 -214
- package/src/integrations/oauth/types.ts +34 -34
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +318 -318
- package/src/integrations/webhook-endpoints/api-schemas.ts +102 -102
- package/src/integrations/webhook-endpoints/index.ts +28 -28
- package/src/integrations/webhook-endpoints/types.ts +51 -51
- package/src/operations/activities/api-schemas.ts +79 -79
- package/src/operations/activities/index.ts +9 -9
- package/src/operations/activities/sse-events.ts +30 -30
- package/src/operations/activities/types.ts +63 -63
- package/src/operations/debug-logs/client.ts +60 -60
- package/src/operations/debug-logs/debug-logger.ts +83 -83
- package/src/operations/debug-logs/index.ts +8 -8
- package/src/operations/debug-logs/server.ts +19 -19
- package/src/operations/debug-logs/types.ts +33 -33
- package/src/operations/index.ts +50 -50
- package/src/operations/notifications/api-schemas.ts +91 -91
- package/src/operations/notifications/index.ts +3 -3
- package/src/operations/notifications/sse-events.ts +21 -21
- package/src/operations/notifications/types.ts +47 -47
- package/src/operations/observability/__tests__/openrouter-cost-flow.test.ts +297 -297
- package/src/operations/observability/__tests__/utils.test.ts +54 -54
- package/src/operations/observability/ai-usage-collector.ts +64 -64
- package/src/operations/observability/index.ts +13 -13
- package/src/operations/observability/metrics-collector.ts +49 -49
- package/src/operations/observability/schemas.ts +39 -39
- package/src/operations/observability/types.ts +463 -463
- package/src/operations/observability/utils.ts +77 -77
- package/src/operations/sessions/__tests__/manager.test.ts +821 -821
- package/src/operations/sessions/index.ts +26 -26
- package/src/operations/sessions/server/manager.ts +90 -90
- package/src/operations/sessions/server/session.ts +180 -180
- package/src/operations/sessions/types.ts +98 -98
- package/src/operations/triggers/index.ts +12 -12
- package/src/operations/triggers/webhook/definitions/instantly-account-error.ts +44 -44
- package/src/operations/triggers/webhook/definitions/instantly-auto-reply-received.ts +51 -51
- package/src/operations/triggers/webhook/definitions/instantly-campaign-completed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-email-bounced.ts +49 -49
- package/src/operations/triggers/webhook/definitions/instantly-lead-unsubscribed.ts +45 -45
- package/src/operations/triggers/webhook/definitions/instantly-reply-received.ts +54 -54
- package/src/operations/triggers/webhook/index.ts +35 -35
- package/src/operations/triggers/webhook/types.ts +74 -74
- package/src/organization-model/README.md +97 -97
- package/src/organization-model/__tests__/defaults.test.ts +175 -175
- package/src/organization-model/__tests__/domains/customers.test.ts +295 -295
- package/src/organization-model/__tests__/domains/goals.test.ts +479 -479
- package/src/organization-model/__tests__/domains/identity.test.ts +279 -279
- package/src/organization-model/__tests__/domains/navigation.test.ts +212 -212
- package/src/organization-model/__tests__/domains/offerings.test.ts +419 -419
- package/src/organization-model/__tests__/domains/operations.test.ts +203 -203
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +362 -362
- package/src/organization-model/__tests__/domains/roles.test.ts +347 -347
- package/src/organization-model/__tests__/domains/statuses.test.ts +243 -243
- package/src/organization-model/__tests__/foundation.test.ts +105 -105
- package/src/organization-model/__tests__/graph.test.ts +894 -894
- package/src/organization-model/__tests__/resolve.test.ts +690 -690
- package/src/organization-model/__tests__/schema.test.ts +407 -407
- package/src/organization-model/contracts.ts +14 -14
- package/src/organization-model/defaults.ts +148 -148
- package/src/organization-model/domains/branding.ts +22 -22
- package/src/organization-model/domains/customers.ts +75 -75
- package/src/organization-model/domains/features.ts +22 -22
- package/src/organization-model/domains/goals.ts +80 -80
- package/src/organization-model/domains/identity.ts +94 -94
- package/src/organization-model/domains/navigation.ts +391 -391
- package/src/organization-model/domains/offerings.ts +66 -66
- package/src/organization-model/domains/operations.ts +85 -85
- package/src/organization-model/domains/projects.ts +48 -48
- package/src/organization-model/domains/prospecting.ts +33 -33
- package/src/organization-model/domains/roles.ts +55 -55
- package/src/organization-model/domains/sales.ts +94 -94
- package/src/organization-model/domains/shared.ts +62 -62
- package/src/organization-model/domains/statuses.ts +130 -130
- package/src/organization-model/foundation.ts +97 -97
- package/src/organization-model/graph/build.ts +399 -399
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/schema.ts +48 -48
- package/src/organization-model/graph/types.ts +40 -40
- package/src/organization-model/index.ts +13 -13
- package/src/organization-model/organization-graph.mdx +272 -272
- package/src/organization-model/organization-model.mdx +320 -320
- package/src/organization-model/published.ts +85 -85
- package/src/organization-model/resolve.ts +66 -66
- package/src/organization-model/schema.ts +287 -287
- package/src/organization-model/types.ts +46 -46
- package/src/platform/api/index.ts +1 -1
- package/src/platform/api/types.ts +35 -35
- package/src/platform/constants/http.ts +37 -37
- package/src/platform/constants/index.ts +5 -5
- package/src/platform/constants/limits.ts +32 -32
- package/src/platform/constants/resilience.ts +51 -51
- package/src/platform/constants/timeouts.ts +20 -20
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/__tests__/resource-registry-static.test.ts +347 -347
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +1028 -1028
- package/src/platform/registry/__tests__/resource-registry.list-executable.test.ts +393 -393
- package/src/platform/registry/__tests__/resource-registry.test.ts +2005 -2005
- package/src/platform/registry/__tests__/serialization.test.ts +1127 -1127
- package/src/platform/registry/command-view.ts +180 -180
- package/src/platform/registry/domains.ts +165 -165
- package/src/platform/registry/index.ts +93 -93
- package/src/platform/registry/reserved.ts +24 -24
- package/src/platform/registry/resource-metadata.ts +59 -59
- package/src/platform/registry/resource-registry.command-queue-groups.test.ts +129 -129
- package/src/platform/registry/resource-registry.ts +876 -876
- package/src/platform/registry/serialization.ts +273 -273
- package/src/platform/registry/serialized-types.ts +231 -231
- package/src/platform/registry/stats-types.ts +66 -66
- package/src/platform/registry/types.ts +404 -404
- package/src/platform/registry/validation.ts +513 -513
- package/src/platform/resilience/__tests__/rate-limiter.test.ts +471 -471
- package/src/platform/resilience/circuit-breaker.ts +164 -164
- package/src/platform/resilience/errors.ts +68 -68
- package/src/platform/resilience/http-error-mapper.ts +129 -129
- package/src/platform/resilience/index.ts +93 -93
- package/src/platform/resilience/rate-limiter-types.ts +46 -46
- package/src/platform/resilience/rate-limiter.ts +140 -140
- package/src/platform/resilience/retry.ts +89 -89
- package/src/platform/resilience/timeout.ts +63 -63
- package/src/platform/sse/events.ts +37 -37
- package/src/platform/sse/index.ts +7 -7
- package/src/platform/utils/__tests__/validation.test.ts +1083 -1083
- package/src/platform/utils/currency.ts +96 -96
- package/src/platform/utils/debounce.ts +52 -52
- package/src/platform/utils/error.ts +41 -41
- package/src/platform/utils/hmac.test.ts +97 -97
- package/src/platform/utils/index.ts +32 -32
- package/src/platform/utils/server/betterstack-logger.ts +210 -210
- package/src/platform/utils/server/hmac.ts +44 -44
- package/src/platform/utils/server/unsubscribe.ts +111 -111
- package/src/platform/utils/token-counter.ts +96 -96
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.ts +268 -268
- package/src/published.ts +1 -1
- package/src/reference/_generated/contracts.md +607 -607
- package/src/reference/glossary.md +105 -105
- package/src/requests/__tests__/api-schemas.test.ts +277 -277
- package/src/requests/api-schemas.ts +83 -83
- package/src/requests/index.ts +1 -1
- package/src/scaffold-registry/__tests__/index.test.ts +17 -0
- package/src/scaffold-registry/__tests__/schema.test.ts +329 -230
- package/src/scaffold-registry/index.ts +205 -189
- package/src/scaffold-registry/schema.ts +196 -128
- package/src/server.ts +272 -272
- package/src/supabase/database.types.ts +2719 -2719
- package/src/supabase/helpers.ts +20 -20
- package/src/supabase/index.ts +52 -52
- package/src/supabase/server/client.ts +58 -58
- package/src/test-utils/README.md +30 -138
- package/src/test-utils/browser-mocks.ts +54 -54
- package/src/test-utils/fixtures/api-keys.ts +52 -52
- package/src/test-utils/fixtures/index.ts +4 -4
- package/src/test-utils/fixtures/memberships.ts +80 -80
- package/src/test-utils/fixtures/organizations.ts +69 -69
- package/src/test-utils/fixtures/users.ts +79 -79
- package/src/test-utils/index.ts +7 -8
- package/src/test-utils/mocks/index.ts +2 -2
- package/src/test-utils/mocks/supabase.ts +142 -142
- package/src/test-utils/mocks/workos.ts +108 -108
- package/src/test-utils/published.ts +4 -0
- package/src/test-utils/rls/RLSTestContext.ts +554 -554
- package/src/test-utils/rls/index.ts +1 -1
|
@@ -1,565 +1,565 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeout Strategy Integration Tests
|
|
3
|
-
* Verifies layered timeout architecture (H-1, M-6, L-3)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
-
import { z } from 'zod'
|
|
8
|
-
import { composeSignal } from '../llm/adapters/server/compose-signal'
|
|
9
|
-
import { executeToolCall } from '../agent/actions/executor'
|
|
10
|
-
import { Agent } from '../agent/core/agent'
|
|
11
|
-
import { AgentTimeoutError, AgentStalledError } from '../agent/errors'
|
|
12
|
-
import type { Tool } from '../tools/types'
|
|
13
|
-
import type { IterationContext, LLMAdapterFactory } from '../agent/core/types'
|
|
14
|
-
import type { ExecutionContext } from '../base/types'
|
|
15
|
-
import { createMockExecutionContext, createMockTool } from '../test-utils/mocks'
|
|
16
|
-
import { MockAdapter } from '../llm/adapters/mock-adapter'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Create a mock LLMAdapterFactory that returns a MockAdapter instance.
|
|
20
|
-
* Used to satisfy the Agent constructor's second parameter.
|
|
21
|
-
*/
|
|
22
|
-
function createMockAdapterFactory(): LLMAdapterFactory {
|
|
23
|
-
const adapter = new MockAdapter()
|
|
24
|
-
return () => adapter
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Test Helpers
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
function createMockIterationContext(overrides?: {
|
|
32
|
-
tools?: Map<string, Tool>
|
|
33
|
-
executionContext?: Partial<ExecutionContext>
|
|
34
|
-
}): IterationContext {
|
|
35
|
-
const memoryHistory: unknown[] = []
|
|
36
|
-
const mockMemoryManager = {
|
|
37
|
-
addToHistory: vi.fn((entry: unknown) => memoryHistory.push(entry)),
|
|
38
|
-
getHistory: vi.fn(() => memoryHistory),
|
|
39
|
-
getHistoryLength: vi.fn(() => memoryHistory.length),
|
|
40
|
-
estimateTokens: vi.fn(() => 0),
|
|
41
|
-
clear: vi.fn(),
|
|
42
|
-
toContext: vi.fn(() => '')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const executionContext: ExecutionContext = {
|
|
46
|
-
executionId: 'test-exec-123',
|
|
47
|
-
organizationId: 'test-org-123',
|
|
48
|
-
organizationName: 'test-org',
|
|
49
|
-
userId: 'test-user-123',
|
|
50
|
-
resourceId: 'test-resource-123',
|
|
51
|
-
executionDepth: 0,
|
|
52
|
-
store: new Map(),
|
|
53
|
-
logger: {
|
|
54
|
-
info: vi.fn(),
|
|
55
|
-
error: vi.fn(),
|
|
56
|
-
warn: vi.fn(),
|
|
57
|
-
debug: vi.fn()
|
|
58
|
-
},
|
|
59
|
-
...overrides?.executionContext
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
executionContext,
|
|
64
|
-
toolRegistry: overrides?.tools ?? new Map(),
|
|
65
|
-
config: {
|
|
66
|
-
type: 'agent' as const,
|
|
67
|
-
resourceId: 'test-agent',
|
|
68
|
-
name: 'Test Agent',
|
|
69
|
-
description: 'Test',
|
|
70
|
-
environment: 'dev',
|
|
71
|
-
systemPrompt: 'Test'
|
|
72
|
-
},
|
|
73
|
-
contract: {
|
|
74
|
-
inputSchema: z.object({}),
|
|
75
|
-
outputSchema: z.object({})
|
|
76
|
-
},
|
|
77
|
-
logger: {
|
|
78
|
-
action: vi.fn(),
|
|
79
|
-
reasoning: vi.fn(),
|
|
80
|
-
lifecycle: vi.fn(),
|
|
81
|
-
toolCall: vi.fn(),
|
|
82
|
-
info: vi.fn(),
|
|
83
|
-
error: vi.fn(),
|
|
84
|
-
warn: vi.fn(),
|
|
85
|
-
debug: vi.fn()
|
|
86
|
-
},
|
|
87
|
-
modelConfig: {
|
|
88
|
-
provider: 'mock' as const,
|
|
89
|
-
model: 'mock',
|
|
90
|
-
apiKey: 'test'
|
|
91
|
-
},
|
|
92
|
-
memoryManager: mockMemoryManager,
|
|
93
|
-
iteration: 1
|
|
94
|
-
} as IterationContext
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Create a tool that hangs forever (never resolves) */
|
|
98
|
-
function createHangingTool(name: string, timeout?: number): Tool {
|
|
99
|
-
return {
|
|
100
|
-
name,
|
|
101
|
-
description: `Hanging tool: ${name}`,
|
|
102
|
-
inputSchema: z.object({ input: z.string() }),
|
|
103
|
-
outputSchema: z.object({ output: z.string() }),
|
|
104
|
-
execute: () => new Promise(() => {}), // Never resolves
|
|
105
|
-
timeout
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Create a tool that takes a specified duration to complete */
|
|
110
|
-
function createSlowTool(name: string, durationMs: number, timeout?: number): Tool {
|
|
111
|
-
return {
|
|
112
|
-
name,
|
|
113
|
-
description: `Slow tool: ${name}`,
|
|
114
|
-
inputSchema: z.object({ input: z.string() }),
|
|
115
|
-
outputSchema: z.object({ output: z.string() }),
|
|
116
|
-
execute: async () => {
|
|
117
|
-
await new Promise((resolve) => setTimeout(resolve, durationMs))
|
|
118
|
-
return { output: 'completed' }
|
|
119
|
-
},
|
|
120
|
-
timeout
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ============================================================================
|
|
125
|
-
// Layer 0: composeSignal() utility
|
|
126
|
-
// ============================================================================
|
|
127
|
-
|
|
128
|
-
describe('composeSignal', () => {
|
|
129
|
-
it('returns signal that fires on timeout', async () => {
|
|
130
|
-
const signal = composeSignal(50) // 50ms timeout
|
|
131
|
-
|
|
132
|
-
expect(signal.aborted).toBe(false)
|
|
133
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
134
|
-
expect(signal.aborted).toBe(true)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('returns signal that fires on execution signal abort', () => {
|
|
138
|
-
const controller = new AbortController()
|
|
139
|
-
const signal = composeSignal(60_000, controller.signal) // Long timeout
|
|
140
|
-
|
|
141
|
-
expect(signal.aborted).toBe(false)
|
|
142
|
-
controller.abort('timeout')
|
|
143
|
-
expect(signal.aborted).toBe(true)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('fires whichever comes first (timeout wins)', async () => {
|
|
147
|
-
const controller = new AbortController()
|
|
148
|
-
const signal = composeSignal(50, controller.signal) // 50ms timeout
|
|
149
|
-
|
|
150
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
151
|
-
expect(signal.aborted).toBe(true)
|
|
152
|
-
// Controller was never aborted, but the composed signal is
|
|
153
|
-
expect(controller.signal.aborted).toBe(false)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('fires whichever comes first (execution signal wins)', () => {
|
|
157
|
-
const controller = new AbortController()
|
|
158
|
-
const signal = composeSignal(60_000, controller.signal) // Long timeout
|
|
159
|
-
|
|
160
|
-
controller.abort('timeout')
|
|
161
|
-
expect(signal.aborted).toBe(true)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('works without execution signal', async () => {
|
|
165
|
-
const signal = composeSignal(50)
|
|
166
|
-
|
|
167
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
168
|
-
expect(signal.aborted).toBe(true)
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
// ============================================================================
|
|
173
|
-
// Layer 1: Tool Executor Timeout
|
|
174
|
-
// ============================================================================
|
|
175
|
-
|
|
176
|
-
describe('Tool Executor Timeout', () => {
|
|
177
|
-
it('times out a hanging tool with custom timeout', async () => {
|
|
178
|
-
const tool = createHangingTool('slow_api', 100) // 100ms timeout
|
|
179
|
-
const toolRegistry = new Map<string, Tool>([['slow_api', tool]])
|
|
180
|
-
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
181
|
-
|
|
182
|
-
await executeToolCall(iterationContext, {
|
|
183
|
-
type: 'tool-call',
|
|
184
|
-
id: 'call-1',
|
|
185
|
-
name: 'slow_api',
|
|
186
|
-
input: { input: 'test' }
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Verify error was added to memory (not thrown - executor catches and stores as 'error' type)
|
|
190
|
-
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
191
|
-
expect.objectContaining({
|
|
192
|
-
type: 'error',
|
|
193
|
-
content: expect.stringContaining('timed out')
|
|
194
|
-
})
|
|
195
|
-
)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('tool timeout error message includes tool name', async () => {
|
|
199
|
-
const tool = createHangingTool('notion_search', 100)
|
|
200
|
-
const toolRegistry = new Map<string, Tool>([['notion_search', tool]])
|
|
201
|
-
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
202
|
-
|
|
203
|
-
await executeToolCall(iterationContext, {
|
|
204
|
-
type: 'tool-call',
|
|
205
|
-
id: 'call-1',
|
|
206
|
-
name: 'notion_search',
|
|
207
|
-
input: { input: 'test' }
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
// Check the logger recorded the timeout with tool name
|
|
211
|
-
expect(iterationContext.logger.toolCall).toHaveBeenCalledWith(
|
|
212
|
-
'notion_search',
|
|
213
|
-
expect.any(Number),
|
|
214
|
-
expect.any(Number),
|
|
215
|
-
expect.any(Number),
|
|
216
|
-
expect.any(Number),
|
|
217
|
-
false,
|
|
218
|
-
expect.stringContaining('notion_search'),
|
|
219
|
-
expect.anything(),
|
|
220
|
-
undefined
|
|
221
|
-
)
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
it('succeeds when tool completes before timeout', async () => {
|
|
225
|
-
const tool = createSlowTool('fast_api', 10, 500) // 10ms execution, 500ms timeout
|
|
226
|
-
const toolRegistry = new Map<string, Tool>([['fast_api', tool]])
|
|
227
|
-
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
228
|
-
|
|
229
|
-
await executeToolCall(iterationContext, {
|
|
230
|
-
type: 'tool-call',
|
|
231
|
-
id: 'call-1',
|
|
232
|
-
name: 'fast_api',
|
|
233
|
-
input: { input: 'test' }
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
// Should have a success result, not an error
|
|
237
|
-
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
238
|
-
expect.objectContaining({
|
|
239
|
-
type: 'tool-result',
|
|
240
|
-
content: expect.stringContaining('completed')
|
|
241
|
-
})
|
|
242
|
-
)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('uses default 30min timeout when tool has no custom timeout', () => {
|
|
246
|
-
const tool = createMockTool('default_tool')
|
|
247
|
-
expect(tool.timeout).toBeUndefined()
|
|
248
|
-
// The executor uses `tool.timeout ?? DEFAULT_TOOL_TIMEOUT` (1_800_000 / 30min)
|
|
249
|
-
// We verify the field is optional and defaults are handled in the executor, not the tool definition
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('execution signal aborts tool immediately', async () => {
|
|
253
|
-
// Tool that hangs forever with a long per-tool timeout
|
|
254
|
-
const tool: Tool = {
|
|
255
|
-
name: 'slow_tool',
|
|
256
|
-
description: 'Hangs forever',
|
|
257
|
-
inputSchema: z.object({ input: z.string() }),
|
|
258
|
-
outputSchema: z.object({ output: z.string() }),
|
|
259
|
-
execute: () => new Promise(() => {}),
|
|
260
|
-
timeout: 60_000 // 60s per-tool timeout
|
|
261
|
-
}
|
|
262
|
-
const toolRegistry = new Map<string, Tool>([['slow_tool', tool]])
|
|
263
|
-
|
|
264
|
-
// Create an AbortController that fires quickly (simulating execution ceiling)
|
|
265
|
-
const controller = new AbortController()
|
|
266
|
-
setTimeout(() => controller.abort('timeout'), 50) // 50ms ceiling
|
|
267
|
-
|
|
268
|
-
const iterationContext = createMockIterationContext({
|
|
269
|
-
tools: toolRegistry,
|
|
270
|
-
executionContext: { signal: controller.signal }
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
await executeToolCall(iterationContext, {
|
|
274
|
-
type: 'tool-call',
|
|
275
|
-
id: 'call-1',
|
|
276
|
-
name: 'slow_tool',
|
|
277
|
-
input: { input: 'test' }
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
// Tool should have been aborted by the execution signal, not the per-tool timeout
|
|
281
|
-
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
282
|
-
expect.objectContaining({
|
|
283
|
-
type: 'error',
|
|
284
|
-
content: expect.stringContaining('timed out')
|
|
285
|
-
})
|
|
286
|
-
)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('passes signal to tool execute options', async () => {
|
|
290
|
-
const executeSpy = vi.fn(async () => ({ output: 'done' }))
|
|
291
|
-
const tool: Tool = {
|
|
292
|
-
name: 'signal_aware_tool',
|
|
293
|
-
description: 'Checks signal',
|
|
294
|
-
inputSchema: z.object({ input: z.string() }),
|
|
295
|
-
outputSchema: z.object({ output: z.string() }),
|
|
296
|
-
execute: executeSpy,
|
|
297
|
-
timeout: 5_000
|
|
298
|
-
}
|
|
299
|
-
const toolRegistry = new Map<string, Tool>([['signal_aware_tool', tool]])
|
|
300
|
-
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
301
|
-
|
|
302
|
-
await executeToolCall(iterationContext, {
|
|
303
|
-
type: 'tool-call',
|
|
304
|
-
id: 'call-1',
|
|
305
|
-
name: 'signal_aware_tool',
|
|
306
|
-
input: { input: 'test' }
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
// Verify signal was passed to tool.execute
|
|
310
|
-
expect(executeSpy).toHaveBeenCalledWith(
|
|
311
|
-
expect.objectContaining({
|
|
312
|
-
signal: expect.any(AbortSignal)
|
|
313
|
-
})
|
|
314
|
-
)
|
|
315
|
-
})
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
// ============================================================================
|
|
319
|
-
// Layer 2: Agent Execution Ceiling (between-iteration check)
|
|
320
|
-
// ============================================================================
|
|
321
|
-
|
|
322
|
-
describe('Agent Execution Ceiling', () => {
|
|
323
|
-
it('throws AgentTimeoutError when signal is aborted with timeout reason', async () => {
|
|
324
|
-
const controller = new AbortController()
|
|
325
|
-
controller.abort('timeout') // Pre-abort with timeout reason
|
|
326
|
-
|
|
327
|
-
const definition = {
|
|
328
|
-
config: {
|
|
329
|
-
type: 'agent' as const,
|
|
330
|
-
resourceId: 'test-agent',
|
|
331
|
-
name: 'Test Agent',
|
|
332
|
-
description: 'Test',
|
|
333
|
-
environment: 'dev' as const,
|
|
334
|
-
systemPrompt: 'Test',
|
|
335
|
-
constraints: { timeout: 5000 }
|
|
336
|
-
},
|
|
337
|
-
contract: {
|
|
338
|
-
inputSchema: z.object({ query: z.string() }),
|
|
339
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
340
|
-
},
|
|
341
|
-
tools: [],
|
|
342
|
-
modelConfig: {
|
|
343
|
-
model: 'mock' as const,
|
|
344
|
-
provider: 'mock',
|
|
345
|
-
apiKey: 'test-key'
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
350
|
-
const context = createMockExecutionContext({ signal: controller.signal })
|
|
351
|
-
|
|
352
|
-
await expect(agent.execute({ query: 'test' }, context)).rejects.toThrow(AgentTimeoutError)
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
it('throws AgentTimeoutError with timeout and iteration context', async () => {
|
|
356
|
-
const controller = new AbortController()
|
|
357
|
-
controller.abort('timeout')
|
|
358
|
-
|
|
359
|
-
const definition = {
|
|
360
|
-
config: {
|
|
361
|
-
type: 'agent' as const,
|
|
362
|
-
resourceId: 'test-agent',
|
|
363
|
-
name: 'Test Agent',
|
|
364
|
-
description: 'Test',
|
|
365
|
-
environment: 'dev' as const,
|
|
366
|
-
systemPrompt: 'Test',
|
|
367
|
-
constraints: { timeout: 30000 }
|
|
368
|
-
},
|
|
369
|
-
contract: {
|
|
370
|
-
inputSchema: z.object({ query: z.string() }),
|
|
371
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
372
|
-
},
|
|
373
|
-
tools: [],
|
|
374
|
-
modelConfig: {
|
|
375
|
-
model: 'mock' as const,
|
|
376
|
-
provider: 'mock',
|
|
377
|
-
apiKey: 'test-key'
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
382
|
-
const context = createMockExecutionContext({ signal: controller.signal })
|
|
383
|
-
|
|
384
|
-
try {
|
|
385
|
-
await agent.execute({ query: 'test' }, context)
|
|
386
|
-
expect.unreachable('Should have thrown')
|
|
387
|
-
} catch (error) {
|
|
388
|
-
expect(error).toBeInstanceOf(AgentTimeoutError)
|
|
389
|
-
const timeoutError = error as AgentTimeoutError
|
|
390
|
-
expect(timeoutError.message).toContain('30000ms')
|
|
391
|
-
expect(timeoutError.context).toEqual({ timeout: 30000, iteration: 1 })
|
|
392
|
-
}
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
it('throws AgentIterationError for non-timeout abort (user cancellation)', async () => {
|
|
396
|
-
const controller = new AbortController()
|
|
397
|
-
controller.abort('user_cancelled') // Non-timeout reason
|
|
398
|
-
|
|
399
|
-
const definition = {
|
|
400
|
-
config: {
|
|
401
|
-
type: 'agent' as const,
|
|
402
|
-
resourceId: 'test-agent',
|
|
403
|
-
name: 'Test Agent',
|
|
404
|
-
description: 'Test',
|
|
405
|
-
environment: 'dev' as const,
|
|
406
|
-
systemPrompt: 'Test'
|
|
407
|
-
},
|
|
408
|
-
contract: {
|
|
409
|
-
inputSchema: z.object({ query: z.string() }),
|
|
410
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
411
|
-
},
|
|
412
|
-
tools: [],
|
|
413
|
-
modelConfig: {
|
|
414
|
-
model: 'mock' as const,
|
|
415
|
-
provider: 'mock',
|
|
416
|
-
apiKey: 'test-key'
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
421
|
-
const context = createMockExecutionContext({ signal: controller.signal })
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
await agent.execute({ query: 'test' }, context)
|
|
425
|
-
expect.unreachable('Should have thrown')
|
|
426
|
-
} catch (error) {
|
|
427
|
-
expect(error).not.toBeInstanceOf(AgentTimeoutError)
|
|
428
|
-
expect((error as Error).message).toContain('cancelled')
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
it('executes normally when no signal is set', async () => {
|
|
433
|
-
const definition = {
|
|
434
|
-
config: {
|
|
435
|
-
type: 'agent' as const,
|
|
436
|
-
resourceId: 'test-agent',
|
|
437
|
-
name: 'Test Agent',
|
|
438
|
-
description: 'Test',
|
|
439
|
-
environment: 'dev' as const,
|
|
440
|
-
systemPrompt: 'Test'
|
|
441
|
-
},
|
|
442
|
-
contract: {
|
|
443
|
-
inputSchema: z.object({ query: z.string() }),
|
|
444
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
445
|
-
},
|
|
446
|
-
tools: [],
|
|
447
|
-
modelConfig: {
|
|
448
|
-
model: 'mock' as const,
|
|
449
|
-
provider: 'mock',
|
|
450
|
-
apiKey: 'test-key'
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
455
|
-
const context = createMockExecutionContext()
|
|
456
|
-
|
|
457
|
-
const output = await agent.execute({ query: 'test' }, context)
|
|
458
|
-
expect(output).toBeDefined()
|
|
459
|
-
expect(output.response).toBe('Mock response')
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
it('throws AgentStalledError when signal is aborted with stalled reason', async () => {
|
|
463
|
-
const controller = new AbortController()
|
|
464
|
-
controller.abort('stalled')
|
|
465
|
-
|
|
466
|
-
const definition = {
|
|
467
|
-
config: {
|
|
468
|
-
type: 'agent' as const,
|
|
469
|
-
resourceId: 'test-agent',
|
|
470
|
-
name: 'Test Agent',
|
|
471
|
-
description: 'Test',
|
|
472
|
-
environment: 'dev' as const,
|
|
473
|
-
systemPrompt: 'Test'
|
|
474
|
-
},
|
|
475
|
-
contract: {
|
|
476
|
-
inputSchema: z.object({ query: z.string() }),
|
|
477
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
478
|
-
},
|
|
479
|
-
tools: [],
|
|
480
|
-
modelConfig: {
|
|
481
|
-
model: 'mock' as const,
|
|
482
|
-
provider: 'mock',
|
|
483
|
-
apiKey: 'test-key'
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
488
|
-
const context = createMockExecutionContext({ signal: controller.signal })
|
|
489
|
-
|
|
490
|
-
try {
|
|
491
|
-
await agent.execute({ query: 'test' }, context)
|
|
492
|
-
expect.unreachable('Should have thrown')
|
|
493
|
-
} catch (error) {
|
|
494
|
-
expect(error).toBeInstanceOf(AgentStalledError)
|
|
495
|
-
const stalledError = error as AgentStalledError
|
|
496
|
-
expect(stalledError.message).toContain('stalled')
|
|
497
|
-
expect(stalledError.context).toEqual({ iteration: 1 })
|
|
498
|
-
}
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
it('calls onHeartbeat per iteration when provided', async () => {
|
|
502
|
-
const heartbeatSpy = vi.fn(async () => {})
|
|
503
|
-
|
|
504
|
-
const definition = {
|
|
505
|
-
config: {
|
|
506
|
-
type: 'agent' as const,
|
|
507
|
-
resourceId: 'test-agent',
|
|
508
|
-
name: 'Test Agent',
|
|
509
|
-
description: 'Test',
|
|
510
|
-
environment: 'dev' as const,
|
|
511
|
-
systemPrompt: 'Test'
|
|
512
|
-
},
|
|
513
|
-
contract: {
|
|
514
|
-
inputSchema: z.object({ query: z.string() }),
|
|
515
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
516
|
-
},
|
|
517
|
-
tools: [],
|
|
518
|
-
modelConfig: {
|
|
519
|
-
model: 'mock' as const,
|
|
520
|
-
provider: 'mock',
|
|
521
|
-
apiKey: 'test-key'
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
526
|
-
const context = createMockExecutionContext({ onHeartbeat: heartbeatSpy })
|
|
527
|
-
|
|
528
|
-
await agent.execute({ query: 'test' }, context)
|
|
529
|
-
expect(heartbeatSpy).toHaveBeenCalled()
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
it('continues execution when onHeartbeat throws', async () => {
|
|
533
|
-
const heartbeatSpy = vi.fn(async () => {
|
|
534
|
-
throw new Error('heartbeat write failed')
|
|
535
|
-
})
|
|
536
|
-
|
|
537
|
-
const definition = {
|
|
538
|
-
config: {
|
|
539
|
-
type: 'agent' as const,
|
|
540
|
-
resourceId: 'test-agent',
|
|
541
|
-
name: 'Test Agent',
|
|
542
|
-
description: 'Test',
|
|
543
|
-
environment: 'dev' as const,
|
|
544
|
-
systemPrompt: 'Test'
|
|
545
|
-
},
|
|
546
|
-
contract: {
|
|
547
|
-
inputSchema: z.object({ query: z.string() }),
|
|
548
|
-
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
549
|
-
},
|
|
550
|
-
tools: [],
|
|
551
|
-
modelConfig: {
|
|
552
|
-
model: 'mock' as const,
|
|
553
|
-
provider: 'mock',
|
|
554
|
-
apiKey: 'test-key'
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const agent = new Agent(definition, createMockAdapterFactory())
|
|
559
|
-
const context = createMockExecutionContext({ onHeartbeat: heartbeatSpy })
|
|
560
|
-
|
|
561
|
-
const output = await agent.execute({ query: 'test' }, context)
|
|
562
|
-
expect(output).toBeDefined()
|
|
563
|
-
expect(heartbeatSpy).toHaveBeenCalled()
|
|
564
|
-
})
|
|
565
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Timeout Strategy Integration Tests
|
|
3
|
+
* Verifies layered timeout architecture (H-1, M-6, L-3)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { composeSignal } from '../llm/adapters/server/compose-signal'
|
|
9
|
+
import { executeToolCall } from '../agent/actions/executor'
|
|
10
|
+
import { Agent } from '../agent/core/agent'
|
|
11
|
+
import { AgentTimeoutError, AgentStalledError } from '../agent/errors'
|
|
12
|
+
import type { Tool } from '../tools/types'
|
|
13
|
+
import type { IterationContext, LLMAdapterFactory } from '../agent/core/types'
|
|
14
|
+
import type { ExecutionContext } from '../base/types'
|
|
15
|
+
import { createMockExecutionContext, createMockTool } from '../test-utils/mocks'
|
|
16
|
+
import { MockAdapter } from '../llm/adapters/mock-adapter'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a mock LLMAdapterFactory that returns a MockAdapter instance.
|
|
20
|
+
* Used to satisfy the Agent constructor's second parameter.
|
|
21
|
+
*/
|
|
22
|
+
function createMockAdapterFactory(): LLMAdapterFactory {
|
|
23
|
+
const adapter = new MockAdapter()
|
|
24
|
+
return () => adapter
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Test Helpers
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
function createMockIterationContext(overrides?: {
|
|
32
|
+
tools?: Map<string, Tool>
|
|
33
|
+
executionContext?: Partial<ExecutionContext>
|
|
34
|
+
}): IterationContext {
|
|
35
|
+
const memoryHistory: unknown[] = []
|
|
36
|
+
const mockMemoryManager = {
|
|
37
|
+
addToHistory: vi.fn((entry: unknown) => memoryHistory.push(entry)),
|
|
38
|
+
getHistory: vi.fn(() => memoryHistory),
|
|
39
|
+
getHistoryLength: vi.fn(() => memoryHistory.length),
|
|
40
|
+
estimateTokens: vi.fn(() => 0),
|
|
41
|
+
clear: vi.fn(),
|
|
42
|
+
toContext: vi.fn(() => '')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const executionContext: ExecutionContext = {
|
|
46
|
+
executionId: 'test-exec-123',
|
|
47
|
+
organizationId: 'test-org-123',
|
|
48
|
+
organizationName: 'test-org',
|
|
49
|
+
userId: 'test-user-123',
|
|
50
|
+
resourceId: 'test-resource-123',
|
|
51
|
+
executionDepth: 0,
|
|
52
|
+
store: new Map(),
|
|
53
|
+
logger: {
|
|
54
|
+
info: vi.fn(),
|
|
55
|
+
error: vi.fn(),
|
|
56
|
+
warn: vi.fn(),
|
|
57
|
+
debug: vi.fn()
|
|
58
|
+
},
|
|
59
|
+
...overrides?.executionContext
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
executionContext,
|
|
64
|
+
toolRegistry: overrides?.tools ?? new Map(),
|
|
65
|
+
config: {
|
|
66
|
+
type: 'agent' as const,
|
|
67
|
+
resourceId: 'test-agent',
|
|
68
|
+
name: 'Test Agent',
|
|
69
|
+
description: 'Test',
|
|
70
|
+
environment: 'dev',
|
|
71
|
+
systemPrompt: 'Test'
|
|
72
|
+
},
|
|
73
|
+
contract: {
|
|
74
|
+
inputSchema: z.object({}),
|
|
75
|
+
outputSchema: z.object({})
|
|
76
|
+
},
|
|
77
|
+
logger: {
|
|
78
|
+
action: vi.fn(),
|
|
79
|
+
reasoning: vi.fn(),
|
|
80
|
+
lifecycle: vi.fn(),
|
|
81
|
+
toolCall: vi.fn(),
|
|
82
|
+
info: vi.fn(),
|
|
83
|
+
error: vi.fn(),
|
|
84
|
+
warn: vi.fn(),
|
|
85
|
+
debug: vi.fn()
|
|
86
|
+
},
|
|
87
|
+
modelConfig: {
|
|
88
|
+
provider: 'mock' as const,
|
|
89
|
+
model: 'mock',
|
|
90
|
+
apiKey: 'test'
|
|
91
|
+
},
|
|
92
|
+
memoryManager: mockMemoryManager,
|
|
93
|
+
iteration: 1
|
|
94
|
+
} as IterationContext
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Create a tool that hangs forever (never resolves) */
|
|
98
|
+
function createHangingTool(name: string, timeout?: number): Tool {
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
description: `Hanging tool: ${name}`,
|
|
102
|
+
inputSchema: z.object({ input: z.string() }),
|
|
103
|
+
outputSchema: z.object({ output: z.string() }),
|
|
104
|
+
execute: () => new Promise(() => {}), // Never resolves
|
|
105
|
+
timeout
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Create a tool that takes a specified duration to complete */
|
|
110
|
+
function createSlowTool(name: string, durationMs: number, timeout?: number): Tool {
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
description: `Slow tool: ${name}`,
|
|
114
|
+
inputSchema: z.object({ input: z.string() }),
|
|
115
|
+
outputSchema: z.object({ output: z.string() }),
|
|
116
|
+
execute: async () => {
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, durationMs))
|
|
118
|
+
return { output: 'completed' }
|
|
119
|
+
},
|
|
120
|
+
timeout
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Layer 0: composeSignal() utility
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
describe('composeSignal', () => {
|
|
129
|
+
it('returns signal that fires on timeout', async () => {
|
|
130
|
+
const signal = composeSignal(50) // 50ms timeout
|
|
131
|
+
|
|
132
|
+
expect(signal.aborted).toBe(false)
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
134
|
+
expect(signal.aborted).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('returns signal that fires on execution signal abort', () => {
|
|
138
|
+
const controller = new AbortController()
|
|
139
|
+
const signal = composeSignal(60_000, controller.signal) // Long timeout
|
|
140
|
+
|
|
141
|
+
expect(signal.aborted).toBe(false)
|
|
142
|
+
controller.abort('timeout')
|
|
143
|
+
expect(signal.aborted).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('fires whichever comes first (timeout wins)', async () => {
|
|
147
|
+
const controller = new AbortController()
|
|
148
|
+
const signal = composeSignal(50, controller.signal) // 50ms timeout
|
|
149
|
+
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
151
|
+
expect(signal.aborted).toBe(true)
|
|
152
|
+
// Controller was never aborted, but the composed signal is
|
|
153
|
+
expect(controller.signal.aborted).toBe(false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('fires whichever comes first (execution signal wins)', () => {
|
|
157
|
+
const controller = new AbortController()
|
|
158
|
+
const signal = composeSignal(60_000, controller.signal) // Long timeout
|
|
159
|
+
|
|
160
|
+
controller.abort('timeout')
|
|
161
|
+
expect(signal.aborted).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('works without execution signal', async () => {
|
|
165
|
+
const signal = composeSignal(50)
|
|
166
|
+
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
168
|
+
expect(signal.aborted).toBe(true)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Layer 1: Tool Executor Timeout
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
describe('Tool Executor Timeout', () => {
|
|
177
|
+
it('times out a hanging tool with custom timeout', async () => {
|
|
178
|
+
const tool = createHangingTool('slow_api', 100) // 100ms timeout
|
|
179
|
+
const toolRegistry = new Map<string, Tool>([['slow_api', tool]])
|
|
180
|
+
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
181
|
+
|
|
182
|
+
await executeToolCall(iterationContext, {
|
|
183
|
+
type: 'tool-call',
|
|
184
|
+
id: 'call-1',
|
|
185
|
+
name: 'slow_api',
|
|
186
|
+
input: { input: 'test' }
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Verify error was added to memory (not thrown - executor catches and stores as 'error' type)
|
|
190
|
+
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
type: 'error',
|
|
193
|
+
content: expect.stringContaining('timed out')
|
|
194
|
+
})
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('tool timeout error message includes tool name', async () => {
|
|
199
|
+
const tool = createHangingTool('notion_search', 100)
|
|
200
|
+
const toolRegistry = new Map<string, Tool>([['notion_search', tool]])
|
|
201
|
+
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
202
|
+
|
|
203
|
+
await executeToolCall(iterationContext, {
|
|
204
|
+
type: 'tool-call',
|
|
205
|
+
id: 'call-1',
|
|
206
|
+
name: 'notion_search',
|
|
207
|
+
input: { input: 'test' }
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Check the logger recorded the timeout with tool name
|
|
211
|
+
expect(iterationContext.logger.toolCall).toHaveBeenCalledWith(
|
|
212
|
+
'notion_search',
|
|
213
|
+
expect.any(Number),
|
|
214
|
+
expect.any(Number),
|
|
215
|
+
expect.any(Number),
|
|
216
|
+
expect.any(Number),
|
|
217
|
+
false,
|
|
218
|
+
expect.stringContaining('notion_search'),
|
|
219
|
+
expect.anything(),
|
|
220
|
+
undefined
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('succeeds when tool completes before timeout', async () => {
|
|
225
|
+
const tool = createSlowTool('fast_api', 10, 500) // 10ms execution, 500ms timeout
|
|
226
|
+
const toolRegistry = new Map<string, Tool>([['fast_api', tool]])
|
|
227
|
+
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
228
|
+
|
|
229
|
+
await executeToolCall(iterationContext, {
|
|
230
|
+
type: 'tool-call',
|
|
231
|
+
id: 'call-1',
|
|
232
|
+
name: 'fast_api',
|
|
233
|
+
input: { input: 'test' }
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Should have a success result, not an error
|
|
237
|
+
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
238
|
+
expect.objectContaining({
|
|
239
|
+
type: 'tool-result',
|
|
240
|
+
content: expect.stringContaining('completed')
|
|
241
|
+
})
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('uses default 30min timeout when tool has no custom timeout', () => {
|
|
246
|
+
const tool = createMockTool('default_tool')
|
|
247
|
+
expect(tool.timeout).toBeUndefined()
|
|
248
|
+
// The executor uses `tool.timeout ?? DEFAULT_TOOL_TIMEOUT` (1_800_000 / 30min)
|
|
249
|
+
// We verify the field is optional and defaults are handled in the executor, not the tool definition
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('execution signal aborts tool immediately', async () => {
|
|
253
|
+
// Tool that hangs forever with a long per-tool timeout
|
|
254
|
+
const tool: Tool = {
|
|
255
|
+
name: 'slow_tool',
|
|
256
|
+
description: 'Hangs forever',
|
|
257
|
+
inputSchema: z.object({ input: z.string() }),
|
|
258
|
+
outputSchema: z.object({ output: z.string() }),
|
|
259
|
+
execute: () => new Promise(() => {}),
|
|
260
|
+
timeout: 60_000 // 60s per-tool timeout
|
|
261
|
+
}
|
|
262
|
+
const toolRegistry = new Map<string, Tool>([['slow_tool', tool]])
|
|
263
|
+
|
|
264
|
+
// Create an AbortController that fires quickly (simulating execution ceiling)
|
|
265
|
+
const controller = new AbortController()
|
|
266
|
+
setTimeout(() => controller.abort('timeout'), 50) // 50ms ceiling
|
|
267
|
+
|
|
268
|
+
const iterationContext = createMockIterationContext({
|
|
269
|
+
tools: toolRegistry,
|
|
270
|
+
executionContext: { signal: controller.signal }
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
await executeToolCall(iterationContext, {
|
|
274
|
+
type: 'tool-call',
|
|
275
|
+
id: 'call-1',
|
|
276
|
+
name: 'slow_tool',
|
|
277
|
+
input: { input: 'test' }
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Tool should have been aborted by the execution signal, not the per-tool timeout
|
|
281
|
+
expect(iterationContext.memoryManager.addToHistory).toHaveBeenCalledWith(
|
|
282
|
+
expect.objectContaining({
|
|
283
|
+
type: 'error',
|
|
284
|
+
content: expect.stringContaining('timed out')
|
|
285
|
+
})
|
|
286
|
+
)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('passes signal to tool execute options', async () => {
|
|
290
|
+
const executeSpy = vi.fn(async () => ({ output: 'done' }))
|
|
291
|
+
const tool: Tool = {
|
|
292
|
+
name: 'signal_aware_tool',
|
|
293
|
+
description: 'Checks signal',
|
|
294
|
+
inputSchema: z.object({ input: z.string() }),
|
|
295
|
+
outputSchema: z.object({ output: z.string() }),
|
|
296
|
+
execute: executeSpy,
|
|
297
|
+
timeout: 5_000
|
|
298
|
+
}
|
|
299
|
+
const toolRegistry = new Map<string, Tool>([['signal_aware_tool', tool]])
|
|
300
|
+
const iterationContext = createMockIterationContext({ tools: toolRegistry })
|
|
301
|
+
|
|
302
|
+
await executeToolCall(iterationContext, {
|
|
303
|
+
type: 'tool-call',
|
|
304
|
+
id: 'call-1',
|
|
305
|
+
name: 'signal_aware_tool',
|
|
306
|
+
input: { input: 'test' }
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
// Verify signal was passed to tool.execute
|
|
310
|
+
expect(executeSpy).toHaveBeenCalledWith(
|
|
311
|
+
expect.objectContaining({
|
|
312
|
+
signal: expect.any(AbortSignal)
|
|
313
|
+
})
|
|
314
|
+
)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// ============================================================================
|
|
319
|
+
// Layer 2: Agent Execution Ceiling (between-iteration check)
|
|
320
|
+
// ============================================================================
|
|
321
|
+
|
|
322
|
+
describe('Agent Execution Ceiling', () => {
|
|
323
|
+
it('throws AgentTimeoutError when signal is aborted with timeout reason', async () => {
|
|
324
|
+
const controller = new AbortController()
|
|
325
|
+
controller.abort('timeout') // Pre-abort with timeout reason
|
|
326
|
+
|
|
327
|
+
const definition = {
|
|
328
|
+
config: {
|
|
329
|
+
type: 'agent' as const,
|
|
330
|
+
resourceId: 'test-agent',
|
|
331
|
+
name: 'Test Agent',
|
|
332
|
+
description: 'Test',
|
|
333
|
+
environment: 'dev' as const,
|
|
334
|
+
systemPrompt: 'Test',
|
|
335
|
+
constraints: { timeout: 5000 }
|
|
336
|
+
},
|
|
337
|
+
contract: {
|
|
338
|
+
inputSchema: z.object({ query: z.string() }),
|
|
339
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
340
|
+
},
|
|
341
|
+
tools: [],
|
|
342
|
+
modelConfig: {
|
|
343
|
+
model: 'mock' as const,
|
|
344
|
+
provider: 'mock',
|
|
345
|
+
apiKey: 'test-key'
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
350
|
+
const context = createMockExecutionContext({ signal: controller.signal })
|
|
351
|
+
|
|
352
|
+
await expect(agent.execute({ query: 'test' }, context)).rejects.toThrow(AgentTimeoutError)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('throws AgentTimeoutError with timeout and iteration context', async () => {
|
|
356
|
+
const controller = new AbortController()
|
|
357
|
+
controller.abort('timeout')
|
|
358
|
+
|
|
359
|
+
const definition = {
|
|
360
|
+
config: {
|
|
361
|
+
type: 'agent' as const,
|
|
362
|
+
resourceId: 'test-agent',
|
|
363
|
+
name: 'Test Agent',
|
|
364
|
+
description: 'Test',
|
|
365
|
+
environment: 'dev' as const,
|
|
366
|
+
systemPrompt: 'Test',
|
|
367
|
+
constraints: { timeout: 30000 }
|
|
368
|
+
},
|
|
369
|
+
contract: {
|
|
370
|
+
inputSchema: z.object({ query: z.string() }),
|
|
371
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
372
|
+
},
|
|
373
|
+
tools: [],
|
|
374
|
+
modelConfig: {
|
|
375
|
+
model: 'mock' as const,
|
|
376
|
+
provider: 'mock',
|
|
377
|
+
apiKey: 'test-key'
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
382
|
+
const context = createMockExecutionContext({ signal: controller.signal })
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
await agent.execute({ query: 'test' }, context)
|
|
386
|
+
expect.unreachable('Should have thrown')
|
|
387
|
+
} catch (error) {
|
|
388
|
+
expect(error).toBeInstanceOf(AgentTimeoutError)
|
|
389
|
+
const timeoutError = error as AgentTimeoutError
|
|
390
|
+
expect(timeoutError.message).toContain('30000ms')
|
|
391
|
+
expect(timeoutError.context).toEqual({ timeout: 30000, iteration: 1 })
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('throws AgentIterationError for non-timeout abort (user cancellation)', async () => {
|
|
396
|
+
const controller = new AbortController()
|
|
397
|
+
controller.abort('user_cancelled') // Non-timeout reason
|
|
398
|
+
|
|
399
|
+
const definition = {
|
|
400
|
+
config: {
|
|
401
|
+
type: 'agent' as const,
|
|
402
|
+
resourceId: 'test-agent',
|
|
403
|
+
name: 'Test Agent',
|
|
404
|
+
description: 'Test',
|
|
405
|
+
environment: 'dev' as const,
|
|
406
|
+
systemPrompt: 'Test'
|
|
407
|
+
},
|
|
408
|
+
contract: {
|
|
409
|
+
inputSchema: z.object({ query: z.string() }),
|
|
410
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
411
|
+
},
|
|
412
|
+
tools: [],
|
|
413
|
+
modelConfig: {
|
|
414
|
+
model: 'mock' as const,
|
|
415
|
+
provider: 'mock',
|
|
416
|
+
apiKey: 'test-key'
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
421
|
+
const context = createMockExecutionContext({ signal: controller.signal })
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
await agent.execute({ query: 'test' }, context)
|
|
425
|
+
expect.unreachable('Should have thrown')
|
|
426
|
+
} catch (error) {
|
|
427
|
+
expect(error).not.toBeInstanceOf(AgentTimeoutError)
|
|
428
|
+
expect((error as Error).message).toContain('cancelled')
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('executes normally when no signal is set', async () => {
|
|
433
|
+
const definition = {
|
|
434
|
+
config: {
|
|
435
|
+
type: 'agent' as const,
|
|
436
|
+
resourceId: 'test-agent',
|
|
437
|
+
name: 'Test Agent',
|
|
438
|
+
description: 'Test',
|
|
439
|
+
environment: 'dev' as const,
|
|
440
|
+
systemPrompt: 'Test'
|
|
441
|
+
},
|
|
442
|
+
contract: {
|
|
443
|
+
inputSchema: z.object({ query: z.string() }),
|
|
444
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
445
|
+
},
|
|
446
|
+
tools: [],
|
|
447
|
+
modelConfig: {
|
|
448
|
+
model: 'mock' as const,
|
|
449
|
+
provider: 'mock',
|
|
450
|
+
apiKey: 'test-key'
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
455
|
+
const context = createMockExecutionContext()
|
|
456
|
+
|
|
457
|
+
const output = await agent.execute({ query: 'test' }, context)
|
|
458
|
+
expect(output).toBeDefined()
|
|
459
|
+
expect(output.response).toBe('Mock response')
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('throws AgentStalledError when signal is aborted with stalled reason', async () => {
|
|
463
|
+
const controller = new AbortController()
|
|
464
|
+
controller.abort('stalled')
|
|
465
|
+
|
|
466
|
+
const definition = {
|
|
467
|
+
config: {
|
|
468
|
+
type: 'agent' as const,
|
|
469
|
+
resourceId: 'test-agent',
|
|
470
|
+
name: 'Test Agent',
|
|
471
|
+
description: 'Test',
|
|
472
|
+
environment: 'dev' as const,
|
|
473
|
+
systemPrompt: 'Test'
|
|
474
|
+
},
|
|
475
|
+
contract: {
|
|
476
|
+
inputSchema: z.object({ query: z.string() }),
|
|
477
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
478
|
+
},
|
|
479
|
+
tools: [],
|
|
480
|
+
modelConfig: {
|
|
481
|
+
model: 'mock' as const,
|
|
482
|
+
provider: 'mock',
|
|
483
|
+
apiKey: 'test-key'
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
488
|
+
const context = createMockExecutionContext({ signal: controller.signal })
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
await agent.execute({ query: 'test' }, context)
|
|
492
|
+
expect.unreachable('Should have thrown')
|
|
493
|
+
} catch (error) {
|
|
494
|
+
expect(error).toBeInstanceOf(AgentStalledError)
|
|
495
|
+
const stalledError = error as AgentStalledError
|
|
496
|
+
expect(stalledError.message).toContain('stalled')
|
|
497
|
+
expect(stalledError.context).toEqual({ iteration: 1 })
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('calls onHeartbeat per iteration when provided', async () => {
|
|
502
|
+
const heartbeatSpy = vi.fn(async () => {})
|
|
503
|
+
|
|
504
|
+
const definition = {
|
|
505
|
+
config: {
|
|
506
|
+
type: 'agent' as const,
|
|
507
|
+
resourceId: 'test-agent',
|
|
508
|
+
name: 'Test Agent',
|
|
509
|
+
description: 'Test',
|
|
510
|
+
environment: 'dev' as const,
|
|
511
|
+
systemPrompt: 'Test'
|
|
512
|
+
},
|
|
513
|
+
contract: {
|
|
514
|
+
inputSchema: z.object({ query: z.string() }),
|
|
515
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
516
|
+
},
|
|
517
|
+
tools: [],
|
|
518
|
+
modelConfig: {
|
|
519
|
+
model: 'mock' as const,
|
|
520
|
+
provider: 'mock',
|
|
521
|
+
apiKey: 'test-key'
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
526
|
+
const context = createMockExecutionContext({ onHeartbeat: heartbeatSpy })
|
|
527
|
+
|
|
528
|
+
await agent.execute({ query: 'test' }, context)
|
|
529
|
+
expect(heartbeatSpy).toHaveBeenCalled()
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('continues execution when onHeartbeat throws', async () => {
|
|
533
|
+
const heartbeatSpy = vi.fn(async () => {
|
|
534
|
+
throw new Error('heartbeat write failed')
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
const definition = {
|
|
538
|
+
config: {
|
|
539
|
+
type: 'agent' as const,
|
|
540
|
+
resourceId: 'test-agent',
|
|
541
|
+
name: 'Test Agent',
|
|
542
|
+
description: 'Test',
|
|
543
|
+
environment: 'dev' as const,
|
|
544
|
+
systemPrompt: 'Test'
|
|
545
|
+
},
|
|
546
|
+
contract: {
|
|
547
|
+
inputSchema: z.object({ query: z.string() }),
|
|
548
|
+
outputSchema: z.object({ response: z.string(), processedAt: z.string() })
|
|
549
|
+
},
|
|
550
|
+
tools: [],
|
|
551
|
+
modelConfig: {
|
|
552
|
+
model: 'mock' as const,
|
|
553
|
+
provider: 'mock',
|
|
554
|
+
apiKey: 'test-key'
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const agent = new Agent(definition, createMockAdapterFactory())
|
|
559
|
+
const context = createMockExecutionContext({ onHeartbeat: heartbeatSpy })
|
|
560
|
+
|
|
561
|
+
const output = await agent.execute({ query: 'test' }, context)
|
|
562
|
+
expect(output).toBeDefined()
|
|
563
|
+
expect(heartbeatSpy).toHaveBeenCalled()
|
|
564
|
+
})
|
|
565
|
+
})
|