@superblocksteam/vite-plugin-file-sync 2.0.156-next.3 → 2.0.156

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 (177) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -9
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  3. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +6 -34
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  5. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts +1 -2
  6. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
  7. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +9 -52
  8. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
  9. package/dist/ai-service/agent/tool-permission-delivery.d.ts +16 -25
  10. package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
  11. package/dist/ai-service/agent/tool-permission-delivery.js +38 -384
  12. package/dist/ai-service/agent/tool-permission-delivery.js.map +1 -1
  13. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts +35 -9
  14. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
  15. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +98 -71
  16. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
  17. package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
  18. package/dist/ai-service/agent/tools/apis/test-api.js +2 -3
  19. package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
  20. package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts +1 -1
  21. package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts.map +1 -1
  22. package/dist/ai-service/agent/tools/app-environment/update-app-environment.js +1 -13
  23. package/dist/ai-service/agent/tools/app-environment/update-app-environment.js.map +1 -1
  24. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  25. package/dist/ai-service/agent/tools/build-capture-screenshot.js +3 -8
  26. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  27. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -1
  28. package/dist/ai-service/agent/tools/build-navigate-preview.js +2 -4
  29. package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -1
  30. package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
  31. package/dist/ai-service/agent/tools/build-reload-file.js +2 -4
  32. package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
  33. package/dist/ai-service/agent/tools/get-logs.d.ts +2 -2
  34. package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts.map +1 -1
  35. package/dist/ai-service/agent/tools/integrations/delete-integration.js +3 -6
  36. package/dist/ai-service/agent/tools/integrations/delete-integration.js.map +1 -1
  37. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +13 -13
  38. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts +6 -18
  39. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
  40. package/dist/ai-service/agent/tools/integrations/integration-setup.js +15 -41
  41. package/dist/ai-service/agent/tools/integrations/integration-setup.js.map +1 -1
  42. package/dist/ai-service/agent/tools/integrations/internal.d.ts.map +1 -1
  43. package/dist/ai-service/agent/tools/integrations/internal.js +2 -4
  44. package/dist/ai-service/agent/tools/integrations/internal.js.map +1 -1
  45. package/dist/ai-service/agent/tools/report-security-findings.d.ts +23 -23
  46. package/dist/ai-service/agent/tools.d.ts +2 -4
  47. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  48. package/dist/ai-service/agent/tools.js +14 -21
  49. package/dist/ai-service/agent/tools.js.map +1 -1
  50. package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
  51. package/dist/ai-service/index.d.ts +25 -26
  52. package/dist/ai-service/index.d.ts.map +1 -1
  53. package/dist/ai-service/index.js +121 -340
  54. package/dist/ai-service/index.js.map +1 -1
  55. package/dist/ai-service/integrations/store.d.ts +1 -2
  56. package/dist/ai-service/integrations/store.d.ts.map +1 -1
  57. package/dist/ai-service/integrations/store.js +2 -3
  58. package/dist/ai-service/integrations/store.js.map +1 -1
  59. package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
  60. package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
  61. package/dist/ai-service/llm/client.d.ts +3 -9
  62. package/dist/ai-service/llm/client.d.ts.map +1 -1
  63. package/dist/ai-service/llm/client.js +3 -10
  64. package/dist/ai-service/llm/client.js.map +1 -1
  65. package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
  66. package/dist/ai-service/llm/context-v2/context-metrics.js +0 -3
  67. package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
  68. package/dist/ai-service/llm/stream/observers/llmobs.d.ts +6 -6
  69. package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
  70. package/dist/ai-service/llm/stream/observers/llmobs.js +9 -10
  71. package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
  72. package/dist/ai-service/llm/stream/orchestrator.d.ts +0 -4
  73. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  74. package/dist/ai-service/llm/stream/orchestrator.js +41 -19
  75. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  76. package/dist/ai-service/llm/stream/retry-engine.d.ts +1 -1
  77. package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
  78. package/dist/ai-service/llm/stream/retry-engine.js +1 -26
  79. package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
  80. package/dist/ai-service/policy-agent-metrics.d.ts +0 -10
  81. package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -1
  82. package/dist/ai-service/policy-agent-metrics.js +0 -33
  83. package/dist/ai-service/policy-agent-metrics.js.map +1 -1
  84. package/dist/ai-service/state-machine/clark-fsm.d.ts +1 -29
  85. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  86. package/dist/ai-service/state-machine/clark-fsm.js +1 -3
  87. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  88. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  89. package/dist/ai-service/state-machine/handlers/agent-planning.js +6 -7
  90. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  91. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  92. package/dist/ai-service/state-machine/handlers/llm-generating.js +5 -18
  93. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  94. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -1
  95. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +6 -8
  96. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -1
  97. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts +14 -0
  98. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts.map +1 -1
  99. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js +36 -0
  100. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js.map +1 -1
  101. package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts +0 -2
  102. package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts.map +1 -1
  103. package/dist/ai-service/state-machine/helpers/pre-session-metrics.js +0 -3
  104. package/dist/ai-service/state-machine/helpers/pre-session-metrics.js.map +1 -1
  105. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +0 -1
  106. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
  107. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +27 -60
  108. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
  109. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +1 -12
  110. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
  111. package/dist/ai-service/state-machine/helpers/stable-peer.js +9 -42
  112. package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
  113. package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
  114. package/dist/ai-service/state-machine/mocks.js +0 -6
  115. package/dist/ai-service/state-machine/mocks.js.map +1 -1
  116. package/dist/ai-service/util/rpc-timeout.d.ts +5 -12
  117. package/dist/ai-service/util/rpc-timeout.d.ts.map +1 -1
  118. package/dist/ai-service/util/rpc-timeout.js +5 -12
  119. package/dist/ai-service/util/rpc-timeout.js.map +1 -1
  120. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  121. package/dist/file-sync-vite-plugin.js +21 -8
  122. package/dist/file-sync-vite-plugin.js.map +1 -1
  123. package/dist/lock-service/activity-tracker.d.ts +5 -0
  124. package/dist/lock-service/activity-tracker.d.ts.map +1 -1
  125. package/dist/lock-service/activity-tracker.js +13 -0
  126. package/dist/lock-service/activity-tracker.js.map +1 -1
  127. package/dist/lock-service/index.d.ts +27 -308
  128. package/dist/lock-service/index.d.ts.map +1 -1
  129. package/dist/lock-service/index.js +156 -680
  130. package/dist/lock-service/index.js.map +1 -1
  131. package/dist/policy-gate-callback-mapper.d.ts +2 -3
  132. package/dist/policy-gate-callback-mapper.d.ts.map +1 -1
  133. package/dist/policy-gate-callback-mapper.js.map +1 -1
  134. package/dist/policy-gate-runner.d.ts +2 -4
  135. package/dist/policy-gate-runner.d.ts.map +1 -1
  136. package/dist/policy-gate-runner.js +8 -31
  137. package/dist/policy-gate-runner.js.map +1 -1
  138. package/dist/socket-manager.d.ts +6 -47
  139. package/dist/socket-manager.d.ts.map +1 -1
  140. package/dist/socket-manager.js +66 -283
  141. package/dist/socket-manager.js.map +1 -1
  142. package/dist/sync-service/index.d.ts.map +1 -1
  143. package/dist/sync-service/index.js +3 -3
  144. package/dist/sync-service/index.js.map +1 -1
  145. package/package.json +8 -8
  146. package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts +0 -3
  147. package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts.map +0 -1
  148. package/dist/ai-service/agent/tool-permission-delivery-metrics.js +0 -23
  149. package/dist/ai-service/agent/tool-permission-delivery-metrics.js.map +0 -1
  150. package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts +0 -15
  151. package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts.map +0 -1
  152. package/dist/ai-service/llm/stream/observers/step-token-usage.js +0 -88
  153. package/dist/ai-service/llm/stream/observers/step-token-usage.js.map +0 -1
  154. package/dist/ai-service/llm/stream/terminal-reason.d.ts +0 -6
  155. package/dist/ai-service/llm/stream/terminal-reason.d.ts.map +0 -1
  156. package/dist/ai-service/llm/stream/terminal-reason.js +0 -73
  157. package/dist/ai-service/llm/stream/terminal-reason.js.map +0 -1
  158. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts +0 -28
  159. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts.map +0 -1
  160. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js +0 -254
  161. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js.map +0 -1
  162. package/dist/ai-service/util/call-peer.d.ts +0 -11
  163. package/dist/ai-service/util/call-peer.d.ts.map +0 -1
  164. package/dist/ai-service/util/call-peer.js +0 -47
  165. package/dist/ai-service/util/call-peer.js.map +0 -1
  166. package/dist/ai-service/util/peer-liveness.d.ts +0 -23
  167. package/dist/ai-service/util/peer-liveness.d.ts.map +0 -1
  168. package/dist/ai-service/util/peer-liveness.js +0 -42
  169. package/dist/ai-service/util/peer-liveness.js.map +0 -1
  170. package/dist/scan-scope-merge.d.ts +0 -16
  171. package/dist/scan-scope-merge.d.ts.map +0 -1
  172. package/dist/scan-scope-merge.js +0 -90
  173. package/dist/scan-scope-merge.js.map +0 -1
  174. package/dist/test-setup.d.ts +0 -2
  175. package/dist/test-setup.d.ts.map +0 -1
  176. package/dist/test-setup.js +0 -21
  177. package/dist/test-setup.js.map +0 -1
