@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.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -9
- 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 +6 -34
- 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 +1 -2
- 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 +9 -52
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery.d.ts +16 -25
- package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery.js +38 -384
- 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 +35 -9
- 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 +98 -71
- 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 +2 -3
- 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 +1 -13
- 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 +3 -8
- 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 +2 -4
- 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 +2 -4
- 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 +3 -6
- 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 +6 -18
- package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/integration-setup.js +15 -41
- 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 +2 -4
- 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 +2 -4
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +14 -21
- 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 +25 -26
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +121 -340
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/integrations/store.d.ts +1 -2
- package/dist/ai-service/integrations/store.d.ts.map +1 -1
- package/dist/ai-service/integrations/store.js +2 -3
- 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 +3 -9
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +3 -10
- 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 +0 -3
- 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 +9 -10
- package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts +0 -4
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +41 -19
- 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 +1 -26
- package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
- package/dist/ai-service/policy-agent-metrics.d.ts +0 -10
- package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -1
- package/dist/ai-service/policy-agent-metrics.js +0 -33
- package/dist/ai-service/policy-agent-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +1 -29
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +1 -3
- 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 +6 -7
- 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 +5 -18
- 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 +6 -8
- 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 +14 -0
- 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 +36 -0
- 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 +0 -2
- 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 +0 -3
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +0 -1
- 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 +27 -60
- 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 +1 -12
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.js +9 -42
- 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 +0 -6
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/util/rpc-timeout.d.ts +5 -12
- package/dist/ai-service/util/rpc-timeout.d.ts.map +1 -1
- package/dist/ai-service/util/rpc-timeout.js +5 -12
- 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 +21 -8
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/lock-service/activity-tracker.d.ts +5 -0
- package/dist/lock-service/activity-tracker.d.ts.map +1 -1
- package/dist/lock-service/activity-tracker.js +13 -0
- package/dist/lock-service/activity-tracker.js.map +1 -1
- package/dist/lock-service/index.d.ts +27 -308
- package/dist/lock-service/index.d.ts.map +1 -1
- package/dist/lock-service/index.js +156 -680
- package/dist/lock-service/index.js.map +1 -1
- package/dist/policy-gate-callback-mapper.d.ts +2 -3
- 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 +2 -4
- package/dist/policy-gate-runner.d.ts.map +1 -1
- package/dist/policy-gate-runner.js +8 -31
- package/dist/policy-gate-runner.js.map +1 -1
- package/dist/socket-manager.d.ts +6 -47
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +66 -283
- 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/package.json +8 -8
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts +0 -3
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts.map +0 -1
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js +0 -23
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js.map +0 -1
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts +0 -15
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts.map +0 -1
- package/dist/ai-service/llm/stream/observers/step-token-usage.js +0 -88
- package/dist/ai-service/llm/stream/observers/step-token-usage.js.map +0 -1
- package/dist/ai-service/llm/stream/terminal-reason.d.ts +0 -6
- package/dist/ai-service/llm/stream/terminal-reason.d.ts.map +0 -1
- package/dist/ai-service/llm/stream/terminal-reason.js +0 -73
- package/dist/ai-service/llm/stream/terminal-reason.js.map +0 -1
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts +0 -28
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts.map +0 -1
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js +0 -254
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js.map +0 -1
- package/dist/ai-service/util/call-peer.d.ts +0 -11
- package/dist/ai-service/util/call-peer.d.ts.map +0 -1
- package/dist/ai-service/util/call-peer.js +0 -47
- package/dist/ai-service/util/call-peer.js.map +0 -1
- package/dist/ai-service/util/peer-liveness.d.ts +0 -23
- package/dist/ai-service/util/peer-liveness.d.ts.map +0 -1
- package/dist/ai-service/util/peer-liveness.js +0 -42
- package/dist/ai-service/util/peer-liveness.js.map +0 -1
- package/dist/scan-scope-merge.d.ts +0 -16
- package/dist/scan-scope-merge.d.ts.map +0 -1
- package/dist/scan-scope-merge.js +0 -90
- package/dist/scan-scope-merge.js.map +0 -1
- package/dist/test-setup.d.ts +0 -2
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -21
- package/dist/test-setup.js.map +0 -1
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, 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
|
|
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 {
|
|
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 {
|
|
93
|
-
import {
|
|
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
|
-
*
|
|
3111
|
-
*
|
|
3112
|
-
*
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
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.
|
|
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:
|
|
3323
|
+
prompt: request.approved
|
|
3460
3324
|
? ``
|
|
3461
|
-
: isClarification
|
|
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
|
-
|
|
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
|
|
3419
|
+
beginHeadlessTurn(peer, peerId, turnId) {
|
|
3561
3420
|
this.getLogger().info(`[ai-service] headless turn started by gateway peer ${peerId}`);
|
|
3562
|
-
this.stablePeer.beginTurn({
|
|
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
|
|
3575
|
-
if (this.stablePeer.claimTurn({
|
|
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
|
|
3598
|
-
|
|
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
|
|
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({
|
|
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
|
-
//
|
|
3655
|
-
//
|
|
3656
|
-
//
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
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
|
|
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 &&
|
|
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
|
|
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
|
|
4095
|
-
// the attempt it belongs to
|
|
4096
|
-
//
|
|
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
|
-
|
|
4099
|
-
let
|
|
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:
|
|
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
|
|
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:
|
|
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 =
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
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
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
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
|
-
|
|
4248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4470
|
-
|
|
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
|
-
|
|
4494
|
-
|
|
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
|
});
|