@superblocksteam/vite-plugin-file-sync 2.0.155 → 2.0.156-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +9 -2
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js +34 -6
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts +2 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +52 -9
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts +3 -0
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts.map +1 -0
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js +23 -0
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js.map +1 -0
- package/dist/ai-service/agent/tool-permission-delivery.d.ts +25 -16
- package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery.js +384 -38
- package/dist/ai-service/agent/tool-permission-delivery.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts +9 -35
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +71 -98
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js +3 -2
- package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.js +13 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.js.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.js +8 -3
- package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
- package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-navigate-preview.js +4 -2
- package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -1
- package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-reload-file.js +4 -2
- package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
- package/dist/ai-service/agent/tools/get-logs.d.ts +2 -2
- package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/delete-integration.js +6 -3
- package/dist/ai-service/agent/tools/integrations/delete-integration.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +13 -13
- package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts +18 -6
- package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/integration-setup.js +41 -15
- package/dist/ai-service/agent/tools/integrations/integration-setup.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/internal.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/internal.js +4 -2
- package/dist/ai-service/agent/tools/integrations/internal.js.map +1 -1
- package/dist/ai-service/agent/tools/report-security-findings.d.ts +23 -23
- package/dist/ai-service/agent/tools.d.ts +4 -2
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +21 -14
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
- package/dist/ai-service/index.d.ts +26 -25
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +340 -121
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/integrations/store.d.ts +2 -1
- package/dist/ai-service/integrations/store.d.ts.map +1 -1
- package/dist/ai-service/integrations/store.js +3 -2
- package/dist/ai-service/integrations/store.js.map +1 -1
- package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
- package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
- package/dist/ai-service/llm/client.d.ts +9 -3
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +10 -3
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js +3 -0
- package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts +6 -6
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.js +10 -9
- package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts +15 -0
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts.map +1 -0
- package/dist/ai-service/llm/stream/observers/step-token-usage.js +88 -0
- package/dist/ai-service/llm/stream/observers/step-token-usage.js.map +1 -0
- package/dist/ai-service/llm/stream/orchestrator.d.ts +4 -0
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +19 -41
- package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.d.ts +1 -1
- package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.js +26 -1
- package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
- package/dist/ai-service/llm/stream/terminal-reason.d.ts +6 -0
- package/dist/ai-service/llm/stream/terminal-reason.d.ts.map +1 -0
- package/dist/ai-service/llm/stream/terminal-reason.js +73 -0
- package/dist/ai-service/llm/stream/terminal-reason.js.map +1 -0
- package/dist/ai-service/policy-agent-metrics.d.ts +10 -0
- package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -1
- package/dist/ai-service/policy-agent-metrics.js +33 -0
- package/dist/ai-service/policy-agent-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +29 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +3 -1
- package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +7 -6
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +18 -5
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +8 -6
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts +0 -14
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js +0 -36
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts +2 -0
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.js +3 -0
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts +28 -0
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js +254 -0
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +1 -0
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +60 -27
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +12 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.js +42 -9
- package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
- package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
- package/dist/ai-service/state-machine/mocks.js +6 -0
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/util/call-peer.d.ts +11 -0
- package/dist/ai-service/util/call-peer.d.ts.map +1 -0
- package/dist/ai-service/util/call-peer.js +47 -0
- package/dist/ai-service/util/call-peer.js.map +1 -0
- package/dist/ai-service/util/peer-liveness.d.ts +23 -0
- package/dist/ai-service/util/peer-liveness.d.ts.map +1 -0
- package/dist/ai-service/util/peer-liveness.js +42 -0
- package/dist/ai-service/util/peer-liveness.js.map +1 -0
- package/dist/ai-service/util/rpc-timeout.d.ts +12 -5
- package/dist/ai-service/util/rpc-timeout.d.ts.map +1 -1
- package/dist/ai-service/util/rpc-timeout.js +12 -5
- package/dist/ai-service/util/rpc-timeout.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +8 -21
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/lock-service/activity-tracker.d.ts +0 -5
- package/dist/lock-service/activity-tracker.d.ts.map +1 -1
- package/dist/lock-service/activity-tracker.js +0 -13
- package/dist/lock-service/activity-tracker.js.map +1 -1
- package/dist/lock-service/index.d.ts +308 -27
- package/dist/lock-service/index.d.ts.map +1 -1
- package/dist/lock-service/index.js +680 -156
- package/dist/lock-service/index.js.map +1 -1
- package/dist/policy-gate-callback-mapper.d.ts +3 -2
- package/dist/policy-gate-callback-mapper.d.ts.map +1 -1
- package/dist/policy-gate-callback-mapper.js.map +1 -1
- package/dist/policy-gate-runner.d.ts +4 -2
- package/dist/policy-gate-runner.d.ts.map +1 -1
- package/dist/policy-gate-runner.js +31 -8
- package/dist/policy-gate-runner.js.map +1 -1
- package/dist/scan-scope-merge.d.ts +16 -0
- package/dist/scan-scope-merge.d.ts.map +1 -0
- package/dist/scan-scope-merge.js +90 -0
- package/dist/scan-scope-merge.js.map +1 -0
- package/dist/socket-manager.d.ts +47 -6
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +283 -66
- package/dist/socket-manager.js.map +1 -1
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +3 -3
- package/dist/sync-service/index.js.map +1 -1
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +21 -0
- package/dist/test-setup.js.map +1 -0
- package/package.json +8 -8
package/dist/ai-service/index.js
CHANGED
|
@@ -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,
|
|
5
|
+
import { hasToolCall, stepCountIs, } from "ai";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { AiGenerationState, AiMode,
|
|
7
|
+
import { AiGenerationState, AiMode, 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,6 +14,8 @@ 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";
|
|
17
19
|
import { buildSecurityScanTools, isBackgroundTaskFrameworkEnabled, } from "./agent/tools.js";
|
|
18
20
|
import { hasPendingEscalations, flushPendingEscalations, } from "./agent/tools/apis/api-validation-orchestrator.js";
|
|
19
21
|
import { safeSampleJson } from "./agent/tools/apis/sample-json.js";
|
|
@@ -22,7 +24,7 @@ import { applyDevDatabaseMigrationsTaskMetadata, createDevDatabaseTaskTypes, wir
|
|
|
22
24
|
import { debugCache } from "./agent/tools/debug-cache.js";
|
|
23
25
|
import { securityScanResultSchema, } from "./agent/tools/report-security-findings.js";
|
|
24
26
|
import { SessionEntityPermissionStore, } from "./agent/tools2/entity-permissions.js";
|
|
25
|
-
import { ToolRegistry } from "./agent/tools2/registry.js";
|
|
27
|
+
import { ToolRegistry, } from "./agent/tools2/registry.js";
|
|
26
28
|
import { FileSystemInterface } from "./app-interface/file-system-interface.js";
|
|
27
29
|
import { createDefaultVirtualFileSystem, createPathValidator, } from "./app-interface/filesystem/index.js";
|
|
28
30
|
import { NpmPackageLookup } from "./app-interface/npm-package-lookup.js";
|
|
@@ -50,6 +52,7 @@ import { LiveEditEventForwarder, readForwarderConfigFromEnv, } from "./live-edit
|
|
|
50
52
|
import { createChaosFetch } from "./llm/chaos-fetch.js";
|
|
51
53
|
import { createClarkStreamTelemetrySink } from "./llm/clark-stream-telemetry-sink.js";
|
|
52
54
|
import { LLMClient } from "./llm/client.js";
|
|
55
|
+
import { resolveContextOptionsV2 } from "./llm/context-v2/config.js";
|
|
53
56
|
import { selectContextManagementStrategy, } from "./llm/context-v2/context-management.js";
|
|
54
57
|
import { recordCheckpointRestore } from "./llm/context-v2/context-metrics.js";
|
|
55
58
|
import { ContextManagerV2 } from "./llm/context-v2/manager.js";
|
|
@@ -57,8 +60,7 @@ import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
|
|
|
57
60
|
import { adoptLegacyPerUserContextDirs } from "./llm/context-v2/storage/legacy-context-adoption.js";
|
|
58
61
|
import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
|
|
59
62
|
import { createLLMProvider, setSpanError } from "./llm/provider.js";
|
|
60
|
-
import { traceLLM } from "./llmobs/helpers.js";
|
|
61
|
-
import { getJwtTraceTags } from "./llmobs/helpers.js";
|
|
63
|
+
import { getJwtTraceTags, traceLLM } from "./llmobs/helpers.js";
|
|
62
64
|
import llmobs from "./llmobs/index.js";
|
|
63
65
|
import { PlaywrightMcpServerManager } from "./mcp/playwright-server.js";
|
|
64
66
|
import { evaluateMigrationAutoDispatchReadiness } from "./migration-auto-dispatch-readiness.js";
|
|
@@ -87,10 +89,10 @@ import { doRuntimeReviewing } from "./state-machine/handlers/runtime-reviewing.j
|
|
|
87
89
|
import { classificationHelper } from "./state-machine/helpers/classification.js";
|
|
88
90
|
import { getContextId } from "./state-machine/helpers/context-id.js";
|
|
89
91
|
import { bindHeadlessTurnLifecycle } from "./state-machine/helpers/headless-turn-lifecycle.js";
|
|
90
|
-
import { sendUserGenerationStateChannel } from "./state-machine/helpers/peer.js";
|
|
91
|
-
import {
|
|
92
|
-
import { collectPolicyGateLlmUsage, finalizePolicyGateUsageMetrics, policyGateFailureFromStream, recordPolicyGateAttempt, } from "./state-machine/helpers/policy-gate-usage-metrics.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";
|
|
93
94
|
import { finalizePromptBillingMetrics } from "./state-machine/helpers/prompt-billing-metrics.js";
|
|
95
|
+
import { setPromptDegradationMetricIdentity } from "./state-machine/helpers/prompt-degradation-metrics.js";
|
|
94
96
|
import { paywallStopAdvice } from "./state-machine/helpers/prompt-failure.js";
|
|
95
97
|
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";
|
|
96
98
|
import { catchBackgroundPeerDisconnect, StablePeer, } from "./state-machine/helpers/stable-peer.js";
|
|
@@ -100,6 +102,7 @@ import { TemplateRenderer } from "./template-renderer.js";
|
|
|
100
102
|
import { describeMigrationInferenceJwt, isUsableMigrationInferenceJwt, } from "./util/inference-jwt.js";
|
|
101
103
|
import { buildModelConfigAttributes, processLLMConfig, } from "./util/llm-config-utils.js";
|
|
102
104
|
import { parseJwt } from "./util/parse-jwt.js";
|
|
105
|
+
import { PERMISSION_PROMPT_DELIVERY_TIMEOUT_MS } from "./util/rpc-timeout.js";
|
|
103
106
|
import { getToolCallSignature } from "./util/tool-signature.js";
|
|
104
107
|
export { AiServiceFeatureFlags } from "./features.js";
|
|
105
108
|
export { getManagedNativeDbGateDenialReason, hasEditLifecycleCapability, isManagedNativeDbAvailableForClark, } from "./managed-native-db-availability.js";
|
|
@@ -142,6 +145,9 @@ const MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH = 500;
|
|
|
142
145
|
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH = 3;
|
|
143
146
|
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS = 20;
|
|
144
147
|
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;
|
|
145
151
|
// AbortError near the budget is treated as timeout (SDK may abort slightly early).
|
|
146
152
|
const POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS = 5_000;
|
|
147
153
|
const POLICY_GATE_STREAM_ERROR_REDACTED_VALUE = "[redacted]";
|
|
@@ -354,6 +360,9 @@ const POLICY_GATE_USAGE_METADATA = {
|
|
|
354
360
|
origin: CLARK_USAGE_ORIGIN.policy_gate,
|
|
355
361
|
surface: "policy_gate",
|
|
356
362
|
};
|
|
363
|
+
const ERROR_JSON_OUTPUT_TYPE = "error-json";
|
|
364
|
+
const TOOL_MESSAGE_ROLE = "tool";
|
|
365
|
+
const TOOL_RESULT_CONTENT_TYPE = "tool-result";
|
|
357
366
|
export class AiService extends TracedEventEmitter {
|
|
358
367
|
config;
|
|
359
368
|
templateRenderer;
|
|
@@ -496,10 +505,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
496
505
|
this._runtimeSyncService = syncService;
|
|
497
506
|
this._runtimeGitOperationLock = gitOperationLock;
|
|
498
507
|
}
|
|
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
|
-
*/
|
|
503
508
|
setSyncContextProvider(provider) {
|
|
504
509
|
this._syncContextProvider = provider;
|
|
505
510
|
}
|
|
@@ -564,7 +569,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
564
569
|
this.tryAutoDispatchMigrationTurnIfReady();
|
|
565
570
|
return true;
|
|
566
571
|
}
|
|
567
|
-
// ===== Recording API =====
|
|
568
572
|
/**
|
|
569
573
|
* Check if recording is currently enabled.
|
|
570
574
|
*/
|
|
@@ -621,6 +625,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
621
625
|
applicationId: config.applicationId,
|
|
622
626
|
organizationId: config.organizationId,
|
|
623
627
|
});
|
|
628
|
+
setPromptDegradationMetricIdentity({
|
|
629
|
+
applicationId: config.applicationId,
|
|
630
|
+
organizationId: config.organizationId,
|
|
631
|
+
});
|
|
624
632
|
this.startLiveEditEventForwarding();
|
|
625
633
|
// Store the wrapped logger so getLogger() can return it
|
|
626
634
|
// The wrapped logger forwards logs to the browser console when Clark logs are enabled
|
|
@@ -904,6 +912,12 @@ export class AiService extends TracedEventEmitter {
|
|
|
904
912
|
organizationId: config.organizationId,
|
|
905
913
|
applicationId: config.applicationId,
|
|
906
914
|
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
|
+
}),
|
|
907
921
|
onUsageRecorded: (usage) => {
|
|
908
922
|
// Accumulate workflow-level metrics
|
|
909
923
|
this.clark.updateContext((context) => {
|
|
@@ -937,9 +951,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
937
951
|
createTagProvider: this.createClarkTagProviderFactory(),
|
|
938
952
|
sessionId: this.sessionId,
|
|
939
953
|
});
|
|
940
|
-
// Initialize judge service
|
|
941
954
|
this.mcpServerManager = new PlaywrightMcpServerManager(this.clark.logger);
|
|
942
|
-
// Initialize recording manager for capturing AI interactions
|
|
943
955
|
this.recordingManager = new RecordingManager({
|
|
944
956
|
recordingsDir: path.join(config.appRootDirPath, ".superblocks", "recordings"),
|
|
945
957
|
appRootDirPath: config.appRootDirPath,
|
|
@@ -1307,6 +1319,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
1307
1319
|
llmClient: this.llmClient,
|
|
1308
1320
|
toolRegistry: this.toolRegistry,
|
|
1309
1321
|
entityPermissionStore: this.entityPermissionStore,
|
|
1322
|
+
onToolPermissionParked: (messageId) => this.scheduleToolPermissionReplayWindow(messageId),
|
|
1310
1323
|
mcpServerManager: this.mcpServerManager,
|
|
1311
1324
|
viteServer: this.viteServer,
|
|
1312
1325
|
recordingManager: this.recordingManager,
|
|
@@ -2259,7 +2272,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
2259
2272
|
useMockGeneration: request.mockGeneration ?? false,
|
|
2260
2273
|
planContext: mergePlanContexts(context.planContext, request.planContext),
|
|
2261
2274
|
browserContext: request.browserContext,
|
|
2262
|
-
// Initialize workflow metrics tracking
|
|
2263
2275
|
workflowMetrics: {
|
|
2264
2276
|
toolCallCount: 0,
|
|
2265
2277
|
stepCount: 0,
|
|
@@ -2609,7 +2621,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
2609
2621
|
const timeoutController = new AbortController();
|
|
2610
2622
|
const timeoutId = setTimeout(() => timeoutController.abort(), timeout);
|
|
2611
2623
|
try {
|
|
2612
|
-
// Build the appropriate content part based on attachment type
|
|
2613
2624
|
const contentPart = this.attachmentToContentPart(attachment);
|
|
2614
2625
|
// Route labeling through the shared LLMClient (APPS-5061) as a stateless
|
|
2615
2626
|
// one-shot, mirroring the APPS-1213 (#20263) migrations. No safety config:
|
|
@@ -2663,7 +2674,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
2663
2674
|
};
|
|
2664
2675
|
}
|
|
2665
2676
|
else if (attachment.type === "pdf" || attachment.type === "archive") {
|
|
2666
|
-
// Parse data URL to extract base64 data
|
|
2667
2677
|
const match = attachment.data.match(/^data:([^;]+);base64,(.+)$/);
|
|
2668
2678
|
const data = match?.[2] ?? attachment.data;
|
|
2669
2679
|
const defaultMediaType = attachment.type === "pdf"
|
|
@@ -2763,6 +2773,10 @@ export class AiService extends TracedEventEmitter {
|
|
|
2763
2773
|
await this.clark.recordSystemMessage(params, "Environment event", "User closed the integration setup form. The integration setup might not have been completed successfully.");
|
|
2764
2774
|
}
|
|
2765
2775
|
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");
|
|
2766
2780
|
const awaitingUser = this.isAwaitingUser();
|
|
2767
2781
|
if (!this.isBusy() && !awaitingUser) {
|
|
2768
2782
|
return;
|
|
@@ -3021,11 +3035,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
3021
3035
|
logger.warn(`[ai-service] Skip request for testRunId=${testRunId} but current is ${this.clark.context.currentTestRunId}`);
|
|
3022
3036
|
return;
|
|
3023
3037
|
}
|
|
3024
|
-
// Update context with skip request
|
|
3025
3038
|
this.clark.updateContext({
|
|
3026
3039
|
skipRequestedForTestCase: testCaseId,
|
|
3027
3040
|
});
|
|
3028
|
-
// Update the test case status to skipped
|
|
3029
3041
|
const testCaseStatuses = this.clark.context.testCaseStatuses ??
|
|
3030
3042
|
new Map();
|
|
3031
3043
|
testCaseStatuses.set(testCaseId, "skipped");
|
|
@@ -3046,24 +3058,127 @@ export class AiService extends TracedEventEmitter {
|
|
|
3046
3058
|
}
|
|
3047
3059
|
logger.info(`[ai-service] Test case ${testCaseId} marked as skipped, flag set for subagent`);
|
|
3048
3060
|
}
|
|
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
|
+
}
|
|
3049
3109
|
/**
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
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.
|
|
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.
|
|
3057
3113
|
*/
|
|
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
|
+
}
|
|
3058
3164
|
async handleAiRespondToToolPermission(request) {
|
|
3165
|
+
return this.handleToolPermissionOutcome({
|
|
3166
|
+
kind: "response",
|
|
3167
|
+
...request,
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
async handleToolPermissionOutcome(request) {
|
|
3059
3171
|
const logger = this.getLogger();
|
|
3060
|
-
|
|
3172
|
+
const approved = request.kind === "response" && request.approved;
|
|
3173
|
+
logger.info(`[ai-service] handleAiRespondToToolPermission: messageId=${request.messageId}, approved=${approved}`, { outcomeKind: request.kind });
|
|
3061
3174
|
// a reconnect can cause the client to re-send this response. If
|
|
3062
3175
|
// we already applied a response with this requestId, treat the repeat as a
|
|
3063
3176
|
// no-op so the tool is not executed (or denied) twice for the same user
|
|
3064
3177
|
// decision. Only a peek here — the id is recorded once the response is
|
|
3065
3178
|
// accepted below.
|
|
3066
|
-
if (request.
|
|
3179
|
+
if (request.kind === "response" &&
|
|
3180
|
+
request.requestId &&
|
|
3181
|
+
this.requestDeduplicator.has(request.requestId)) {
|
|
3067
3182
|
logger.info("[ai-service] Ignoring duplicate tool-permission re-send", {
|
|
3068
3183
|
requestId: request.requestId,
|
|
3069
3184
|
});
|
|
@@ -3083,14 +3198,17 @@ export class AiService extends TracedEventEmitter {
|
|
|
3083
3198
|
// and honor the result so a re-send dropped here can't be applied twice. A
|
|
3084
3199
|
// duplicate that loses the atomic `markIfNew` bails before the tool runs.
|
|
3085
3200
|
// (APPS-4663)
|
|
3086
|
-
if (request.
|
|
3201
|
+
if (request.kind === "response" &&
|
|
3202
|
+
request.requestId &&
|
|
3087
3203
|
!this.requestDeduplicator.markIfNew(request.requestId)) {
|
|
3088
3204
|
logger.warn("[ai-service] Ignoring duplicate tool-permission re-send (lost dedup race)", { requestId: request.requestId });
|
|
3089
3205
|
return;
|
|
3090
3206
|
}
|
|
3091
3207
|
logger.info(`[ai-service] Clearing pending request`);
|
|
3092
3208
|
// Clear the pending request and notify UI
|
|
3093
|
-
clearPendingToolPermissionRequest(this.clark, "
|
|
3209
|
+
clearPendingToolPermissionRequest(this.clark, request.kind === "delivery_error"
|
|
3210
|
+
? "tool-permission-delivery-expired"
|
|
3211
|
+
: "tool-permission-response");
|
|
3094
3212
|
const toolCallId = pendingRequest.messageId;
|
|
3095
3213
|
const toolName = pendingRequest.toolName;
|
|
3096
3214
|
const rawInput = pendingRequest.rawInput;
|
|
@@ -3098,7 +3216,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
3098
3216
|
const llmConfig = this.clark.context.llmConfig;
|
|
3099
3217
|
logger.info(`[ai-service] Getting context toolCallId=${toolCallId})`);
|
|
3100
3218
|
const sendUserMessage = sendUserMessageChannel(this.clark, this.chatSessionStore);
|
|
3101
|
-
// Build the assistant message with the NEW tool call ID
|
|
3102
3219
|
const assistantMessage = {
|
|
3103
3220
|
role: "assistant",
|
|
3104
3221
|
content: [
|
|
@@ -3110,9 +3227,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
3110
3227
|
},
|
|
3111
3228
|
],
|
|
3112
3229
|
};
|
|
3113
|
-
// Build the tool result message based on approval/execution
|
|
3114
3230
|
let toolResultMessage;
|
|
3115
|
-
if (
|
|
3231
|
+
if (approved) {
|
|
3116
3232
|
// Approve in entity permission store
|
|
3117
3233
|
this.entityPermissionStore.approveToolCall(toolName, rawInput);
|
|
3118
3234
|
const signature = getToolCallSignature(toolName, rawInput);
|
|
@@ -3128,9 +3244,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
3128
3244
|
signature,
|
|
3129
3245
|
});
|
|
3130
3246
|
// Store always-allow permissions
|
|
3131
|
-
request.
|
|
3132
|
-
|
|
3133
|
-
|
|
3247
|
+
if (request.kind === "response") {
|
|
3248
|
+
request.alwaysAllowPermissions?.forEach((permission) => {
|
|
3249
|
+
void this.entityPermissionStore.approve(permission.entityType, permission.entityId, permission.permissionType);
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3134
3252
|
// Execute the tool with proper access control context from Clark
|
|
3135
3253
|
const accessControlContext = this.clark.getAccessControl(this.config.features);
|
|
3136
3254
|
logger.info(`[ai-service] Executing approved tool: ${toolName}`);
|
|
@@ -3222,6 +3340,23 @@ export class AiService extends TracedEventEmitter {
|
|
|
3222
3340
|
});
|
|
3223
3341
|
}
|
|
3224
3342
|
}
|
|
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
|
+
}
|
|
3225
3360
|
else {
|
|
3226
3361
|
const denyReason = request.denyMessage || "User denied permission for this tool call";
|
|
3227
3362
|
if (toolName === "testApi") {
|
|
@@ -3296,10 +3431,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
3296
3431
|
if (!this.clark.context.llmConfig) {
|
|
3297
3432
|
throw new Error("Cannot continue after tool permission: llmConfig is missing from context");
|
|
3298
3433
|
}
|
|
3299
|
-
const isClarification = isToolPermissionClarification(
|
|
3434
|
+
const isClarification = isToolPermissionClarification(approved, request.kind === "response" ? request.denyMessage : undefined);
|
|
3300
3435
|
logger.info("[ai-service] tool_permission_response continuation", {
|
|
3301
|
-
approved
|
|
3436
|
+
approved,
|
|
3302
3437
|
clarification: isClarification,
|
|
3438
|
+
outcomeKind: request.kind,
|
|
3303
3439
|
});
|
|
3304
3440
|
trace
|
|
3305
3441
|
.getActiveSpan()
|
|
@@ -3320,9 +3456,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
3320
3456
|
outcomeDedupeKey: promptOutcomeDedupeKey,
|
|
3321
3457
|
});
|
|
3322
3458
|
const continueRequest = {
|
|
3323
|
-
prompt: request.
|
|
3459
|
+
prompt: approved || request.kind === "delivery_error"
|
|
3324
3460
|
? ``
|
|
3325
|
-
: isClarification
|
|
3461
|
+
: isClarification && request.denyMessage
|
|
3326
3462
|
? request.denyMessage.trim()
|
|
3327
3463
|
: this.buildDenialContinuationPrompt(toolName, rawInput),
|
|
3328
3464
|
promptContext: {
|
|
@@ -3337,8 +3473,13 @@ export class AiService extends TracedEventEmitter {
|
|
|
3337
3473
|
responseMetadata: {
|
|
3338
3474
|
type: "tool_permission_response",
|
|
3339
3475
|
responseToMessageId: request.messageId,
|
|
3340
|
-
approved
|
|
3341
|
-
|
|
3476
|
+
approved,
|
|
3477
|
+
outcome: request.kind === "delivery_error"
|
|
3478
|
+
? "delivery_expired"
|
|
3479
|
+
: "user_response",
|
|
3480
|
+
alwaysAllowPermissions: request.kind === "response"
|
|
3481
|
+
? request.alwaysAllowPermissions
|
|
3482
|
+
: undefined,
|
|
3342
3483
|
...(isClarification ? { clarification: true } : {}),
|
|
3343
3484
|
},
|
|
3344
3485
|
};
|
|
@@ -3416,9 +3557,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
3416
3557
|
* Returned so the caller can be told which turn it now owns; a turn started
|
|
3417
3558
|
* without one is reachable by this socket alone.
|
|
3418
3559
|
*/
|
|
3419
|
-
beginHeadlessTurn(peer, peerId, turnId) {
|
|
3560
|
+
beginHeadlessTurn(peer, peerId, turnId, liveness) {
|
|
3420
3561
|
this.getLogger().info(`[ai-service] headless turn started by gateway peer ${peerId}`);
|
|
3421
|
-
this.stablePeer.beginTurn({ peer, peerId, turnId });
|
|
3562
|
+
this.stablePeer.beginTurn({ liveness, peer, peerId, turnId });
|
|
3422
3563
|
return turnId;
|
|
3423
3564
|
}
|
|
3424
3565
|
/**
|
|
@@ -3430,8 +3571,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
3430
3571
|
* the caller already owns the turn, or is talking about a turn that is not
|
|
3431
3572
|
* this one — are silent. Only an actual handover is worth a line.
|
|
3432
3573
|
*/
|
|
3433
|
-
claimHeadlessTurn(peer, peerId, turnId) {
|
|
3434
|
-
if (this.stablePeer.claimTurn({ peer, peerId, turnId }) !==
|
|
3574
|
+
claimHeadlessTurn(peer, peerId, turnId, liveness) {
|
|
3575
|
+
if (this.stablePeer.claimTurn({ liveness, peer, peerId, turnId }) !==
|
|
3576
|
+
"claimed") {
|
|
3435
3577
|
return;
|
|
3436
3578
|
}
|
|
3437
3579
|
this.getLogger().info(`[ai-service] headless turn moved to reattached gateway peer ${peerId}`);
|
|
@@ -3452,14 +3594,19 @@ export class AiService extends TracedEventEmitter {
|
|
|
3452
3594
|
* it, so a socket that a reattach already replaced cannot strip routing from
|
|
3453
3595
|
* its successor.
|
|
3454
3596
|
*/
|
|
3455
|
-
endHeadlessTurn(peerId) {
|
|
3456
|
-
|
|
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)) {
|
|
3457
3604
|
return;
|
|
3458
3605
|
}
|
|
3459
3606
|
this.getLogger().info(`[ai-service] headless turn peer ${peerId} detached; falling back to the editor binding`);
|
|
3460
3607
|
this.stablePeer.releaseTurnPeer(peerId);
|
|
3461
3608
|
}
|
|
3462
|
-
handleUserConnected(peer, peerId) {
|
|
3609
|
+
handleUserConnected(peer, peerId, liveness) {
|
|
3463
3610
|
const existingPeerId = this.stablePeer.peerId;
|
|
3464
3611
|
this.getLogger().info(`[ai-service] handleUserConnected: peerId=${peerId}`);
|
|
3465
3612
|
if (existingPeerId) {
|
|
@@ -3476,7 +3623,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
3476
3623
|
// `clark.context.peer` is a permanent StablePeer proxy; we swap its
|
|
3477
3624
|
// inner (and peerId) here. Any queued calls accumulated while
|
|
3478
3625
|
// disconnected flush in order as part of setInner.
|
|
3479
|
-
this.stablePeer.setInner({ peer, peerId });
|
|
3626
|
+
this.stablePeer.setInner({ liveness, peer, peerId });
|
|
3480
3627
|
this.getLogger().info(`[ai-service] Peer set: ${peerId}`);
|
|
3481
3628
|
// A replacement (or freshly reconnecting) peer must re-prove its
|
|
3482
3629
|
// identity via `notifyAuthenticatedRpc` before live task pushes
|
|
@@ -3504,25 +3651,18 @@ export class AiService extends TracedEventEmitter {
|
|
|
3504
3651
|
messageId: request.messageId,
|
|
3505
3652
|
toolName: request.toolName,
|
|
3506
3653
|
});
|
|
3507
|
-
//
|
|
3508
|
-
//
|
|
3509
|
-
//
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
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
|
-
});
|
|
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
|
+
}
|
|
3526
3666
|
});
|
|
3527
3667
|
}
|
|
3528
3668
|
else {
|
|
@@ -3621,7 +3761,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
3621
3761
|
* will never answer — the case a headless build leaves behind when its
|
|
3622
3762
|
* gateway socket closes over an open editor tab.
|
|
3623
3763
|
*/
|
|
3624
|
-
handleUserDisconnected(peerId, replacement) {
|
|
3764
|
+
handleUserDisconnected(peerId, replacement, liveness) {
|
|
3625
3765
|
const currentPeerId = this.stablePeer.peerId;
|
|
3626
3766
|
this.getLogger().info(`[ai-service] handleUserDisconnected: peerId=${peerId}`);
|
|
3627
3767
|
if (!currentPeerId) {
|
|
@@ -3631,6 +3771,19 @@ export class AiService extends TracedEventEmitter {
|
|
|
3631
3771
|
this.getLogger().info(`[ai-service] Stale disconnect ignored: peer ${peerId} disconnecting but current peer is ${currentPeerId}`);
|
|
3632
3772
|
return; // Don't clear the current peer on stale disconnect
|
|
3633
3773
|
}
|
|
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
|
+
}
|
|
3634
3787
|
this.getLogger().info(`[ai-service] peer disconnecting: ${peerId}`);
|
|
3635
3788
|
// Drop any deferred state-push registration for this peer so a
|
|
3636
3789
|
// disconnect that races a never-arrived auth proof doesn't leave
|
|
@@ -3650,11 +3803,11 @@ export class AiService extends TracedEventEmitter {
|
|
|
3650
3803
|
if (contextId) {
|
|
3651
3804
|
this.contextManagerV2.clearContext(contextId);
|
|
3652
3805
|
}
|
|
3653
|
-
if (replacement && replacement.peerId !== peerId) {
|
|
3806
|
+
if (replacement && (replacement.peerId !== peerId || liveness)) {
|
|
3654
3807
|
this.getLogger().info(`[ai-service] Rebinding peer ${peerId} to surviving editor ${replacement.peerId}`);
|
|
3655
3808
|
// Same door a fresh tab comes through, so the survivor gets the state
|
|
3656
3809
|
// resync and re-auth a newly bound peer is owed.
|
|
3657
|
-
this.handleUserConnected(replacement.peer, replacement.peerId);
|
|
3810
|
+
this.handleUserConnected(replacement.peer, replacement.peerId, replacement.liveness);
|
|
3658
3811
|
return;
|
|
3659
3812
|
}
|
|
3660
3813
|
this.clearPeerConnection();
|
|
@@ -3938,12 +4091,12 @@ export class AiService extends TracedEventEmitter {
|
|
|
3938
4091
|
let textDeltaChars = 0;
|
|
3939
4092
|
let modelName;
|
|
3940
4093
|
let promptHash;
|
|
3941
|
-
// Hoisted so the outer catch can attribute a failed run to the same model
|
|
3942
|
-
//
|
|
3943
|
-
//
|
|
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.
|
|
3944
4097
|
let coreModel;
|
|
3945
|
-
|
|
3946
|
-
let
|
|
4098
|
+
const llmUsage = [];
|
|
4099
|
+
let hadFallback = false;
|
|
3947
4100
|
let attemptRecorded = false;
|
|
3948
4101
|
const scanRoot = typeof this.appShell.appRootDirPath === "string"
|
|
3949
4102
|
? this.appShell.appRootDirPath
|
|
@@ -3958,13 +4111,15 @@ export class AiService extends TracedEventEmitter {
|
|
|
3958
4111
|
metricsRecorded = true;
|
|
3959
4112
|
recordPolicyAgentExecution({
|
|
3960
4113
|
durationMs: Date.now() - startedAt,
|
|
4114
|
+
llmUsage,
|
|
3961
4115
|
outcome,
|
|
3962
4116
|
stepCount,
|
|
3963
4117
|
toolCallCount: totalToolCalls(),
|
|
3964
4118
|
});
|
|
3965
4119
|
};
|
|
3966
4120
|
const buildRuntimeMetadata = (finalFailureReason) => ({
|
|
3967
|
-
diagnosticsVersion:
|
|
4121
|
+
diagnosticsVersion: 3,
|
|
4122
|
+
diffFiles: request.scanScopeJson?.changedFiles ?? [],
|
|
3968
4123
|
durationMs: Date.now() - startedAt,
|
|
3969
4124
|
filePathsListed: Array.from(filePathsListed).slice(0, 100),
|
|
3970
4125
|
filePathsRead: Array.from(filePathsRead).slice(0, 100),
|
|
@@ -4001,8 +4156,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
4001
4156
|
trigger_type: "policy_gate",
|
|
4002
4157
|
}));
|
|
4003
4158
|
const { model, providerConfig } = llmProvider.modelForTask("generateBroadEdit");
|
|
4159
|
+
const { model: summarizationModel, providerConfig: summarizationProviderConfig, } = llmProvider.modelForTask("summarizeMessages");
|
|
4004
4160
|
modelName = typeof model === "string" ? model : model.modelId;
|
|
4005
|
-
providerName = providerConfig?.provider ?? "unknown";
|
|
4006
4161
|
coreModel = llmConfig?.coreModel;
|
|
4007
4162
|
// Counted as soon as the model resolves, because everything below is
|
|
4008
4163
|
// inside this try and so is guaranteed a terminal outcome from the catch.
|
|
@@ -4015,37 +4170,105 @@ export class AiService extends TracedEventEmitter {
|
|
|
4015
4170
|
name: i.name,
|
|
4016
4171
|
pluginType: i.plugin?.id ?? "unknown",
|
|
4017
4172
|
}));
|
|
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
|
+
}
|
|
4018
4191
|
const appContext = { integrations };
|
|
4019
|
-
const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
|
|
4192
|
+
const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt, scanScopeJson);
|
|
4020
4193
|
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
|
+
});
|
|
4021
4218
|
const scanToolsDeps = {
|
|
4022
4219
|
appShell: this.appShell,
|
|
4023
4220
|
features: this.config.features ?? {},
|
|
4024
4221
|
integrationStore: this.integrationStore,
|
|
4025
|
-
llmClient:
|
|
4222
|
+
llmClient: policyGateLlmClient,
|
|
4026
4223
|
llmProvider: llmProvider,
|
|
4224
|
+
storeSummary: (toolCallId, summary) => {
|
|
4225
|
+
scanContext.current?.storeSummary(toolCallId, summary);
|
|
4226
|
+
},
|
|
4027
4227
|
};
|
|
4028
4228
|
const tools = await buildSecurityScanTools(this.clark, scanToolsDeps);
|
|
4029
|
-
const result =
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
{
|
|
4033
|
-
|
|
4034
|
-
|
|
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",
|
|
4035
4236
|
},
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
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,
|
|
4040
4246
|
model,
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
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
|
+
},
|
|
4044
4262
|
tools,
|
|
4045
|
-
stopWhen:
|
|
4046
|
-
|
|
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
|
+
},
|
|
4047
4271
|
});
|
|
4048
|
-
streamResult = result;
|
|
4049
4272
|
// Best-effort: surface the agent's latest tool/reasoning step in the editor
|
|
4050
4273
|
// so a running policy row shows live activity instead of an opaque spinner.
|
|
4051
4274
|
// Only meaningful when an editor session is connected; never blocks the run.
|
|
@@ -4076,16 +4299,6 @@ export class AiService extends TracedEventEmitter {
|
|
|
4076
4299
|
: "unknown";
|
|
4077
4300
|
streamPartTypeCounts[partType] =
|
|
4078
4301
|
(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
|
-
}
|
|
4089
4302
|
const textDelta = part.textDelta ??
|
|
4090
4303
|
part.text ??
|
|
4091
4304
|
part.delta;
|
|
@@ -4198,7 +4411,9 @@ export class AiService extends TracedEventEmitter {
|
|
|
4198
4411
|
}
|
|
4199
4412
|
catch (error) {
|
|
4200
4413
|
if (error instanceof Error &&
|
|
4201
|
-
(error.name === "AbortError" ||
|
|
4414
|
+
(error.name === "AbortError" ||
|
|
4415
|
+
error.name === "AbortedError" ||
|
|
4416
|
+
error.name === "TimeoutError")) {
|
|
4202
4417
|
const elapsedMs = Date.now() - startedAt;
|
|
4203
4418
|
const isBudgetTimeout = error.name === "TimeoutError" ||
|
|
4204
4419
|
elapsedMs + POLICY_GATE_TIMEOUT_CLASSIFICATION_SLACK_MS >=
|
|
@@ -4218,6 +4433,18 @@ export class AiService extends TracedEventEmitter {
|
|
|
4218
4433
|
});
|
|
4219
4434
|
failPolicyGateExecution("Policy gate agent aborted", "aborted");
|
|
4220
4435
|
}
|
|
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
|
+
}
|
|
4221
4448
|
throw error;
|
|
4222
4449
|
}
|
|
4223
4450
|
if (!scanResult) {
|
|
@@ -4239,11 +4466,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
4239
4466
|
aiAgentRunId: request.aiAgentRunId,
|
|
4240
4467
|
chatSessionStore: this.chatSessionStore,
|
|
4241
4468
|
coreModel,
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
providerName,
|
|
4245
|
-
result,
|
|
4246
|
-
}),
|
|
4469
|
+
hadFallback,
|
|
4470
|
+
llmUsage,
|
|
4247
4471
|
logger,
|
|
4248
4472
|
outcome: "success",
|
|
4249
4473
|
});
|
|
@@ -4266,13 +4490,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
4266
4490
|
chatSessionStore: this.chatSessionStore,
|
|
4267
4491
|
coreModel,
|
|
4268
4492
|
failure: policyGateFailureFromStream(streamErrors),
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
modelName: modelName ?? "unknown",
|
|
4272
|
-
providerName,
|
|
4273
|
-
result: streamResult,
|
|
4274
|
-
})
|
|
4275
|
-
: [],
|
|
4493
|
+
hadFallback,
|
|
4494
|
+
llmUsage,
|
|
4276
4495
|
logger,
|
|
4277
4496
|
outcome: "failure",
|
|
4278
4497
|
});
|