@@ -2,9 +2,9 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { context as otelContext, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
5
- import { hasToolCall, stepCountIs, } from "ai";
5
+ import { hasToolCall, streamText as aiStreamText, } from "ai";
6
6
  import { z } from "zod";
7
- import { AiGenerationState, AiMode, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
7
+ import { AiGenerationState, AiMode, MAX_OUTPUT_TOKENS, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
8
8
  import { isToolPermissionClarification } from "@superblocksteam/library-shared/types/tool-permission-response";
9
9
  import { addTracingToMethods, AiSystemTriggerType, classifyAttachmentDelivery, FactAccessType, FactCreatedSource, FactEntityScope, TracedEventEmitter, getAiQuotaPaywallReasonFromMessage, } from "@superblocksteam/shared";
10
10
  import { npmRegistryEmitter } from "@superblocksteam/telemetry";
@@ -14,8 +14,6 @@ import { POLICY_GATE_RUN_IN_PROGRESS_ERROR, policyGateRunRegistry, } from "../po
14
14
  import { getErrorMeta, getLogger, setDefaultLogger } from "../util/logger.js";
15
15
  import { composeSecurityAgentPrompt, } from "./agent/prompts/build-security-scan-prompt.js";
16
16
  import { getToolCallArguments, getToolErrorCode, getToolOutput, } from "./agent/tool-message-utils.js";
17
- import { recordToolPermissionDeliveryOutcome } from "./agent/tool-permission-delivery-metrics.js";
18
- import { createExpiredPermissionPromptDeliveryError, flushPendingToolPermissionPrompt, inFlightToolPermissionReplay, } from "./agent/tool-permission-delivery.js";
19
17
  import { buildSecurityScanTools, isBackgroundTaskFrameworkEnabled, } from "./agent/tools.js";
20
18
  import { hasPendingEscalations, flushPendingEscalations, } from "./agent/tools/apis/api-validation-orchestrator.js";
21
19
  import { safeSampleJson } from "./agent/tools/apis/sample-json.js";
@@ -24,7 +22,7 @@ import { applyDevDatabaseMigrationsTaskMetadata, createDevDatabaseTaskTypes, wir
24
22
  import { debugCache } from "./agent/tools/debug-cache.js";
25
23
  import { securityScanResultSchema, } from "./agent/tools/report-security-findings.js";
26
24
  import { SessionEntityPermissionStore, } from "./agent/tools2/entity-permissions.js";
27
- import { ToolRegistry, } from "./agent/tools2/registry.js";
25
+ import { ToolRegistry } from "./agent/tools2/registry.js";
28
26
  import { FileSystemInterface } from "./app-interface/file-system-interface.js";
29
27
  import { createDefaultVirtualFileSystem, createPathValidator, } from "./app-interface/filesystem/index.js";
30
28
  import { NpmPackageLookup } from "./app-interface/npm-package-lookup.js";
@@ -52,7 +50,6 @@ import { LiveEditEventForwarder, readForwarderConfigFromEnv, } from "./live-edit
52
50
  import { createChaosFetch } from "./llm/chaos-fetch.js";
53
51
  import { createClarkStreamTelemetrySink } from "./llm/clark-stream-telemetry-sink.js";
54
52
  import { LLMClient } from "./llm/client.js";
55
- import { resolveContextOptionsV2 } from "./llm/context-v2/config.js";
56
53
  import { selectContextManagementStrategy, } from "./llm/context-v2/context-management.js";
57
54
  import { recordCheckpointRestore } from "./llm/context-v2/context-metrics.js";
58
55
  import { ContextManagerV2 } from "./llm/context-v2/manager.js";
@@ -60,7 +57,8 @@ import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
60
57
  import { adoptLegacyPerUserContextDirs } from "./llm/context-v2/storage/legacy-context-adoption.js";
61
58
  import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
62
59
  import { createLLMProvider, setSpanError } from "./llm/provider.js";
63
- import { getJwtTraceTags, traceLLM } from "./llmobs/helpers.js";
60
+ import { traceLLM } from "./llmobs/helpers.js";
61
+ import { getJwtTraceTags } from "./llmobs/helpers.js";
64
62
  import llmobs from "./llmobs/index.js";
65
63
  import { PlaywrightMcpServerManager } from "./mcp/playwright-server.js";
66
64
  import { evaluateMigrationAutoDispatchReadiness } from "./migration-auto-dispatch-readiness.js";
@@ -89,10 +87,10 @@ import { doRuntimeReviewing } from "./state-machine/handlers/runtime-reviewing.j
89
87
  import { classificationHelper } from "./state-machine/helpers/classification.js";
90
88
  import { getContextId } from "./state-machine/helpers/context-id.js";
91
89
  import { bindHeadlessTurnLifecycle } from "./state-machine/helpers/headless-turn-lifecycle.js";
92
- import { clearPendingToolPermissionRequest, sendUserGenerationStateChannel, sendUserMessageChannel, } from "./state-machine/helpers/peer.js";
93
- import { finalizePolicyGateUsageMetrics, policyGateFailureFromStream, recordPolicyGateAttempt, } from "./state-machine/helpers/policy-gate-usage-metrics.js";
90
+ import { sendUserGenerationStateChannel } from "./state-machine/helpers/peer.js";
91
+ import { sendUserMessageChannel, clearPendingToolPermissionRequest, } from "./state-machine/helpers/peer.js";
92
+ import { collectPolicyGateLlmUsage, finalizePolicyGateUsageMetrics, policyGateFailureFromStream, recordPolicyGateAttempt, } from "./state-machine/helpers/policy-gate-usage-metrics.js";
94
93
  import { finalizePromptBillingMetrics } from "./state-machine/helpers/prompt-billing-metrics.js";
95
- import { setPromptDegradationMetricIdentity } from "./state-machine/helpers/prompt-degradation-metrics.js";
96
94
  import { paywallStopAdvice } from "./state-machine/helpers/prompt-failure.js";
97
95
  import { CLARK_USAGE_ORIGIN, promptFailureFromStreamAbort, promptFailurePaywall, promptFailurePlatform, promptFailureUserCanceled, PROMPT_ATTEMPT_ENTRYPOINT, recordBaseCommitResolutionFailed, recordPromptAttempt, recordPromptEntryRejection, recordPromptOutcome, recordAwaitingUserHandoffDismissed, resolvePromptMetricLabels, resolvePromptOutcomeDedupeKey, resolvePromptRequestDedupeKey, setPromptRequestMetricIdentity, } from "./state-machine/helpers/prompt-request-metrics.js";
98
96
  import { catchBackgroundPeerDisconnect, StablePeer, } from "./state-machine/helpers/stable-peer.js";
@@ -102,7 +100,6 @@ import { TemplateRenderer } from "./template-renderer.js";
102
100
  import { describeMigrationInferenceJwt, isUsableMigrationInferenceJwt, } from "./util/inference-jwt.js";
103
101
  import { buildModelConfigAttributes, processLLMConfig, } from "./util/llm-config-utils.js";
104
102
  import { parseJwt } from "./util/parse-jwt.js";
105
- import { PERMISSION_PROMPT_DELIVERY_TIMEOUT_MS } from "./util/rpc-timeout.js";
106
103
  import { getToolCallSignature } from "./util/tool-signature.js";
107
104
  export { AiServiceFeatureFlags } from "./features.js";
108
105
  export { getManagedNativeDbGateDenialReason, hasEditLifecycleCapability, isManagedNativeDbAvailableForClark, } from "./managed-native-db-availability.js";
@@ -145,9 +142,6 @@ const MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH = 500;
145
142
  const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH = 3;
146
143
  const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS = 20;
147
144
  const POLICY_GATE_MAX_TOTAL_TIME_MS = 15 * 60 * 1000;
148
- // Ceiling above the historical 30-step cap; 100 is an existing policy-agent
149
- // histogram bucket. Tighten from production p99 if scans complete far below this.
150
- const POLICY_GATE_MAX_STEPS = 100;
151
145
  // AbortError near the budget is treated as timeout (SDK may abort slightly early).
152
146
  const POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS = 5_000;
153
147
  const POLICY_GATE_STREAM_ERROR_REDACTED_VALUE = "[redacted]";
@@ -360,9 +354,6 @@ const POLICY_GATE_USAGE_METADATA = {
360
354
  origin: CLARK_USAGE_ORIGIN.policy_gate,
361
355
  surface: "policy_gate",
362
356
  };
363
- const ERROR_JSON_OUTPUT_TYPE = "error-json";
364
- const TOOL_MESSAGE_ROLE = "tool";
365
- const TOOL_RESULT_CONTENT_TYPE = "tool-result";
366
357
  export class AiService extends TracedEventEmitter {
367
358
  config;
368
359
  templateRenderer;
@@ -505,6 +496,10 @@ export class AiService extends TracedEventEmitter {
505
496
  this._runtimeSyncService = syncService;
506
497
  this._runtimeGitOperationLock = gitOperationLock;
507
498
  }
499
+ /**
500
+ * Sets the sync context provider used by the remote context restore fallback.
501
+ * Called by the socket manager once the sync service is initialized.
502
+ */
508
503
  setSyncContextProvider(provider) {
509
504
  this._syncContextProvider = provider;
510
505
  }
@@ -569,6 +564,7 @@ export class AiService extends TracedEventEmitter {
569
564
  this.tryAutoDispatchMigrationTurnIfReady();
570
565
  return true;
571
566
  }
567
+ // ===== Recording API =====
572
568
  /**
573
569
  * Check if recording is currently enabled.
574
570
  */
@@ -625,10 +621,6 @@ export class AiService extends TracedEventEmitter {
625
621
  applicationId: config.applicationId,
626
622
  organizationId: config.organizationId,
627
623
  });
628
- setPromptDegradationMetricIdentity({
629
- applicationId: config.applicationId,
630
- organizationId: config.organizationId,
631
- });
632
624
  this.startLiveEditEventForwarding();
633
625
  // Store the wrapped logger so getLogger() can return it
634
626
  // The wrapped logger forwards logs to the browser console when Clark logs are enabled
@@ -912,12 +904,6 @@ export class AiService extends TracedEventEmitter {
912
904
  organizationId: config.organizationId,
913
905
  applicationId: config.applicationId,
914
906
  getJwt: () => this.requireProviderInferenceAuthToken(),
915
- stepTokenUsageLabels: () => ({
916
- coreModel: this.clark?.context.llmConfig?.coreModel,
917
- mode: this.clark?.context.currentMode === AiMode.PLAN ? "plan" : "build",
918
- surface: this.clark?.context.usageMetadata?.surface ??
919
- APP_CLARK_GENERATION_METADATA.surface,
920
- }),
921
907
  onUsageRecorded: (usage) => {
922
908
  // Accumulate workflow-level metrics
923
909
  this.clark.updateContext((context) => {
@@ -951,7 +937,9 @@ export class AiService extends TracedEventEmitter {
951
937
  createTagProvider: this.createClarkTagProviderFactory(),
952
938
  sessionId: this.sessionId,
953
939
  });
940
+ // Initialize judge service
954
941
  this.mcpServerManager = new PlaywrightMcpServerManager(this.clark.logger);
942
+ // Initialize recording manager for capturing AI interactions
955
943
  this.recordingManager = new RecordingManager({
956
944
  recordingsDir: path.join(config.appRootDirPath, ".superblocks", "recordings"),
957
945
  appRootDirPath: config.appRootDirPath,
@@ -1319,7 +1307,6 @@ export class AiService extends TracedEventEmitter {
1319
1307
  llmClient: this.llmClient,
1320
1308
  toolRegistry: this.toolRegistry,
1321
1309
  entityPermissionStore: this.entityPermissionStore,
1322
- onToolPermissionParked: (messageId) => this.scheduleToolPermissionReplayWindow(messageId),
1323
1310
  mcpServerManager: this.mcpServerManager,
1324
1311
  viteServer: this.viteServer,
1325
1312
  recordingManager: this.recordingManager,
@@ -2272,6 +2259,7 @@ export class AiService extends TracedEventEmitter {
2272
2259
  useMockGeneration: request.mockGeneration ?? false,
2273
2260
  planContext: mergePlanContexts(context.planContext, request.planContext),
2274
2261
  browserContext: request.browserContext,
2262
+ // Initialize workflow metrics tracking
2275
2263
  workflowMetrics: {
2276
2264
  toolCallCount: 0,
2277
2265
  stepCount: 0,
@@ -2621,6 +2609,7 @@ export class AiService extends TracedEventEmitter {
2621
2609
  const timeoutController = new AbortController();
2622
2610
  const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
2623
2611
  try {
2612
+ // Build the appropriate content part based on attachment type
2624
2613
  const contentPart = this.attachmentToContentPart(attachment);
2625
2614
  // Route labeling through the shared LLMClient (APPS-5061) as a stateless
2626
2615
  // one-shot, mirroring the APPS-1213 (#20263) migrations. No safety config:
@@ -2674,6 +2663,7 @@ export class AiService extends TracedEventEmitter {
2674
2663
  };
2675
2664
  }
2676
2665
  else if (attachment.type === "pdf" || attachment.type === "archive") {
2666
+ // Parse data URL to extract base64 data
2677
2667
  const match = attachment.data.match(/^data:([^;]+);base64,(.+)$/);
2678
2668
  const data = match?.[2] ?? attachment.data;
2679
2669
  const defaultMediaType = attachment.type === "pdf"
@@ -2773,10 +2763,6 @@ export class AiService extends TracedEventEmitter {
2773
2763
  await this.clark.recordSystemMessage(params, "Environment event", "User closed the integration setup form. The integration setup might not have been completed successfully.");
2774
2764
  }
2775
2765
  async handleUserCanceled() {
2776
- // Drop parked/in-flight permission prompts first so interval replay and
2777
- // grace expiry cannot keep pushing the dialog or inject a continuation
2778
- // after the user has already stopped the turn.
2779
- clearPendingToolPermissionRequest(this.clark, "user-canceled");
2780
2766
  const awaitingUser = this.isAwaitingUser();
2781
2767
  if (!this.isBusy() && !awaitingUser) {
2782
2768
  return;
@@ -3035,9 +3021,11 @@ export class AiService extends TracedEventEmitter {
3035
3021
  logger.warn(`[ai-service] Skip request for testRunId=${testRunId} but current is ${this.clark.context.currentTestRunId}`);
3036
3022
  return;
3037
3023
  }
3024
+ // Update context with skip request
3038
3025
  this.clark.updateContext({
3039
3026
  skipRequestedForTestCase: testCaseId,
3040
3027
  });
3028
+ // Update the test case status to skipped
3041
3029
  const testCaseStatuses = this.clark.context.testCaseStatuses ??
3042
3030
  new Map();
3043
3031
  testCaseStatuses.set(testCaseId, "skipped");
@@ -3058,127 +3046,24 @@ export class AiService extends TracedEventEmitter {
3058
3046
  }
3059
3047
  logger.info(`[ai-service] Test case ${testCaseId} marked as skipped, flag set for subagent`);
3060
3048
  }
3061
- scheduleToolPermissionReplayWindow(messageId) {
3062
- const pending = this.clark.context.pendingToolPermissionRequest;
3063
- if (pending?.messageId !== messageId ||
3064
- pending.delivery.status !== "parked") {
3065
- return;
3066
- }
3067
- this.scheduleToolPermissionExpiry(messageId);
3068
- this.scheduleToolPermissionReplay(messageId);
3069
- }
3070
- scheduleToolPermissionExpiry(messageId) {
3071
- const pending = this.clark.context.pendingToolPermissionRequest;
3072
- if (pending?.messageId !== messageId ||
3073
- pending.delivery.status !== "parked") {
3074
- return;
3075
- }
3076
- const expiresInMs = Math.max(0, pending.delivery.expiresAtMs - Date.now());
3077
- setTimeout(() => {
3078
- void (async () => {
3079
- await inFlightToolPermissionReplay(this.clark);
3080
- const current = this.clark.context.pendingToolPermissionRequest;
3081
- if (current?.messageId !== messageId ||
3082
- current.delivery.status !== "parked") {
3083
- return;
3084
- }
3085
- if (Date.now() < current.delivery.expiresAtMs) {
3086
- this.scheduleToolPermissionExpiry(messageId);
3087
- return;
3088
- }
3089
- if (!(await this.waitUntilToolPermissionContinuationAllowed(messageId))) {
3090
- return;
3091
- }
3092
- const expired = this.clark.context.pendingToolPermissionRequest;
3093
- if (expired?.messageId !== messageId ||
3094
- expired.delivery.status !== "parked") {
3095
- return;
3096
- }
3097
- const error = createExpiredPermissionPromptDeliveryError(expired.toolName, expired.delivery);
3098
- recordToolPermissionDeliveryOutcome("expired");
3099
- await this.handleToolPermissionOutcome({
3100
- kind: "delivery_error",
3101
- messageId,
3102
- error,
3103
- });
3104
- })().catch((cause) => {
3105
- this.getLogger().error("[ai-service] Failed to continue after permission delivery expired", getErrorMeta(cause));
3106
- });
3107
- }, expiresInMs);
3108
- }
3109
3049
  /**
3110
- * `userSentPrompt` is only valid from Idle or AwaitingUser. A parked
3111
- * permission can expire while a sibling tool is still generating; wait
3112
- * until the continuation can actually run, or abort if the request is gone.
3050
+ * Handles the user's response to a tool permission request.
3051
+ *
3052
+ * If approved, immediately executes the tool with the original input,
3053
+ * injects the tool call and result into the conversation context,
3054
+ * and restarts the LLM with the updated context.
3055
+ *
3056
+ * If denied, injects a tool error into the context and restarts the LLM.
3113
3057
  */
3114
- async waitUntilToolPermissionContinuationAllowed(messageId) {
3115
- const TOOL_PERMISSION_CONTINUATION_POLL_MS = 250;
3116
- for (;;) {
3117
- const current = this.clark.context.pendingToolPermissionRequest;
3118
- if (current?.messageId !== messageId ||
3119
- current.delivery.status !== "parked") {
3120
- return false;
3121
- }
3122
- switch (this.clark.state) {
3123
- case ClarkStateNames.Idle:
3124
- case ClarkStateNames.AwaitingUser:
3125
- return true;
3126
- case ClarkStateNames.Dead:
3127
- return false;
3128
- case ClarkStateNames.AgentPlanning:
3129
- case ClarkStateNames.LLMGenerating:
3130
- case ClarkStateNames.PostProcessing:
3131
- case ClarkStateNames.RuntimeReviewing: {
3132
- await new Promise((resolve) => {
3133
- setTimeout(resolve, TOOL_PERMISSION_CONTINUATION_POLL_MS);
3134
- });
3135
- break;
3136
- }
3137
- default: {
3138
- const exhaustive = this.clark.state;
3139
- return exhaustive;
3140
- }
3141
- }
3142
- }
3143
- }
3144
- scheduleToolPermissionReplay(messageId) {
3145
- setTimeout(() => {
3146
- const pending = this.clark.context.pendingToolPermissionRequest;
3147
- if (pending?.messageId !== messageId ||
3148
- pending.delivery.status !== "parked" ||
3149
- Date.now() >= pending.delivery.expiresAtMs) {
3150
- return;
3151
- }
3152
- const replay = this.stablePeer.isConnected
3153
- ? flushPendingToolPermissionPrompt(this.clark, { source: "retry" })
3154
- : Promise.resolve();
3155
- void replay.finally(() => {
3156
- const current = this.clark.context.pendingToolPermissionRequest;
3157
- if (current?.messageId === messageId &&
3158
- current.delivery.status === "parked") {
3159
- this.scheduleToolPermissionReplay(messageId);
3160
- }
3161
- });
3162
- }, PERMISSION_PROMPT_DELIVERY_TIMEOUT_MS);
3163
- }
3164
3058
  async handleAiRespondToToolPermission(request) {
3165
- return this.handleToolPermissionOutcome({
3166
- kind: "response",
3167
- ...request,
3168
- });
3169
- }
3170
- async handleToolPermissionOutcome(request) {
3171
3059
  const logger = this.getLogger();
3172
- const approved = request.kind === "response" && request.approved;
3173
- logger.info(`[ai-service] handleAiRespondToToolPermission: messageId=${request.messageId}, approved=${approved}`, { outcomeKind: request.kind });
3060
+ logger.info(`[ai-service] handleAiRespondToToolPermission: messageId=${request.messageId}, approved=${request.approved}`);
3174
3061
  // a reconnect can cause the client to re-send this response. If
3175
3062
  // we already applied a response with this requestId, treat the repeat as a
3176
3063
  // no-op so the tool is not executed (or denied) twice for the same user
3177
3064
  // decision. Only a peek here — the id is recorded once the response is
3178
3065
  // accepted below.
3179
- if (request.kind === "response" &&
3180
- request.requestId &&
3181
- this.requestDeduplicator.has(request.requestId)) {
3066
+ if (request.requestId && this.requestDeduplicator.has(request.requestId)) {
3182
3067
  logger.info("[ai-service] Ignoring duplicate tool-permission re-send", {
3183
3068
  requestId: request.requestId,
3184
3069
  });
@@ -3198,17 +3083,14 @@ export class AiService extends TracedEventEmitter {
3198
3083
  // and honor the result so a re-send dropped here can't be applied twice. A
3199
3084
  // duplicate that loses the atomic `markIfNew` bails before the tool runs.
3200
3085
  // (APPS-4663)
3201
- if (request.kind === "response" &&
3202
- request.requestId &&
3086
+ if (request.requestId &&
3203
3087
  !this.requestDeduplicator.markIfNew(request.requestId)) {
3204
3088
  logger.warn("[ai-service] Ignoring duplicate tool-permission re-send (lost dedup race)", { requestId: request.requestId });
3205
3089
  return;
3206
3090
  }
3207
3091
  logger.info(`[ai-service] Clearing pending request`);
3208
3092
  // Clear the pending request and notify UI
3209
- clearPendingToolPermissionRequest(this.clark, request.kind === "delivery_error"
3210
- ? "tool-permission-delivery-expired"
3211
- : "tool-permission-response");
3093
+ clearPendingToolPermissionRequest(this.clark, "tool-permission-response");
3212
3094
  const toolCallId = pendingRequest.messageId;
3213
3095
  const toolName = pendingRequest.toolName;
3214
3096
  const rawInput = pendingRequest.rawInput;
@@ -3216,6 +3098,7 @@ export class AiService extends TracedEventEmitter {
3216
3098
  const llmConfig = this.clark.context.llmConfig;
3217
3099
  logger.info(`[ai-service] Getting context toolCallId=${toolCallId})`);
3218
3100
  const sendUserMessage = sendUserMessageChannel(this.clark, this.chatSessionStore);
3101
+ // Build the assistant message with the NEW tool call ID
3219
3102
  const assistantMessage = {
3220
3103
  role: "assistant",
3221
3104
  content: [
@@ -3227,8 +3110,9 @@ export class AiService extends TracedEventEmitter {
3227
3110
  },
3228
3111
  ],
3229
3112
  };
3113
+ // Build the tool result message based on approval/execution
3230
3114
  let toolResultMessage;
3231
- if (approved) {
3115
+ if (request.approved) {
3232
3116
  // Approve in entity permission store
3233
3117
  this.entityPermissionStore.approveToolCall(toolName, rawInput);
3234
3118
  const signature = getToolCallSignature(toolName, rawInput);
@@ -3244,11 +3128,9 @@ export class AiService extends TracedEventEmitter {
3244
3128
  signature,
3245
3129
  });
3246
3130
  // Store always-allow permissions
3247
- if (request.kind === "response") {
3248
- request.alwaysAllowPermissions?.forEach((permission) => {
3249
- void this.entityPermissionStore.approve(permission.entityType, permission.entityId, permission.permissionType);
3250
- });
3251
- }
3131
+ request.alwaysAllowPermissions?.forEach((permission) => {
3132
+ void this.entityPermissionStore.approve(permission.entityType, permission.entityId, permission.permissionType);
3133
+ });
3252
3134
  // Execute the tool with proper access control context from Clark
3253
3135
  const accessControlContext = this.clark.getAccessControl(this.config.features);
3254
3136
  logger.info(`[ai-service] Executing approved tool: ${toolName}`);
@@ -3340,23 +3222,6 @@ export class AiService extends TracedEventEmitter {
3340
3222
  });
3341
3223
  }
3342
3224
  }
3343
- else if (request.kind === "delivery_error") {
3344
- toolResultMessage = {
3345
- role: TOOL_MESSAGE_ROLE,
3346
- content: [
3347
- {
3348
- type: TOOL_RESULT_CONTENT_TYPE,
3349
- toolCallId,
3350
- toolName,
3351
- output: {
3352
- type: ERROR_JSON_OUTPUT_TYPE,
3353
- value: { error: request.error.message },
3354
- },
3355
- isError: true,
3356
- },
3357
- ],
3358
- };
3359
- }
3360
3225
  else {
3361
3226
  const denyReason = request.denyMessage || "User denied permission for this tool call";
3362
3227
  if (toolName === "testApi") {
@@ -3431,11 +3296,10 @@ export class AiService extends TracedEventEmitter {
3431
3296
  if (!this.clark.context.llmConfig) {
3432
3297
  throw new Error("Cannot continue after tool permission: llmConfig is missing from context");
3433
3298
  }
3434
- const isClarification = isToolPermissionClarification(approved, request.kind === "response" ? request.denyMessage : undefined);
3299
+ const isClarification = isToolPermissionClarification(request.approved, request.denyMessage);
3435
3300
  logger.info("[ai-service] tool_permission_response continuation", {
3436
- approved,
3301
+ approved: request.approved,
3437
3302
  clarification: isClarification,
3438
- outcomeKind: request.kind,
3439
3303
  });
3440
3304
  trace
3441
3305
  .getActiveSpan()
@@ -3456,9 +3320,9 @@ export class AiService extends TracedEventEmitter {
3456
3320
  outcomeDedupeKey: promptOutcomeDedupeKey,
3457
3321
  });
3458
3322
  const continueRequest = {
3459
- prompt: approved || request.kind === "delivery_error"
3323
+ prompt: request.approved
3460
3324
  ? ``
3461
- : isClarification && request.denyMessage
3325
+ : isClarification
3462
3326
  ? request.denyMessage.trim()
3463
3327
  : this.buildDenialContinuationPrompt(toolName, rawInput),
3464
3328
  promptContext: {
@@ -3473,13 +3337,8 @@ export class AiService extends TracedEventEmitter {
3473
3337
  responseMetadata: {
3474
3338
  type: "tool_permission_response",
3475
3339
  responseToMessageId: request.messageId,
3476
- approved,
3477
- outcome: request.kind === "delivery_error"
3478
- ? "delivery_expired"
3479
- : "user_response",
3480
- alwaysAllowPermissions: request.kind === "response"
3481
- ? request.alwaysAllowPermissions
3482
- : undefined,
3340
+ approved: request.approved,
3341
+ alwaysAllowPermissions: request.alwaysAllowPermissions,
3483
3342
  ...(isClarification ? { clarification: true } : {}),
3484
3343
  },
3485
3344
  };
@@ -3557,9 +3416,9 @@ export class AiService extends TracedEventEmitter {
3557
3416
  * Returned so the caller can be told which turn it now owns; a turn started
3558
3417
  * without one is reachable by this socket alone.
3559
3418
  */
3560
- beginHeadlessTurn(peer, peerId, turnId, liveness) {
3419
+ beginHeadlessTurn(peer, peerId, turnId) {
3561
3420
  this.getLogger().info(`[ai-service] headless turn started by gateway peer ${peerId}`);
3562
- this.stablePeer.beginTurn({ liveness, peer, peerId, turnId });
3421
+ this.stablePeer.beginTurn({ peer, peerId, turnId });
3563
3422
  return turnId;
3564
3423
  }
3565
3424
  /**
@@ -3571,9 +3430,8 @@ export class AiService extends TracedEventEmitter {
3571
3430
  * the caller already owns the turn, or is talking about a turn that is not
3572
3431
  * this one — are silent. Only an actual handover is worth a line.
3573
3432
  */
3574
- claimHeadlessTurn(peer, peerId, turnId, liveness) {
3575
- if (this.stablePeer.claimTurn({ liveness, peer, peerId, turnId }) !==
3576
- "claimed") {
3433
+ claimHeadlessTurn(peer, peerId, turnId) {
3434
+ if (this.stablePeer.claimTurn({ peer, peerId, turnId }) !== "claimed") {
3577
3435
  return;
3578
3436
  }
3579
3437
  this.getLogger().info(`[ai-service] headless turn moved to reattached gateway peer ${peerId}`);
@@ -3594,19 +3452,14 @@ export class AiService extends TracedEventEmitter {
3594
3452
  * it, so a socket that a reattach already replaced cannot strip routing from
3595
3453
  * its successor.
3596
3454
  */
3597
- endHeadlessTurn(peerId, turnIdOrLiveness) {
3598
- const turnId = typeof turnIdOrLiveness === "string" ? turnIdOrLiveness : undefined;
3599
- const liveness = typeof turnIdOrLiveness === "string" ? undefined : turnIdOrLiveness;
3600
- if (this.stablePeer.turnPeerId !== peerId ||
3601
- (turnId !== undefined && this.stablePeer.turnId !== turnId) ||
3602
- (liveness &&
3603
- this.stablePeer.turnLivenessGeneration !== liveness.generation)) {
3455
+ endHeadlessTurn(peerId) {
3456
+ if (this.stablePeer.turnPeerId !== peerId) {
3604
3457
  return;
3605
3458
  }
3606
3459
  this.getLogger().info(`[ai-service] headless turn peer ${peerId} detached; falling back to the editor binding`);
3607
3460
  this.stablePeer.releaseTurnPeer(peerId);
3608
3461
  }
3609
- handleUserConnected(peer, peerId, liveness) {
3462
+ handleUserConnected(peer, peerId) {
3610
3463
  const existingPeerId = this.stablePeer.peerId;
3611
3464
  this.getLogger().info(`[ai-service] handleUserConnected: peerId=${peerId}`);
3612
3465
  if (existingPeerId) {
@@ -3623,7 +3476,7 @@ export class AiService extends TracedEventEmitter {
3623
3476
  // `clark.context.peer` is a permanent StablePeer proxy; we swap its
3624
3477
  // inner (and peerId) here. Any queued calls accumulated while
3625
3478
  // disconnected flush in order as part of setInner.
3626
- this.stablePeer.setInner({ liveness, peer, peerId });
3479
+ this.stablePeer.setInner({ peer, peerId });
3627
3480
  this.getLogger().info(`[ai-service] Peer set: ${peerId}`);
3628
3481
  // A replacement (or freshly reconnecting) peer must re-prove its
3629
3482
  // identity via `notifyAuthenticatedRpc` before live task pushes
@@ -3651,18 +3504,25 @@ export class AiService extends TracedEventEmitter {
3651
3504
  messageId: request.messageId,
3652
3505
  toolName: request.toolName,
3653
3506
  });
3654
- // replaceInFlight also covers an in-progress first delivery
3655
- // (awaiting_ack). The replacement takes park/ack ownership so a
3656
- // miss while the tab is still loading still starts the replay
3657
- // grace window instead of wedging the session.
3658
- const needsReplayWindow = request.delivery.status === "acked";
3659
- void flushPendingToolPermissionPrompt(this.clark, {
3660
- replaceInFlight: true,
3661
- source: "reconnect",
3662
- }).then((outcome) => {
3663
- if (needsReplayWindow && outcome === "parked") {
3664
- this.scheduleToolPermissionReplayWindow(request.messageId);
3665
- }
3507
+ // Note: The promise from the original RPC call will have timed out,
3508
+ // but resending allows the user to see and respond to the request.
3509
+ // The tool execution will fail, but the agent can retry it naturally.
3510
+ void peer.call
3511
+ .aiRequestToolPermission({
3512
+ messageId: request.messageId,
3513
+ toolName: request.toolName,
3514
+ actionName: request.actionName ?? undefined,
3515
+ formattedInput: request.formattedInput ?? undefined,
3516
+ rawInput: request.rawInput,
3517
+ permissions: request.permissions ?? [],
3518
+ })
3519
+ .catch((error) => {
3520
+ this.getLogger().error(`[ai-service] Failed to resend tool permission request: ${error}`);
3521
+ this.getLogger().warn(`[debug] reconnect: resend pending tool permission failed`, {
3522
+ messageId: request.messageId,
3523
+ toolName: request.toolName,
3524
+ error: error instanceof Error ? error.message : String(error),
3525
+ });
3666
3526
  });
3667
3527
  }
3668
3528
  else {
@@ -3761,7 +3621,7 @@ export class AiService extends TracedEventEmitter {
3761
3621
  * will never answer — the case a headless build leaves behind when its
3762
3622
  * gateway socket closes over an open editor tab.
3763
3623
  */
3764
- handleUserDisconnected(peerId, replacement, liveness) {
3624
+ handleUserDisconnected(peerId, replacement) {
3765
3625
  const currentPeerId = this.stablePeer.peerId;
3766
3626
  this.getLogger().info(`[ai-service] handleUserDisconnected: peerId=${peerId}`);
3767
3627
  if (!currentPeerId) {
@@ -3771,19 +3631,6 @@ export class AiService extends TracedEventEmitter {
3771
3631
  this.getLogger().info(`[ai-service] Stale disconnect ignored: peer ${peerId} disconnecting but current peer is ${currentPeerId}`);
3772
3632
  return; // Don't clear the current peer on stale disconnect
3773
3633
  }
3774
- if (liveness &&
3775
- this.stablePeer.editorLivenessGeneration !== liveness.generation) {
3776
- this.getLogger().info(`[ai-service] Stale disconnect ignored for replaced connection generation`);
3777
- return;
3778
- }
3779
- if (replacement?.peerId === peerId &&
3780
- replacement.liveness &&
3781
- liveness &&
3782
- replacement.liveness.generation !== liveness.generation) {
3783
- this.getLogger().info(`[ai-service] Rebinding peer ${peerId} to surviving editor connection`);
3784
- this.handleUserConnected(replacement.peer, replacement.peerId, replacement.liveness);
3785
- return;
3786
- }
3787
3634
  this.getLogger().info(`[ai-service] peer disconnecting: ${peerId}`);
3788
3635
  // Drop any deferred state-push registration for this peer so a
3789
3636
  // disconnect that races a never-arrived auth proof doesn't leave
@@ -3803,11 +3650,11 @@ export class AiService extends TracedEventEmitter {
3803
3650
  if (contextId) {
3804
3651
  this.contextManagerV2.clearContext(contextId);
3805
3652
  }
3806
- if (replacement && (replacement.peerId !== peerId || liveness)) {
3653
+ if (replacement && replacement.peerId !== peerId) {
3807
3654
  this.getLogger().info(`[ai-service] Rebinding peer ${peerId} to surviving editor ${replacement.peerId}`);
3808
3655
  // Same door a fresh tab comes through, so the survivor gets the state
3809
3656
  // resync and re-auth a newly bound peer is owed.
3810
- this.handleUserConnected(replacement.peer, replacement.peerId, replacement.liveness);
3657
+ this.handleUserConnected(replacement.peer, replacement.peerId);
3811
3658
  return;
3812
3659
  }
3813
3660
  this.clearPeerConnection();
@@ -4091,12 +3938,12 @@ export class AiService extends TracedEventEmitter {
4091
3938
  let textDeltaChars = 0;
4092
3939
  let modelName;
4093
3940
  let promptHash;
4094
- // Hoisted so the outer catch can attribute a failed run to the same model as
4095
- // the attempt it belongs to. Usage arrives from the client's usage observer,
4096
- // which also flushes on abort, on error, and for compaction calls.
3941
+ // Hoisted so the outer catch can attribute a failed run to the same model
3942
+ // and provider as the attempt it belongs to, and can still price whatever
3943
+ // tokens the stream consumed before it threw.
4097
3944
  let coreModel;
4098
- const llmUsage = [];
4099
- let hadFallback = false;
3945
+ let providerName = "unknown";
3946
+ let streamResult;
4100
3947
  let attemptRecorded = false;
4101
3948
  const scanRoot = typeof this.appShell.appRootDirPath === "string"
4102
3949
  ? this.appShell.appRootDirPath
@@ -4111,15 +3958,13 @@ export class AiService extends TracedEventEmitter {
4111
3958
  metricsRecorded = true;
4112
3959
  recordPolicyAgentExecution({
4113
3960
  durationMs: Date.now() - startedAt,
4114
- llmUsage,
4115
3961
  outcome,
4116
3962
  stepCount,
4117
3963
  toolCallCount: totalToolCalls(),
4118
3964
  });
4119
3965
  };
4120
3966
  const buildRuntimeMetadata = (finalFailureReason) => ({
4121
- diagnosticsVersion: 3,
4122
- diffFiles: request.scanScopeJson?.changedFiles ?? [],
3967
+ diagnosticsVersion: 2,
4123
3968
  durationMs: Date.now() - startedAt,
4124
3969
  filePathsListed: Array.from(filePathsListed).slice(0, 100),
4125
3970
  filePathsRead: Array.from(filePathsRead).slice(0, 100),
@@ -4156,8 +4001,8 @@ export class AiService extends TracedEventEmitter {
4156
4001
  trigger_type: "policy_gate",
4157
4002
  }));
4158
4003
  const { model, providerConfig } = llmProvider.modelForTask("generateBroadEdit");
4159
- const { model: summarizationModel, providerConfig: summarizationProviderConfig, } = llmProvider.modelForTask("summarizeMessages");
4160
4004
  modelName = typeof model === "string" ? model : model.modelId;
4005
+ providerName = providerConfig?.provider ?? "unknown";
4161
4006
  coreModel = llmConfig?.coreModel;
4162
4007
  // Counted as soon as the model resolves, because everything below is
4163
4008
  // inside this try and so is guaranteed a terminal outcome from the catch.
@@ -4170,105 +4015,37 @@ export class AiService extends TracedEventEmitter {
4170
4015
  name: i.name,
4171
4016
  pluginType: i.plugin?.id ?? "unknown",
4172
4017
  }));
4173
- let scanScopeJson = request.scanScopeJson;
4174
- if (scanScopeJson?.unifiedDiff) {
4175
- try {
4176
- const scanResult = await scanContentForSecrets(scanScopeJson.unifiedDiff);
4177
- if (scanResult.success && scanResult.hasSecrets) {
4178
- scanScopeJson = {
4179
- ...scanScopeJson,
4180
- unifiedDiff: new SecretRedactor(scanResult.findings).redact(scanScopeJson.unifiedDiff),
4181
- };
4182
- }
4183
- else if (!scanResult.success) {
4184
- logger.warn("[secret-scan] Secret scan failed on policy gate diff; using the original diff");
4185
- }
4186
- }
4187
- catch {
4188
- logger.warn("[secret-scan] Secret scan threw on policy gate diff; using the original diff");
4189
- }
4190
- }
4191
4018
  const appContext = { integrations };
4192
- const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt, scanScopeJson);
4019
+ const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
4193
4020
  promptHash = createHash("sha256").update(systemPrompt).digest("hex");
4194
- const scanContext = {};
4195
- const { headers, interleavedThinking, maxOutputTokens, providerOptions, thinkingBudgetTokens, thinkingEnabled, } = processLLMConfig(llmConfig);
4196
- // Scan-owned client: gate JWT, own usage sink, built first so scan tools
4197
- // (webFetch generateObject) use this client instead of Clark's.
4198
- const policyGateLlmClient = new LLMClient({
4199
- appShell: this.appShell,
4200
- applicationId: request.applicationId,
4201
- contextManagerV2: this.contextManagerV2,
4202
- getJwt: () => this.requireProvidedInferenceJwt(request.jwt, "policy gate"),
4203
- onUsageRecorded: (usage) => {
4204
- if (usage.hadProviderFallback === true) {
4205
- hadFallback = true;
4206
- }
4207
- llmUsage.push(...usage.promptCostUsageEntries);
4208
- },
4209
- organizationId: this.config.organizationId,
4210
- stepTokenUsageLabels: () => ({
4211
- coreModel,
4212
- mode: "build",
4213
- surface: POLICY_GATE_USAGE_METADATA.surface,
4214
- }),
4215
- retryOptions: streamRetryOptionsFromConfig(this.config.retryOptions, this.config.features.streamTimeoutBudgets),
4216
- unifiedProviderEnabled: this.config.features.unifiedProviderEnabled ?? false,
4217
- });
4218
4021
  const scanToolsDeps = {
4219
4022
  appShell: this.appShell,
4220
4023
  features: this.config.features ?? {},
4221
4024
  integrationStore: this.integrationStore,
4222
- llmClient: policyGateLlmClient,
4025
+ llmClient: this.llmClient,
4223
4026
  llmProvider: llmProvider,
4224
- storeSummary: (toolCallId, summary) => {
4225
- scanContext.current?.storeSummary(toolCallId, summary);
4226
- },
4227
4027
  };
4228
4028
  const tools = await buildSecurityScanTools(this.clark, scanToolsDeps);
4229
- const result = await policyGateLlmClient.streamText({
4230
- abortSignal: AbortSignal.timeout(POLICY_GATE_MAX_TOTAL_TIME_MS),
4231
- context: {
4232
- contextManagement: {
4233
- summarizationModel,
4234
- summarizationProviderConfig,
4235
- type: "client",
4029
+ const result = aiStreamText({
4030
+ messages: [
4031
+ { content: systemPrompt, role: "system" },
4032
+ {
4033
+ content: "Begin your security review of this application now.",
4034
+ role: "user",
4236
4035
  },
4237
- // Product defaults: caller llmConfig cannot shrink the scan window to 0.
4238
- contextOptionsV2: resolveContextOptionsV2(),
4239
- },
4240
- enableFallback: llmConfig?.unifiedProviderEnabled ?? false,
4241
- fallbackStrategy: llmConfig?.fallbackStrategy,
4242
- headers,
4243
- logging: false,
4244
- // processLLMConfig returns the same MAX_OUTPUT_TOKENS cap Clark uses.
4245
- maxOutputTokens,
4036
+ ],
4037
+ // Same output cap Clark uses (see processLLMConfig). Without it the
4038
+ // provider default applies, which is model-dependent.
4039
+ maxOutputTokens: MAX_OUTPUT_TOKENS,
4246
4040
  model,
4247
- onContextReady: (ctx) => {
4248
- scanContext.current = ctx;
4249
- },
4250
- providerConfig,
4251
- providerOptions: {
4252
- ...providerOptions,
4253
- ...(coreModel ? { unified: { coreModel } } : {}),
4254
- },
4255
- recordObsContent: false,
4256
- system: systemPrompt,
4257
- thinking: {
4258
- budgetTokens: thinkingBudgetTokens,
4259
- enabled: thinkingEnabled,
4260
- interleaved: interleavedThinking,
4261
- },
4041
+ // The unified router resolves the model from the request, falling back
4042
+ // to Clark's core-model flag when absent.
4043
+ ...(coreModel ? { providerOptions: { unified: { coreModel } } } : {}),
4262
4044
  tools,
4263
- stopWhen: [
4264
- hasToolCall("reportReviewRun"),
4265
- stepCountIs(POLICY_GATE_MAX_STEPS),
4266
- ],
4267
- user: {
4268
- content: "Begin your security review of this application now.",
4269
- role: "user",
4270
- },
4045
+ stopWhen: hasToolCall("reportReviewRun"),
4046
+ timeout: { totalMs: POLICY_GATE_MAX_TOTAL_TIME_MS },
4271
4047
  });
4048
+ streamResult = result;
4272
4049
  // Best-effort: surface the agent's latest tool/reasoning step in the editor
4273
4050
  // so a running policy row shows live activity instead of an opaque spinner.
4274
4051
  // Only meaningful when an editor session is connected; never blocks the run.
@@ -4299,6 +4076,16 @@ export class AiService extends TracedEventEmitter {
4299
4076
  : "unknown";
4300
4077
  streamPartTypeCounts[partType] =
4301
4078
  (streamPartTypeCounts[partType] ?? 0) + 1;
4079
+ if (partType === "error") {
4080
+ const streamError = serializePolicyGateStreamError(part.error, part);
4081
+ if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
4082
+ streamErrors.push(streamError);
4083
+ }
4084
+ logger.warn("[ai-service] Policy gate stream error", {
4085
+ ...policyRunLogContext,
4086
+ streamError,
4087
+ });
4088
+ }
4302
4089
  const textDelta = part.textDelta ??
4303
4090
  part.text ??
4304
4091
  part.delta;
@@ -4411,9 +4198,7 @@ export class AiService extends TracedEventEmitter {
4411
4198
  }
4412
4199
  catch (error) {
4413
4200
  if (error instanceof Error &&
4414
- (error.name === "AbortError" ||
4415
- error.name === "AbortedError" ||
4416
- error.name === "TimeoutError")) {
4201
+ (error.name === "AbortError" || error.name === "TimeoutError")) {
4417
4202
  const elapsedMs = Date.now() - startedAt;
4418
4203
  const isBudgetTimeout = error.name === "TimeoutError" ||
4419
4204
  elapsedMs + POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS >=
@@ -4433,18 +4218,6 @@ export class AiService extends TracedEventEmitter {
4433
4218
  });
4434
4219
  failPolicyGateExecution("Policy gate agent aborted", "aborted");
4435
4220
  }
4436
- if (error instanceof Error &&
4437
- (error.name === "LLMProviderError" ||
4438
- error.name === "RetriesExhaustedError")) {
4439
- const streamError = serializePolicyGateStreamError(error);
4440
- if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
4441
- streamErrors.push(streamError);
4442
- }
4443
- logger.warn("[ai-service] Policy gate stream error", {
4444
- ...policyRunLogContext,
4445
- streamError,
4446
- });
4447
- }
4448
4221
  throw error;
4449
4222
  }
4450
4223
  if (!scanResult) {
@@ -4466,8 +4239,11 @@ export class AiService extends TracedEventEmitter {
4466
4239
  aiAgentRunId: request.aiAgentRunId,
4467
4240
  chatSessionStore: this.chatSessionStore,
4468
4241
  coreModel,
4469
- hadFallback,
4470
- llmUsage,
4242
+ llmUsage: await collectPolicyGateLlmUsage({
4243
+ modelName: modelName ?? "unknown",
4244
+ providerName,
4245
+ result,
4246
+ }),
4471
4247
  logger,
4472
4248
  outcome: "success",
4473
4249
  });
@@ -4490,8 +4266,13 @@ export class AiService extends TracedEventEmitter {
4490
4266
  chatSessionStore: this.chatSessionStore,
4491
4267
  coreModel,
4492
4268
  failure: policyGateFailureFromStream(streamErrors),
4493
- hadFallback,
4494
- llmUsage,
4269
+ llmUsage: streamResult
4270
+ ? await collectPolicyGateLlmUsage({
4271
+ modelName: modelName ?? "unknown",
4272
+ providerName,
4273
+ result: streamResult,
4274
+ })
4275
+ : [],
4495
4276
  logger,
4496
4277
  outcome: "failure",
4497
4278
  });