@superblocksteam/vite-plugin-file-sync 2.0.123 → 2.0.124-next.1
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 +16 -1
- 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 +32 -1
- 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.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +3 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/api-comparator.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/api-comparator.js +1 -4
- package/dist/ai-service/agent/tools/apis/api-comparator.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/api-testing-state.js +7 -0
- package/dist/ai-service/agent/tools/apis/api-testing-state.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-api-docs.d.ts +1 -1
- package/dist/ai-service/agent/tools/apis/write-api.d.ts +2 -2
- package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.js +11 -33
- package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
- package/dist/ai-service/agent/tools/build-copy-directory.d.ts +1 -1
- package/dist/ai-service/agent/tools/build-finalize.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-finalize.js +11 -2
- package/dist/ai-service/agent/tools/build-finalize.js.map +1 -1
- package/dist/ai-service/agent/tools/build-install-packages.d.ts +41 -1
- package/dist/ai-service/agent/tools/build-install-packages.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-install-packages.js +217 -0
- package/dist/ai-service/agent/tools/build-install-packages.js.map +1 -1
- package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts +28 -0
- package/dist/ai-service/agent/tools/build-lookup-npm-package.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/build-lookup-npm-package.js +78 -0
- package/dist/ai-service/agent/tools/build-lookup-npm-package.js.map +1 -0
- package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +4 -4
- package/dist/ai-service/agent/tools/build-navigate-preview.d.ts +16 -0
- package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/build-navigate-preview.js +68 -0
- package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -0
- package/dist/ai-service/agent/tools/build-write-file.d.ts +1 -1
- package/dist/ai-service/agent/tools/databases/dev-database.d.ts +9 -9
- package/dist/ai-service/agent/tools/index.d.ts +2 -0
- package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/index.js +2 -0
- package/dist/ai-service/agent/tools/index.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +3 -3
- package/dist/ai-service/agent/tools/report-security-findings.d.ts +11 -5
- package/dist/ai-service/agent/tools/report-security-findings.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/report-security-findings.js +1 -0
- package/dist/ai-service/agent/tools/report-security-findings.js.map +1 -1
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +3 -1
- 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/agent/tools2/tools/grep.d.ts +2 -2
- package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts +2 -2
- package/dist/ai-service/app-interface/npm-error-parser.d.ts +161 -0
- package/dist/ai-service/app-interface/npm-error-parser.d.ts.map +1 -0
- package/dist/ai-service/app-interface/npm-error-parser.js +510 -0
- package/dist/ai-service/app-interface/npm-error-parser.js.map +1 -0
- package/dist/ai-service/app-interface/npm-package-lookup.d.ts +286 -0
- package/dist/ai-service/app-interface/npm-package-lookup.d.ts.map +1 -0
- package/dist/ai-service/app-interface/npm-package-lookup.js +793 -0
- package/dist/ai-service/app-interface/npm-package-lookup.js.map +1 -0
- package/dist/ai-service/app-interface/npm-registry.d.ts +735 -47
- package/dist/ai-service/app-interface/npm-registry.d.ts.map +1 -1
- package/dist/ai-service/app-interface/npm-registry.js +1882 -153
- package/dist/ai-service/app-interface/npm-registry.js.map +1 -1
- package/dist/ai-service/app-interface/shell.d.ts +118 -1
- package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
- package/dist/ai-service/app-interface/shell.js +590 -157
- package/dist/ai-service/app-interface/shell.js.map +1 -1
- package/dist/ai-service/app-skills/manager.d.ts +1 -1
- package/dist/ai-service/app-skills/manager.d.ts.map +1 -1
- package/dist/ai-service/app-skills/manager.js +39 -7
- package/dist/ai-service/app-skills/manager.js.map +1 -1
- package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts +9 -0
- package/dist/ai-service/checklist/api-migration-checklist-gate.d.ts.map +1 -0
- package/dist/ai-service/checklist/api-migration-checklist-gate.js +30 -0
- package/dist/ai-service/checklist/api-migration-checklist-gate.js.map +1 -0
- package/dist/ai-service/checklist/api-migration-origins.d.ts +4 -0
- package/dist/ai-service/checklist/api-migration-origins.d.ts.map +1 -0
- package/dist/ai-service/checklist/api-migration-origins.js +8 -0
- package/dist/ai-service/checklist/api-migration-origins.js.map +1 -0
- package/dist/ai-service/checklist/persisted-checklist-store.d.ts +2 -3
- package/dist/ai-service/checklist/persisted-checklist-store.d.ts.map +1 -1
- package/dist/ai-service/checklist/persisted-checklist-store.js +6 -7
- package/dist/ai-service/checklist/persisted-checklist-store.js.map +1 -1
- package/dist/ai-service/context-archive-paths.d.ts +4 -0
- package/dist/ai-service/context-archive-paths.d.ts.map +1 -0
- package/dist/ai-service/context-archive-paths.js +28 -0
- package/dist/ai-service/context-archive-paths.js.map +1 -0
- package/dist/ai-service/context-download.d.ts +3 -1
- package/dist/ai-service/context-download.d.ts.map +1 -1
- package/dist/ai-service/context-download.js +48 -9
- package/dist/ai-service/context-download.js.map +1 -1
- package/dist/ai-service/context-upload.d.ts +3 -2
- package/dist/ai-service/context-upload.d.ts.map +1 -1
- package/dist/ai-service/context-upload.js +44 -26
- package/dist/ai-service/context-upload.js.map +1 -1
- package/dist/ai-service/dev-database-client.d.ts +3 -11
- package/dist/ai-service/dev-database-client.d.ts.map +1 -1
- package/dist/ai-service/dev-database-client.js.map +1 -1
- package/dist/ai-service/features.d.ts +4 -0
- package/dist/ai-service/features.d.ts.map +1 -1
- package/dist/ai-service/features.js +8 -0
- package/dist/ai-service/features.js.map +1 -1
- package/dist/ai-service/filter-disabled-tools-for-migration.d.ts.map +1 -1
- package/dist/ai-service/filter-disabled-tools-for-migration.js +4 -0
- package/dist/ai-service/filter-disabled-tools-for-migration.js.map +1 -1
- package/dist/ai-service/index.d.ts +103 -0
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +610 -25
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/llm/client.d.ts +38 -3
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +73 -22
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context-v2/adapter.d.ts +3 -1
- package/dist/ai-service/llm/context-v2/adapter.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/adapter.js +11 -11
- package/dist/ai-service/llm/context-v2/adapter.js.map +1 -1
- package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts +44 -0
- package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/client-side.js +170 -0
- package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts +56 -0
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js +9 -0
- package/dist/ai-service/llm/context-v2/compaction/compaction-strategy.js.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/index.d.ts +5 -0
- package/dist/ai-service/llm/context-v2/compaction/index.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/index.js +3 -0
- package/dist/ai-service/llm/context-v2/compaction/index.js.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts +30 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.js +130 -0
- package/dist/ai-service/llm/context-v2/compaction/server-side.js.map +1 -0
- package/dist/ai-service/llm/context-v2/context-management.d.ts +203 -0
- package/dist/ai-service/llm/context-v2/context-management.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/context-management.js +183 -0
- package/dist/ai-service/llm/context-v2/context-management.js.map +1 -0
- package/dist/ai-service/llm/context-v2/context.d.ts +64 -22
- package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context.js +233 -157
- package/dist/ai-service/llm/context-v2/context.js.map +1 -1
- package/dist/ai-service/llm/context-v2/conversation-context.d.ts +24 -7
- package/dist/ai-service/llm/context-v2/conversation-context.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/conversation-context.js +1 -1
- package/dist/ai-service/llm/context-v2/index.d.ts +0 -4
- package/dist/ai-service/llm/context-v2/index.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/index.js +0 -4
- package/dist/ai-service/llm/context-v2/index.js.map +1 -1
- package/dist/ai-service/llm/context-v2/manager.d.ts +24 -3
- package/dist/ai-service/llm/context-v2/manager.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/manager.js +146 -4
- package/dist/ai-service/llm/context-v2/manager.js.map +1 -1
- package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts +2 -7
- package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js +5 -73
- package/dist/ai-service/llm/context-v2/migrations/v1-to-v2.js.map +1 -1
- package/dist/ai-service/llm/context-v2/storage/event-store.d.ts +57 -0
- package/dist/ai-service/llm/context-v2/storage/event-store.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/event-store.js +10 -0
- package/dist/ai-service/llm/context-v2/storage/event-store.js.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +50 -0
- package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/event-types.js +10 -0
- package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts +87 -0
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js +184 -0
- package/dist/ai-service/llm/context-v2/storage/jsonl-event-store.js.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/migration.d.ts +49 -0
- package/dist/ai-service/llm/context-v2/storage/migration.d.ts.map +1 -0
- package/dist/ai-service/llm/context-v2/storage/migration.js +126 -0
- package/dist/ai-service/llm/context-v2/storage/migration.js.map +1 -0
- package/dist/ai-service/llm/context-v2/types.d.ts +16 -11
- package/dist/ai-service/llm/context-v2/types.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/types.js +2 -2
- package/dist/ai-service/llm/context-v2/types.js.map +1 -1
- package/dist/ai-service/llm/impl/clark.d.ts +8 -0
- package/dist/ai-service/llm/impl/clark.d.ts.map +1 -1
- package/dist/ai-service/llm/impl/clark.js +8 -0
- package/dist/ai-service/llm/impl/clark.js.map +1 -1
- package/dist/ai-service/llm/interaction/adapters/vercel.d.ts.map +1 -1
- package/dist/ai-service/llm/interaction/adapters/vercel.js +17 -1
- package/dist/ai-service/llm/interaction/adapters/vercel.js.map +1 -1
- package/dist/ai-service/llm/provider.d.ts.map +1 -1
- package/dist/ai-service/llm/provider.js +1 -3
- package/dist/ai-service/llm/provider.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/context.d.ts +23 -0
- package/dist/ai-service/llm/stream/observers/context.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/observers/context.js +62 -0
- package/dist/ai-service/llm/stream/observers/context.js.map +1 -1
- package/dist/ai-service/llm/tool-context-integrity.d.ts +25 -0
- package/dist/ai-service/llm/tool-context-integrity.d.ts.map +1 -0
- package/dist/ai-service/llm/tool-context-integrity.js +141 -0
- package/dist/ai-service/llm/tool-context-integrity.js.map +1 -0
- package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts +1 -1
- package/dist/ai-service/skills/system/superblocks-migration/skill.generated.d.ts.map +1 -1
- package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js +6 -1
- package/dist/ai-service/skills/system/superblocks-migration/skill.generated.js.map +1 -1
- package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts +1 -1
- package/dist/ai-service/skills/system/third-party-migration/skill.generated.d.ts.map +1 -1
- package/dist/ai-service/skills/system/third-party-migration/skill.generated.js +3 -2
- package/dist/ai-service/skills/system/third-party-migration/skill.generated.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +23 -2
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +37 -2
- 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 +29 -2
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/awaiting-user.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/awaiting-user.js +9 -0
- package/dist/ai-service/state-machine/handlers/awaiting-user.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 +25 -2
- 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 +6 -0
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +36 -0
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -0
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +30 -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 +85 -2
- package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/user-preferences.d.ts +8 -0
- package/dist/ai-service/state-machine/helpers/user-preferences.d.ts.map +1 -0
- package/dist/ai-service/state-machine/helpers/user-preferences.js +11 -0
- package/dist/ai-service/state-machine/helpers/user-preferences.js.map +1 -0
- package/dist/ai-service/template-renderer.d.ts +18 -1
- package/dist/ai-service/template-renderer.d.ts.map +1 -1
- package/dist/ai-service/template-renderer.js +85 -20
- package/dist/ai-service/template-renderer.js.map +1 -1
- package/dist/ai-service/types.d.ts +157 -0
- package/dist/ai-service/types.d.ts.map +1 -1
- package/dist/ai-service/types.js +137 -0
- package/dist/ai-service/types.js.map +1 -1
- package/dist/ai-service/util/llm-config-utils.d.ts +21 -22
- package/dist/ai-service/util/llm-config-utils.d.ts.map +1 -1
- package/dist/ai-service/util/llm-config-utils.js +40 -67
- package/dist/ai-service/util/llm-config-utils.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +5 -0
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/file-system-helpers.d.ts +17 -0
- package/dist/file-system-helpers.d.ts.map +1 -1
- package/dist/file-system-helpers.js +22 -0
- package/dist/file-system-helpers.js.map +1 -1
- package/dist/git-service/index.d.ts.map +1 -1
- package/dist/git-service/index.js +15 -1
- package/dist/git-service/index.js.map +1 -1
- package/dist/migration/migration-routes.d.ts.map +1 -1
- package/dist/migration/migration-routes.js +26 -3
- package/dist/migration/migration-routes.js.map +1 -1
- package/dist/migration/translation-prompt.d.ts.map +1 -1
- package/dist/migration/translation-prompt.js +5 -0
- package/dist/migration/translation-prompt.js.map +1 -1
- package/dist/migration-templates/app-fullstack/package.json +2 -2
- package/dist/policy-gate-callback-mapper.d.ts +24 -0
- package/dist/policy-gate-callback-mapper.d.ts.map +1 -0
- package/dist/policy-gate-callback-mapper.js +61 -0
- package/dist/policy-gate-callback-mapper.js.map +1 -0
- package/dist/policy-gate-runner.d.ts +32 -0
- package/dist/policy-gate-runner.d.ts.map +1 -0
- package/dist/policy-gate-runner.js +191 -0
- package/dist/policy-gate-runner.js.map +1 -0
- package/dist/router-parser.d.ts.map +1 -1
- package/dist/router-parser.js +107 -11
- package/dist/router-parser.js.map +1 -1
- package/dist/sync-service/list-dir.d.ts.map +1 -1
- package/dist/sync-service/list-dir.js +13 -3
- package/dist/sync-service/list-dir.js.map +1 -1
- package/package.json +28 -10
package/dist/ai-service/index.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { generateObject, hasToolCall, } from "ai";
|
|
4
|
+
import { generateObject, hasToolCall, stepCountIs, streamText as aiStreamText, } from "ai";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { AiMode, resolveAttachmentFileName, } from "@superblocksteam/library-shared/types";
|
|
7
7
|
import { addTracingToMethods, classifyAttachmentDelivery, FactAccessType, FactCreatedSource, FactEntityScope, TracedEventEmitter, } from "@superblocksteam/shared";
|
|
8
8
|
import { buildTranslationPrompt } from "../migration/translation-prompt.js";
|
|
9
9
|
import { getErrorMeta, getLogger, setDefaultLogger } from "../util/logger.js";
|
|
10
|
+
import { composeSecurityAgentPrompt, } from "./agent/prompts/build-security-scan-prompt.js";
|
|
10
11
|
import { getToolCallArguments, getToolErrorCode, getToolOutput, } from "./agent/tool-message-utils.js";
|
|
12
|
+
import { buildSecurityScanTools, } from "./agent/tools.js";
|
|
11
13
|
import { hasPendingEscalations, flushPendingEscalations, } from "./agent/tools/apis/api-validation-orchestrator.js";
|
|
12
14
|
import { safeSampleJson } from "./agent/tools/apis/sample-json.js";
|
|
13
15
|
import { resolveSdkApiEntryPoint } from "./agent/tools/apis/sdk-registry.js";
|
|
14
16
|
import { readFile } from "./agent/tools/build-read-file.js";
|
|
15
17
|
import { debugCache } from "./agent/tools/debug-cache.js";
|
|
16
18
|
import { buildReadFileToolFactory } from "./agent/tools/index.js";
|
|
19
|
+
import { securityScanResultSchema, } from "./agent/tools/report-security-findings.js";
|
|
17
20
|
import { SessionEntityPermissionStore, } from "./agent/tools2/entity-permissions.js";
|
|
18
21
|
import { ToolRegistry } from "./agent/tools2/registry.js";
|
|
19
22
|
import { explainCodeFinalizeToolFactory } from "./agent/tools2/tools/explain-code-finalize.js";
|
|
20
23
|
import { FileSystemInterface } from "./app-interface/file-system-interface.js";
|
|
21
24
|
import { createDefaultVirtualFileSystem, createPathValidator, } from "./app-interface/filesystem/index.js";
|
|
25
|
+
import { NpmPackageLookup } from "./app-interface/npm-package-lookup.js";
|
|
26
|
+
import { NpmRegistryClient, createDiskRegistryPolicyStore, } from "./app-interface/npm-registry.js";
|
|
22
27
|
import { AppShell } from "./app-interface/shell.js";
|
|
23
28
|
import { AppSkillsManager } from "./app-skills/manager.js";
|
|
24
29
|
import { isUploadedAttachment, toUploadedAttachmentContentPart, } from "./attachments/uploaded-content-part.js";
|
|
25
30
|
import { ChatSessionStore } from "./chat/chat-session-store.js";
|
|
26
31
|
import { normalizeTextAttachmentForModel } from "./chat/utils.js";
|
|
27
|
-
import { API_MIGRATION_ORIGINS, readPersistedChecklist, } from "./checklist/persisted-checklist-store.js";
|
|
32
|
+
import { API_MIGRATION_ORIGINS, readPersistedChecklist, updateChecklistItem, } from "./checklist/persisted-checklist-store.js";
|
|
28
33
|
import { downloadContextFromBucketeer, restoreContextFromCheckpoint, } from "./context-download.js";
|
|
29
34
|
import { uploadContextToBucketeer } from "./context-upload.js";
|
|
30
35
|
import { AppContextStore } from "./context/app-context.js";
|
|
@@ -35,7 +40,9 @@ import { CsvJudgeStorage } from "./judge/storage/csv-storage.js";
|
|
|
35
40
|
import { KnowledgeManager } from "./knowledge/knowledge-manager.js";
|
|
36
41
|
import { createChaosFetch } from "./llm/chaos-fetch.js";
|
|
37
42
|
import { LLMClient } from "./llm/client.js";
|
|
43
|
+
import { selectContextManagementStrategy, } from "./llm/context-v2/context-management.js";
|
|
38
44
|
import { ContextManagerV2 } from "./llm/context-v2/manager.js";
|
|
45
|
+
import { JsonlEventStore } from "./llm/context-v2/storage/jsonl-event-store.js";
|
|
39
46
|
import { LocalContextStorageV2 } from "./llm/context-v2/storage/local.js";
|
|
40
47
|
import { createLLMProvider } from "./llm/provider.js";
|
|
41
48
|
import { traceLLM } from "./llmobs/helpers.js";
|
|
@@ -62,12 +69,145 @@ import { transitionFrom } from "./state-machine/helpers/transition.js";
|
|
|
62
69
|
import { TemplateRenderer } from "./template-renderer.js";
|
|
63
70
|
import { createJsonStreamParser } from "./util/json-stream-parser.js";
|
|
64
71
|
import { processLLMConfig } from "./util/llm-config-utils.js";
|
|
72
|
+
import { buildModeMessage } from "./util/mode-message.js";
|
|
65
73
|
import { parseJwt } from "./util/parse-jwt.js";
|
|
66
74
|
import { getToolCallSignature } from "./util/tool-signature.js";
|
|
67
75
|
export { AiServiceFeatureFlags } from "./features.js";
|
|
68
76
|
export { stripResolvedFromLockfile } from "./app-interface/npm-registry.js";
|
|
69
77
|
export { SnapshotManager } from "./recording/index.js";
|
|
70
78
|
export { isSdkApiTemplate } from "./sdk-api-templates.js";
|
|
79
|
+
/**
|
|
80
|
+
* APPS-4370. Build the disk-backed `LastKnownRegistryPolicyStore` the
|
|
81
|
+
* production `AiService` wires into its default `NpmRegistryClient`.
|
|
82
|
+
*
|
|
83
|
+
* Exported so unit tests can exercise the exact wiring path the
|
|
84
|
+
* constructor uses without standing up a full AiService — the
|
|
85
|
+
* constructor calls this function inline. Tests pass a tmp directory as
|
|
86
|
+
* `rootDir`; production callers leave the field undefined on
|
|
87
|
+
* `AiServiceConfig` and we fall back to `os.homedir()` at the call
|
|
88
|
+
* site.
|
|
89
|
+
*
|
|
90
|
+
* Kept as a thin pass-through (rather than inlining
|
|
91
|
+
* `createDiskRegistryPolicyStore`) so the production wiring shape is
|
|
92
|
+
* named and reviewable: a single function, a single call site, one
|
|
93
|
+
* place to assert about in coverage.
|
|
94
|
+
*/
|
|
95
|
+
export function buildLastKnownPolicyStore(opts) {
|
|
96
|
+
return createDiskRegistryPolicyStore(opts);
|
|
97
|
+
}
|
|
98
|
+
const MAX_POLICY_GATE_STREAM_ERRORS = 5;
|
|
99
|
+
const MAX_POLICY_GATE_STREAM_ERROR_ARRAY_LENGTH = 10;
|
|
100
|
+
const MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH = 500;
|
|
101
|
+
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH = 3;
|
|
102
|
+
const MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS = 20;
|
|
103
|
+
const POLICY_GATE_STREAM_ERROR_REDACTED_VALUE = "[redacted]";
|
|
104
|
+
const POLICY_GATE_STREAM_ERROR_SECRET_KEY_PATTERN = /(access[-_]?key|api[-_]?key|authorization|connection[-_]?string|credential|dsn|password|private[-_]?key|secret|token)/i;
|
|
105
|
+
const POLICY_GATE_STREAM_ERROR_SECRET_VALUE_PATTERN_SOURCES = [
|
|
106
|
+
String.raw `\bbearer\s+[a-z0-9._~+/=-]+`,
|
|
107
|
+
String.raw `\bhttps?:\/\/[^@\s/:]+:[^@\s]+@\S+`,
|
|
108
|
+
String.raw `\b(?:mongodb(?:\+srv)?|mysql|postgres(?:ql)?|snowflake):\/\/\S+`,
|
|
109
|
+
String.raw `\bA[SK]IA[0-9A-Z]{16}\b`,
|
|
110
|
+
String.raw `\bgh[pousr]_[a-z0-9_]{20,}\b`,
|
|
111
|
+
String.raw `\bsk-[a-z0-9_-]{8,}\b`,
|
|
112
|
+
String.raw `\bxox[baprs]-[a-z0-9-]{10,}\b`,
|
|
113
|
+
];
|
|
114
|
+
function truncatePolicyGateStreamErrorField(value) {
|
|
115
|
+
if (value.length <= MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH) {
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
return `${value.slice(0, MAX_POLICY_GATE_STREAM_ERROR_FIELD_LENGTH)}...`;
|
|
119
|
+
}
|
|
120
|
+
function sanitizePolicyGateStreamErrorString(value) {
|
|
121
|
+
const redacted = POLICY_GATE_STREAM_ERROR_SECRET_VALUE_PATTERN_SOURCES.reduce((current, patternSource) => current.replace(new RegExp(patternSource, "gi"), POLICY_GATE_STREAM_ERROR_REDACTED_VALUE), value);
|
|
122
|
+
return truncatePolicyGateStreamErrorField(redacted);
|
|
123
|
+
}
|
|
124
|
+
function isUnhelpfulPolicyGateStreamError(serialized) {
|
|
125
|
+
return (Object.keys(serialized).length === 1 &&
|
|
126
|
+
serialized.message === "[object Object]");
|
|
127
|
+
}
|
|
128
|
+
function serializePolicyGateStreamError(error, streamPart) {
|
|
129
|
+
const attachStreamPartIfUseful = (serialized) => {
|
|
130
|
+
if (isUnhelpfulPolicyGateStreamError(serialized) &&
|
|
131
|
+
streamPart &&
|
|
132
|
+
typeof streamPart === "object") {
|
|
133
|
+
return {
|
|
134
|
+
...serialized,
|
|
135
|
+
streamPart: serializePolicyGateStreamErrorRecord(streamPart, 0),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return serialized;
|
|
139
|
+
};
|
|
140
|
+
if (error instanceof Error) {
|
|
141
|
+
const serialized = {
|
|
142
|
+
message: sanitizePolicyGateStreamErrorString(error.message),
|
|
143
|
+
name: error.name,
|
|
144
|
+
};
|
|
145
|
+
const extraFields = serializePolicyGateStreamErrorRecord(error, 0);
|
|
146
|
+
return attachStreamPartIfUseful({
|
|
147
|
+
...extraFields,
|
|
148
|
+
...serialized,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (error && typeof error === "object") {
|
|
152
|
+
const serialized = serializePolicyGateStreamErrorRecord(error, 0);
|
|
153
|
+
if (Object.keys(serialized).length > 0) {
|
|
154
|
+
return attachStreamPartIfUseful(serialized);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return attachStreamPartIfUseful({
|
|
158
|
+
message: truncatePolicyGateStreamErrorField(String(error)),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function serializePolicyGateStreamErrorValue(value, depth) {
|
|
162
|
+
if (typeof value === "string") {
|
|
163
|
+
return sanitizePolicyGateStreamErrorString(value);
|
|
164
|
+
}
|
|
165
|
+
if (typeof value === "number" ||
|
|
166
|
+
typeof value === "boolean" ||
|
|
167
|
+
value === null) {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
if (value instanceof Error) {
|
|
171
|
+
return {
|
|
172
|
+
...serializePolicyGateStreamErrorRecord(value, depth + 1),
|
|
173
|
+
message: sanitizePolicyGateStreamErrorString(value.message),
|
|
174
|
+
name: value.name,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
if (depth >= MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH) {
|
|
179
|
+
return `[array:${value.length}]`;
|
|
180
|
+
}
|
|
181
|
+
return value
|
|
182
|
+
.slice(0, MAX_POLICY_GATE_STREAM_ERROR_ARRAY_LENGTH)
|
|
183
|
+
.map((item) => serializePolicyGateStreamErrorValue(item, depth + 1));
|
|
184
|
+
}
|
|
185
|
+
if (value && typeof value === "object") {
|
|
186
|
+
if (depth >= MAX_POLICY_GATE_STREAM_ERROR_OBJECT_DEPTH) {
|
|
187
|
+
return "[object]";
|
|
188
|
+
}
|
|
189
|
+
return serializePolicyGateStreamErrorRecord(value, depth + 1);
|
|
190
|
+
}
|
|
191
|
+
if (value === undefined) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
return sanitizePolicyGateStreamErrorString(String(value));
|
|
195
|
+
}
|
|
196
|
+
function serializePolicyGateStreamErrorRecord(value, depth) {
|
|
197
|
+
const output = {};
|
|
198
|
+
for (const key of Object.keys(value).slice(0, MAX_POLICY_GATE_STREAM_ERROR_OBJECT_KEYS)) {
|
|
199
|
+
if (POLICY_GATE_STREAM_ERROR_SECRET_KEY_PATTERN.test(key)) {
|
|
200
|
+
output[key] = POLICY_GATE_STREAM_ERROR_REDACTED_VALUE;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const fieldValue = value[key];
|
|
204
|
+
const serializedValue = serializePolicyGateStreamErrorValue(fieldValue, depth);
|
|
205
|
+
if (serializedValue !== undefined) {
|
|
206
|
+
output[key] = serializedValue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return output;
|
|
210
|
+
}
|
|
71
211
|
// Helper functions for console log and runtime error buffer management
|
|
72
212
|
export function pruneByTTL(items, ttlMs) {
|
|
73
213
|
const now = Date.now();
|
|
@@ -89,6 +229,29 @@ export function estimateSize(obj) {
|
|
|
89
229
|
return 0;
|
|
90
230
|
}
|
|
91
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* How long the npm-registry client's `getJwt` will block waiting for the
|
|
234
|
+
* first `tokenUpdated` event when no JWT is yet primed. Kept conservative —
|
|
235
|
+
* a cold prefetch that misses the window degrades to last-known-good or
|
|
236
|
+
* not-configured rather than crashing the install path.
|
|
237
|
+
*/
|
|
238
|
+
const NPM_REGISTRY_JWT_WAIT_MS = 5_000;
|
|
239
|
+
/**
|
|
240
|
+
* How long the npm-registry client will wait for a fresh `tokenUpdated`
|
|
241
|
+
* event between an HTTP 401 and its retry.
|
|
242
|
+
*
|
|
243
|
+
* Two failure modes for the refresh:
|
|
244
|
+
* - `waitForTokenUpdate` times out (push-refresh channel hung): the
|
|
245
|
+
* adapter throws, the client treats it as a refresh-channel failure
|
|
246
|
+
* and routes through `serveStaleOrUnconfigured` rather than retrying
|
|
247
|
+
* with the stale JWT — the install path gets last-known-good (or
|
|
248
|
+
* `not-configured`).
|
|
249
|
+
* - No `tokenManager` is wired (SaaS without push refresh, some test
|
|
250
|
+
* fixtures): the adapter resolves immediately on the next microtask;
|
|
251
|
+
* the retry runs with the existing JWT, and a second 401 hard-throws
|
|
252
|
+
* via the second-401 branch in `NpmRegistryClient.fetchWithRefresh`.
|
|
253
|
+
*/
|
|
254
|
+
const NPM_REGISTRY_REFRESH_WAIT_MS = 5_000;
|
|
92
255
|
export function truncateString(str, maxBytes) {
|
|
93
256
|
// Rough approximation: assume 2 bytes per character
|
|
94
257
|
const maxChars = Math.floor(maxBytes / 2);
|
|
@@ -148,6 +311,39 @@ export class AiService extends TracedEventEmitter {
|
|
|
148
311
|
entityPermissionStore = new SessionEntityPermissionStore();
|
|
149
312
|
_onGenerationCompleteCallback;
|
|
150
313
|
tokenManagerJwt;
|
|
314
|
+
/**
|
|
315
|
+
* Listener body for `tokenManager.on("tokenUpdated", ...)`. Defined as a
|
|
316
|
+
* prototype method (not a class field arrow) so tests can exercise the
|
|
317
|
+
* real implementation via `AiService.prototype.handleTokenUpdated`; the
|
|
318
|
+
* constructor binds `this` explicitly when registering with the emitter.
|
|
319
|
+
* Wrapped in try/catch because EventEmitter re-throws synchronous
|
|
320
|
+
* listener exceptions — a logger transport disconnect during JWT
|
|
321
|
+
* rotation would otherwise crash the process. `console.error` is the
|
|
322
|
+
* fallback because it cannot itself trigger the emitter chain.
|
|
323
|
+
*/
|
|
324
|
+
handleTokenUpdated(event) {
|
|
325
|
+
try {
|
|
326
|
+
this.tokenManagerJwt = event.token;
|
|
327
|
+
this.getLogger().info(`[ai-service] token updated via tokenManager`);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
console.error("[ai-service] tokenUpdated listener threw", err);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
npmRegistryClient;
|
|
334
|
+
/**
|
|
335
|
+
* Long-lived `NpmPackageLookup` reused across every `buildTools()` call.
|
|
336
|
+
* Constructed once in the AiService constructor and passed into the
|
|
337
|
+
* per-turn tool factory via `ClarkStateHandlerParams`. The factory used
|
|
338
|
+
* to `new NpmPackageLookup(...)` on every LLM generation turn, which
|
|
339
|
+
* leaked the `superblocks.npm.lookup.cache_size` `ObservableGauge`
|
|
340
|
+
* callback (the OTel API keeps a strong reference to every registered
|
|
341
|
+
* callback) and produced multiple same-instrument/same-attribute
|
|
342
|
+
* observations per collection cycle. Sharing one instance lifecycle-
|
|
343
|
+
* bound to the AiService ties the gauge to the process lifetime — same
|
|
344
|
+
* pattern as `npmRegistryClient` / `templateRenderer` / `appShell`.
|
|
345
|
+
*/
|
|
346
|
+
npmPackageLookup;
|
|
151
347
|
viteServer;
|
|
152
348
|
mcpServerManager;
|
|
153
349
|
recordingManager;
|
|
@@ -163,6 +359,21 @@ export class AiService extends TracedEventEmitter {
|
|
|
163
359
|
get clarkState() {
|
|
164
360
|
return this.clark.state;
|
|
165
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Exposes the shared `NpmRegistryClient` so external SDK dev-server startup
|
|
364
|
+
* paths in `cli-replacement/dev.mts` — the home `~/.npmrc` writer
|
|
365
|
+
* (APPS-4231) and the startup `installPackages` call that honors the
|
|
366
|
+
* per-org `allow_install_scripts` policy (APPS-4251) — can share the
|
|
367
|
+
* in-memory cache, 401-refresh, and last-known-policy state already used
|
|
368
|
+
* by AppShell and TemplateRenderer. A separate client would fragment that
|
|
369
|
+
* state and double the network roundtrips against the registry-config
|
|
370
|
+
* endpoint. The client is always constructed (LD flag and empty-list
|
|
371
|
+
* cases short-circuit internally) so callers can treat this as
|
|
372
|
+
* non-optional once the AiService has been initialized.
|
|
373
|
+
*/
|
|
374
|
+
getNpmRegistryClient() {
|
|
375
|
+
return this.npmRegistryClient;
|
|
376
|
+
}
|
|
166
377
|
/**
|
|
167
378
|
* Injects git and sync services for the git tool (called by the plugin when native git is available).
|
|
168
379
|
*/
|
|
@@ -292,6 +503,71 @@ export class AiService extends TracedEventEmitter {
|
|
|
292
503
|
// Keep this opt-out alongside the injected model factory.
|
|
293
504
|
useSuperblocksAuthorizationHeader: config.providerSettings?.useSuperblocksAuthorizationHeader,
|
|
294
505
|
};
|
|
506
|
+
// Seed `tokenManagerJwt` synchronously from the manager's current
|
|
507
|
+
// value before any consumer that calls `getJwt()` is constructed. The
|
|
508
|
+
// `tokenUpdated` listener below covers FUTURE refreshes; this read
|
|
509
|
+
// covers the cold-start case where the CLI (or any earlier code) has
|
|
510
|
+
// already called `tokenManager.updateToken(...)` before this AiService
|
|
511
|
+
// was constructed. Without it, the prior emission is lost — Node
|
|
512
|
+
// `EventEmitter` has no replay — and `NpmRegistryClient.getJwt()`
|
|
513
|
+
// would time out via `waitForJwt(...)`, downgrading `syncHomeNpmrc`
|
|
514
|
+
// to `unreachable` and leaving `~/.npmrc` unwritten on cold boot.
|
|
515
|
+
this.tokenManagerJwt = this.config.tokenManager?.getCurrentToken();
|
|
516
|
+
// Resolve the npm registry client once and reuse across TemplateRenderer
|
|
517
|
+
// and AppShell so both install paths share the same in-memory cache and
|
|
518
|
+
// 401-refresh state. Explicit injection wins (tests); otherwise we
|
|
519
|
+
// construct a default that reads the LD flag from `config.features` and
|
|
520
|
+
// routes JWT through the AiService's own `getJwt()` (which prefers the
|
|
521
|
+
// tokenManager token and falls back to clark.context.jwt). Empty
|
|
522
|
+
// tokens are refused upstream of `attempt()` so the server doesn't
|
|
523
|
+
// see `Bearer ` and reply 400 (which the client would now treat as a
|
|
524
|
+
// hard fail rather than a transient outage).
|
|
525
|
+
//
|
|
526
|
+
// APPS-4370. Wire the disk-backed `LastKnownRegistryPolicyStore` here
|
|
527
|
+
// so the production client carries `everConfigured` end-to-end. Without
|
|
528
|
+
// this the unreachable short-circuit in AppShell is inert: the marker
|
|
529
|
+
// writer is fire-and-forget and `hasEverBeenConfigured()` always
|
|
530
|
+
// resolves false when no store is wired. `rootDir` is `os.homedir()`
|
|
531
|
+
// for the same reason the home-npmrc writer roots at
|
|
532
|
+
// `~/.superblocks/...`: the marker lives next to the existing
|
|
533
|
+
// CLI-managed state and survives application-directory recycles.
|
|
534
|
+
const npmRegistryClient = config.npmRegistryClient ??
|
|
535
|
+
new NpmRegistryClient({
|
|
536
|
+
isFlagEnabled: () => config.features.npmRegistryEnabled === true,
|
|
537
|
+
// The prefetch can fire before tokenManager has primed
|
|
538
|
+
// `tokenManagerJwt`. Block (with a short timeout) on the next
|
|
539
|
+
// `tokenUpdated` event rather than throwing immediately, so the
|
|
540
|
+
// first install in the shared template dir actually gets a
|
|
541
|
+
// `.npmrc` instead of silently downgrading to "not configured".
|
|
542
|
+
getJwt: () => this.waitForJwt(NPM_REGISTRY_JWT_WAIT_MS),
|
|
543
|
+
// Push-based refresh hook: on 401, wait briefly for the next
|
|
544
|
+
// `tokenUpdated` event before re-reading the JWT. Without this,
|
|
545
|
+
// a 401 retry fires immediately with the same expired token and
|
|
546
|
+
// throws `re-authenticated and still received HTTP 401` for any
|
|
547
|
+
// user whose JWT expired mid-install.
|
|
548
|
+
refreshJwt: () => this.waitForTokenUpdate(NPM_REGISTRY_REFRESH_WAIT_MS),
|
|
549
|
+
organizationId: config.organizationId,
|
|
550
|
+
baseUrl: config.superblocksBaseUrl,
|
|
551
|
+
lastKnownPolicyStore: buildLastKnownPolicyStore({
|
|
552
|
+
organizationId: config.organizationId,
|
|
553
|
+
rootDir: config.policyStoreRootDir ?? os.homedir(),
|
|
554
|
+
}),
|
|
555
|
+
});
|
|
556
|
+
this.npmRegistryClient = npmRegistryClient;
|
|
557
|
+
// One process-lifetime `NpmPackageLookup` shared across every
|
|
558
|
+
// `buildTools()` call. The `build_lookupNpmPackage` tool factory used
|
|
559
|
+
// to construct a fresh instance per LLM turn; each construction
|
|
560
|
+
// registered an `ObservableGauge` callback for
|
|
561
|
+
// `superblocks.npm.lookup.cache_size` that the OTel API keeps strongly
|
|
562
|
+
// referenced for the lifetime of the meter. `ToolRegistry.clear()`
|
|
563
|
+
// does not dispose tool resources, so every cleared instance's callback
|
|
564
|
+
// (and the cache it closed over) stuck around for the process lifetime
|
|
565
|
+
// and emitted multiple same-instrument/same-attribute observations per
|
|
566
|
+
// collection cycle. Owning the lookup here — same pattern as
|
|
567
|
+
// `npmRegistryClient` / `templateRenderer` / `appShell` — means the
|
|
568
|
+
// gauge wires up exactly once and the 60s TTL cache stays warm across
|
|
569
|
+
// turns instead of being thrown away.
|
|
570
|
+
this.npmPackageLookup = new NpmPackageLookup({ client: npmRegistryClient });
|
|
295
571
|
this.templateRenderer =
|
|
296
572
|
config.templateRenderer ??
|
|
297
573
|
new TemplateRenderer({
|
|
@@ -301,6 +577,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
301
577
|
// process.cwd() — process.cwd() is fragile in CI matrix shards and
|
|
302
578
|
// monorepo sub-invocations. See PR #19282 (F3).
|
|
303
579
|
detectionCwd: config.appRootDirPath,
|
|
580
|
+
npmRegistryClient,
|
|
304
581
|
});
|
|
305
582
|
// Create KnowledgeManager before AppShell so it can be passed to VirtualFileSystem
|
|
306
583
|
this.knowledgeManager = new KnowledgeManager();
|
|
@@ -309,6 +586,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
309
586
|
fsOperationQueue: config.fsOperationQueue,
|
|
310
587
|
draftInterface: config.draftInterface,
|
|
311
588
|
templateRenderer: this.templateRenderer,
|
|
589
|
+
npmRegistryClient,
|
|
312
590
|
virtualFileSystem: createDefaultVirtualFileSystem({
|
|
313
591
|
useSystemSkills: config.features.useSystemSkills,
|
|
314
592
|
knowledgeManager: this.knowledgeManager,
|
|
@@ -344,15 +622,13 @@ export class AiService extends TracedEventEmitter {
|
|
|
344
622
|
logger: config.logger,
|
|
345
623
|
});
|
|
346
624
|
if (this.config.tokenManager) {
|
|
347
|
-
this.config.tokenManager.on("tokenUpdated", (
|
|
348
|
-
this.tokenManagerJwt = event.token;
|
|
349
|
-
this.getLogger().info(`[ai-service] token updated via tokenManager`);
|
|
350
|
-
});
|
|
625
|
+
this.config.tokenManager.on("tokenUpdated", this.handleTokenUpdated.bind(this));
|
|
351
626
|
}
|
|
352
627
|
this.clarkProfiler = new ClarkProfiler(this.sessionId);
|
|
353
628
|
this.clarkProfiler.initialize(this.appShell);
|
|
354
629
|
this.contextManagerV2 = new ContextManagerV2({
|
|
355
630
|
storage: new LocalContextStorageV2(config.appRootDirPath),
|
|
631
|
+
eventStore: new JsonlEventStore(config.appRootDirPath),
|
|
356
632
|
rootDir: config.appRootDirPath,
|
|
357
633
|
getModeState: () => {
|
|
358
634
|
const mode = this.clark?.context?.currentMode;
|
|
@@ -363,7 +639,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
363
639
|
planApproved: this.clark?.context?.planContext?.approved,
|
|
364
640
|
};
|
|
365
641
|
},
|
|
366
|
-
remoteRestoreFallback: async () => {
|
|
642
|
+
remoteRestoreFallback: async (id) => {
|
|
367
643
|
const jwt = this.getJwt();
|
|
368
644
|
// Prefer the live sync service commitId (set after uploads), but on
|
|
369
645
|
// fresh pods downloadDirectory doesn't set it. Fall back to the
|
|
@@ -382,6 +658,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
382
658
|
superblocksBaseUrl: config.superblocksBaseUrl,
|
|
383
659
|
logger: this.getLogger(),
|
|
384
660
|
jwt,
|
|
661
|
+
contextId: id,
|
|
662
|
+
appRootDirPath: config.appRootDirPath,
|
|
385
663
|
});
|
|
386
664
|
},
|
|
387
665
|
});
|
|
@@ -489,6 +767,18 @@ export class AiService extends TracedEventEmitter {
|
|
|
489
767
|
if (!checklist) {
|
|
490
768
|
return;
|
|
491
769
|
}
|
|
770
|
+
// Reset stale in_progress items to pending. After a dev-server restart,
|
|
771
|
+
// items left in_progress are not truly running — resetting them allows
|
|
772
|
+
// the UI polling trigger and backend handler to re-dispatch Clark.
|
|
773
|
+
const staleInProgressItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) && item.status === "in_progress");
|
|
774
|
+
for (const item of staleInProgressItems) {
|
|
775
|
+
await updateChecklistItem({
|
|
776
|
+
appRootDirPath: this.appRootDirPath,
|
|
777
|
+
itemId: item.id,
|
|
778
|
+
status: "pending",
|
|
779
|
+
});
|
|
780
|
+
this.getLogger().info(`[ai-service] Reset stale in_progress item "${item.id}" to pending after restart`);
|
|
781
|
+
}
|
|
492
782
|
const hasPendingSeedApi = checklist.items.some((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
|
|
493
783
|
(item.status === "pending" || item.status === "in_progress"));
|
|
494
784
|
if (hasPendingSeedApi) {
|
|
@@ -553,6 +843,20 @@ export class AiService extends TracedEventEmitter {
|
|
|
553
843
|
logger: this.getLogger(),
|
|
554
844
|
templateRenderer: this.templateRenderer,
|
|
555
845
|
appShell: this.appShell,
|
|
846
|
+
// AiServiceConfig.npmRegistryClient is optional (injectable test
|
|
847
|
+
// seam); the SDK's `dev()` never passes it in production, so
|
|
848
|
+
// spreading `...this.config` above leaves it `undefined` and any
|
|
849
|
+
// state-handler / tool that reads `services.npmRegistryClient`
|
|
850
|
+
// sees nothing. The `build_lookupNpmPackage` tool's `!this.client`
|
|
851
|
+
// branch silently downgraded to public npm because of this gap.
|
|
852
|
+
// Carry the AiService's resolved instance through explicitly,
|
|
853
|
+
// same pattern as `appShell` / `templateRenderer` above.
|
|
854
|
+
npmRegistryClient: this.npmRegistryClient,
|
|
855
|
+
// Pass the AiService's long-lived lookup so `build_lookupNpmPackage`
|
|
856
|
+
// reuses one instance across LLM turns (gauge registered once,
|
|
857
|
+
// cache warm). See the `npmPackageLookup` field comment for why
|
|
858
|
+
// per-turn instantiation in the tool factory leaked OTel callbacks.
|
|
859
|
+
npmPackageLookup: this.npmPackageLookup,
|
|
556
860
|
signals: this,
|
|
557
861
|
fileSystemInterface: this.fileSystemInterface,
|
|
558
862
|
integrationStore: this.integrationStore,
|
|
@@ -627,6 +931,79 @@ export class AiService extends TracedEventEmitter {
|
|
|
627
931
|
// fallback to clark context if no token manager is provided
|
|
628
932
|
return this.clark?.context?.jwt;
|
|
629
933
|
}
|
|
934
|
+
buildContextManagementForCurrentClark(llmConfig) {
|
|
935
|
+
const modeInstructions = this.clark.context.currentMode
|
|
936
|
+
? `Preserve the following mode context in your summary: ${buildModeMessage(this.clark.context.currentMode, this.clark.context.planContext?.approved)}`
|
|
937
|
+
: undefined;
|
|
938
|
+
return selectContextManagementStrategy({
|
|
939
|
+
serverContextManagementEnabled: llmConfig?.serverContextManagement?.enabled,
|
|
940
|
+
modeInstructions,
|
|
941
|
+
thresholds: llmConfig?.serverContextManagement,
|
|
942
|
+
summarizationModel: () => createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getJwt(), llmConfig, () => ({
|
|
943
|
+
prompt_id: this.clark.context.promptId,
|
|
944
|
+
chatmessage_id: this.clark.context.chatmessageId,
|
|
945
|
+
application_id: this.config.applicationId,
|
|
946
|
+
session_id: this.sessionId,
|
|
947
|
+
mode: this.clark.context.currentMode === AiMode.PLAN ? "plan" : "build",
|
|
948
|
+
plan_size: this.clark.context.planSize,
|
|
949
|
+
})).modelForTask("summarizeMessages"),
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Resolve a JWT for the npm-registry fetch, blocking briefly on the
|
|
954
|
+
* first `tokenUpdated` event when no JWT is yet primed. The
|
|
955
|
+
* `TemplateRenderer` prefetch fires during AiService construction —
|
|
956
|
+
* before the `tokenManager` subscription has emitted — so a synchronous
|
|
957
|
+
* `getJwt()` would observe `undefined` and silently downgrade the
|
|
958
|
+
* private-registry install to public npm. Blocking here is the
|
|
959
|
+
* narrowest fix: callers that already have a primed JWT take the fast
|
|
960
|
+
* path and never await.
|
|
961
|
+
*/
|
|
962
|
+
async waitForJwt(timeoutMs) {
|
|
963
|
+
const immediate = this.getJwt();
|
|
964
|
+
if (immediate) {
|
|
965
|
+
return immediate;
|
|
966
|
+
}
|
|
967
|
+
if (!this.config.tokenManager) {
|
|
968
|
+
throw new Error("[npm-registry] no JWT available and no tokenManager wired to wait for one");
|
|
969
|
+
}
|
|
970
|
+
await this.waitForTokenUpdate(timeoutMs);
|
|
971
|
+
const after = this.getJwt();
|
|
972
|
+
if (!after) {
|
|
973
|
+
throw new Error("[npm-registry] no JWT available after waiting for tokenManager update");
|
|
974
|
+
}
|
|
975
|
+
return after;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Resolve when the next `tokenUpdated` event lands on `tokenManager`,
|
|
979
|
+
* or reject when `timeoutMs` elapses without one. When no
|
|
980
|
+
* `tokenManager` is wired (test fixtures, SaaS paths without push
|
|
981
|
+
* refresh), resolves immediately on the next microtask.
|
|
982
|
+
*
|
|
983
|
+
* Resolution only signals "a wait completed" — never that a JWT actually
|
|
984
|
+
* changed. The constructor's `tokenUpdated` listener (above) is the only
|
|
985
|
+
* code that mutates `tokenManagerJwt`. Callers (`waitForJwt`, the
|
|
986
|
+
* npm-registry `refreshJwt` adapter) must re-read the JWT after this
|
|
987
|
+
* resolves and treat an unchanged value the same as a refresh that
|
|
988
|
+
* never happened.
|
|
989
|
+
*/
|
|
990
|
+
waitForTokenUpdate(timeoutMs) {
|
|
991
|
+
const tokenManager = this.config.tokenManager;
|
|
992
|
+
if (!tokenManager) {
|
|
993
|
+
return Promise.resolve();
|
|
994
|
+
}
|
|
995
|
+
return new Promise((resolve, reject) => {
|
|
996
|
+
const onUpdate = () => {
|
|
997
|
+
clearTimeout(timer);
|
|
998
|
+
resolve();
|
|
999
|
+
};
|
|
1000
|
+
const timer = setTimeout(() => {
|
|
1001
|
+
tokenManager.off("tokenUpdated", onUpdate);
|
|
1002
|
+
reject(new Error(`[npm-registry] timed out after ${timeoutMs}ms waiting for tokenManager update`));
|
|
1003
|
+
}, timeoutMs);
|
|
1004
|
+
tokenManager.once("tokenUpdated", onUpdate);
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
630
1007
|
createClarkTagProviderFactory() {
|
|
631
1008
|
return (fsm) => {
|
|
632
1009
|
const clark = fsm;
|
|
@@ -652,11 +1029,19 @@ export class AiService extends TracedEventEmitter {
|
|
|
652
1029
|
}
|
|
653
1030
|
return (await response.json());
|
|
654
1031
|
}
|
|
655
|
-
|
|
1032
|
+
/**
|
|
1033
|
+
* Non-throwing AI quota check. Returns `{ allowed: true }` when the org is
|
|
1034
|
+
* within its limit, and also when the check itself can't run (no JWT, or the
|
|
1035
|
+
* quota endpoint errors) — callers fail open so a flaky quota service never
|
|
1036
|
+
* blocks legitimate work. When the org is over limit, returns the reason and
|
|
1037
|
+
* a user-facing message instead of throwing, so callers that don't surface
|
|
1038
|
+
* exceptions (e.g. the background migration turn) can react.
|
|
1039
|
+
*/
|
|
1040
|
+
async checkAiQuota(jwt) {
|
|
656
1041
|
const token = jwt ?? this.getJwt();
|
|
657
1042
|
if (!token) {
|
|
658
1043
|
this.getLogger().warn("[ai-service] No JWT available for quota check, skipping");
|
|
659
|
-
return;
|
|
1044
|
+
return { allowed: true };
|
|
660
1045
|
}
|
|
661
1046
|
try {
|
|
662
1047
|
const quota = await this.callServer({
|
|
@@ -670,14 +1055,19 @@ export class AiService extends TracedEventEmitter {
|
|
|
670
1055
|
: reason === "no_seat_assigned"
|
|
671
1056
|
? "You don't have an AI Builder license assigned. Ask your admin to assign a license."
|
|
672
1057
|
: "Your organization has reached its AI usage limit.";
|
|
673
|
-
|
|
1058
|
+
return { allowed: false, reason, message };
|
|
674
1059
|
}
|
|
1060
|
+
return { allowed: true };
|
|
675
1061
|
}
|
|
676
1062
|
catch (error) {
|
|
677
|
-
if (error instanceof Error && error.message.includes("[reason:")) {
|
|
678
|
-
throw error;
|
|
679
|
-
}
|
|
680
1063
|
this.getLogger().warn("[ai-service] Quota check failed, allowing request", getErrorMeta(error));
|
|
1064
|
+
return { allowed: true };
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
async ensureAiQuotaAvailable(jwt) {
|
|
1068
|
+
const quota = await this.checkAiQuota(jwt);
|
|
1069
|
+
if (!quota.allowed) {
|
|
1070
|
+
throw new Error(`${quota.message} [reason:${quota.reason}]`);
|
|
681
1071
|
}
|
|
682
1072
|
}
|
|
683
1073
|
/**
|
|
@@ -712,16 +1102,35 @@ export class AiService extends TracedEventEmitter {
|
|
|
712
1102
|
if (this.clark.context.pendingToolPermissionRequest) {
|
|
713
1103
|
return { dispatched: false, reason: "pending-tool-permission" };
|
|
714
1104
|
}
|
|
1105
|
+
if (this.clark.state === ClarkStateNames.AwaitingUser) {
|
|
1106
|
+
if (this.clark.context.activeInteractiveMessage ||
|
|
1107
|
+
this.clark.context.deferredInteractiveMessage) {
|
|
1108
|
+
return { dispatched: false, reason: "awaiting-interactive-response" };
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
715
1111
|
const checklist = await readPersistedChecklist(this.appRootDirPath);
|
|
716
1112
|
if (!checklist) {
|
|
717
1113
|
this.getLogger().warn("[ai-service] Migration checklist not found or unreadable; skipping translation turn");
|
|
718
1114
|
return { dispatched: false, reason: "checklist-unavailable" };
|
|
719
1115
|
}
|
|
720
|
-
const pendingSeedApiItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
|
|
1116
|
+
const pendingSeedApiItems = checklist.items.filter((item) => API_MIGRATION_ORIGINS.has(item.origin) &&
|
|
1117
|
+
(item.status === "pending" || item.status === "in_progress"));
|
|
721
1118
|
const pendingSeedApiCount = pendingSeedApiItems.length;
|
|
722
1119
|
if (pendingSeedApiCount === 0) {
|
|
723
1120
|
return { dispatched: false, reason: "no-pending-items" };
|
|
724
1121
|
}
|
|
1122
|
+
// Pre-flight quota gate. The migration turn drives LLM inference (which
|
|
1123
|
+
// doesn't enforce quota) followed by tool calls that hit quota-protected
|
|
1124
|
+
// endpoints (e.g. attachment uploads via /api/v1/files). Without this gate
|
|
1125
|
+
// an over-limit org burns more credits on the migration turn, then fails
|
|
1126
|
+
// mid-turn with a generic "inference error" when a downstream tool call
|
|
1127
|
+
// 429s. Fail fast with a clear reason before consuming any credits, the
|
|
1128
|
+
// same pre-flight check handleAiGenerate already runs.
|
|
1129
|
+
const quota = await this.checkAiQuota(request.jwt);
|
|
1130
|
+
if (!quota.allowed) {
|
|
1131
|
+
this.getLogger().warn("[ai-service] Skipping v2→v3 migration turn — AI quota exceeded", { reason: quota.reason });
|
|
1132
|
+
return { dispatched: false, reason: "quota-exceeded" };
|
|
1133
|
+
}
|
|
725
1134
|
this.migrationTurnTriggered = true;
|
|
726
1135
|
this.appShell.setApiHandlerEnabled(false);
|
|
727
1136
|
this.getLogger().info(`[ai-service] Dispatching internal v2→v3 migration turn for ${pendingSeedApiCount} API(s) (API VFS projection disabled, retryMode=${request.retryMode ?? "none"})`);
|
|
@@ -764,7 +1173,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
764
1173
|
getMigrationSafeLlmConfig(llmConfig) {
|
|
765
1174
|
const result = filterDisabledToolsForMigration(llmConfig);
|
|
766
1175
|
if (result.removedTools.length > 0) {
|
|
767
|
-
this.getLogger().warn("[ai-service] Migration turn overrides operator-disabled tools required for migration: install/checklist/askMultiChoice", { removedTools: result.removedTools });
|
|
1176
|
+
this.getLogger().warn("[ai-service] Migration turn overrides operator-disabled tools required for migration: install/checklist/askMultiChoice/build_navigatePreview/build_captureScreenshot", { removedTools: result.removedTools });
|
|
768
1177
|
}
|
|
769
1178
|
return result.config;
|
|
770
1179
|
}
|
|
@@ -814,6 +1223,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
814
1223
|
},
|
|
815
1224
|
llmConfig: request.llmConfig,
|
|
816
1225
|
partialResponse: undefined,
|
|
1226
|
+
userPreferences: request.userPreferences,
|
|
817
1227
|
};
|
|
818
1228
|
});
|
|
819
1229
|
const transitionTo = transitionFrom(this.clark);
|
|
@@ -1688,12 +2098,13 @@ export class AiService extends TracedEventEmitter {
|
|
|
1688
2098
|
}
|
|
1689
2099
|
catch (error) {
|
|
1690
2100
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1691
|
-
//
|
|
2101
|
+
// Provider-facing context expects tool failures to be encoded as
|
|
2102
|
+
// errored tool results so they still satisfy the preceding tool call.
|
|
1692
2103
|
toolResultMessage = {
|
|
1693
2104
|
role: "tool",
|
|
1694
2105
|
content: [
|
|
1695
2106
|
{
|
|
1696
|
-
type: "tool-
|
|
2107
|
+
type: "tool-result",
|
|
1697
2108
|
toolCallId,
|
|
1698
2109
|
toolName,
|
|
1699
2110
|
output: {
|
|
@@ -1712,9 +2123,7 @@ export class AiService extends TracedEventEmitter {
|
|
|
1712
2123
|
type: "tool-error",
|
|
1713
2124
|
toolCallId,
|
|
1714
2125
|
toolName,
|
|
1715
|
-
error:
|
|
1716
|
-
? errorMessage
|
|
1717
|
-
: JSON.stringify(errorMessage),
|
|
2126
|
+
error: errorMessage,
|
|
1718
2127
|
errorCode,
|
|
1719
2128
|
});
|
|
1720
2129
|
}
|
|
@@ -1749,10 +2158,8 @@ export class AiService extends TracedEventEmitter {
|
|
|
1749
2158
|
};
|
|
1750
2159
|
logger.info(`[ai-service] Tool ${toolName} permission denied, error injected into context`);
|
|
1751
2160
|
}
|
|
1752
|
-
const
|
|
1753
|
-
const
|
|
1754
|
-
const summarizationModel = llmProvider.modelForTask("summarizeMessages");
|
|
1755
|
-
const context = await this.contextManagerV2.getOrCreateContext(contextId, summarizationModel, llmConfig?.contextOptionsV2);
|
|
2161
|
+
const contextManagement = this.buildContextManagementForCurrentClark(llmConfig);
|
|
2162
|
+
const context = await this.contextManagerV2.getOrCreateContext(contextId, llmConfig?.contextOptionsV2, undefined, contextManagement);
|
|
1756
2163
|
const placeholderUsage = {
|
|
1757
2164
|
inputTokens: 0,
|
|
1758
2165
|
outputTokens: 0,
|
|
@@ -2185,6 +2592,184 @@ export class AiService extends TracedEventEmitter {
|
|
|
2185
2592
|
this.tokenManagerJwt = previousJwt;
|
|
2186
2593
|
}
|
|
2187
2594
|
}
|
|
2595
|
+
async handlePolicyGateExecution(request) {
|
|
2596
|
+
const logger = this.getLogger();
|
|
2597
|
+
logger.info(`[ai-service] Policy gate execution requested for app=${request.applicationId}`);
|
|
2598
|
+
const requestLlmConfig = request.llmConfig;
|
|
2599
|
+
const contextLlmConfig = this.clark.context.llmConfig;
|
|
2600
|
+
const llmConfig = requestLlmConfig ?? contextLlmConfig;
|
|
2601
|
+
const llmProvider = createLLMProvider(this.getLLMProviderSettings(llmConfig), () => this.getJwt(), llmConfig, () => ({
|
|
2602
|
+
ai_agent_run_id: request.aiAgentRunId,
|
|
2603
|
+
application_id: request.applicationId,
|
|
2604
|
+
session_id: this.sessionId,
|
|
2605
|
+
trigger_type: "policy_gate",
|
|
2606
|
+
}));
|
|
2607
|
+
const model = llmProvider.modelForTask("generateBroadEdit");
|
|
2608
|
+
const availableIntegrations = await this.integrationStore.availableIntegrations();
|
|
2609
|
+
const integrations = availableIntegrations.map((i) => ({
|
|
2610
|
+
name: i.name,
|
|
2611
|
+
pluginType: i.plugin?.id ?? "unknown",
|
|
2612
|
+
}));
|
|
2613
|
+
const appContext = { integrations };
|
|
2614
|
+
const systemPrompt = composeSecurityAgentPrompt(appContext, request.adminPrompt);
|
|
2615
|
+
const startedAt = Date.now();
|
|
2616
|
+
const toolCallCounts = {};
|
|
2617
|
+
const filePathsRead = new Set();
|
|
2618
|
+
const filePathsListed = new Set();
|
|
2619
|
+
const finishReasons = [];
|
|
2620
|
+
const grepPatterns = [];
|
|
2621
|
+
const grepMatchCounts = [];
|
|
2622
|
+
const grepPatternByToolCallId = new Map();
|
|
2623
|
+
const streamErrors = [];
|
|
2624
|
+
const streamPartTypeCounts = {};
|
|
2625
|
+
let textDeltaChars = 0;
|
|
2626
|
+
const promptHash = createHash("sha256").update(systemPrompt).digest("hex");
|
|
2627
|
+
const modelName = typeof model === "string" ? model : model.modelId;
|
|
2628
|
+
const scanRoot = typeof this.appShell.appRootDirPath === "string"
|
|
2629
|
+
? this.appShell.appRootDirPath
|
|
2630
|
+
: undefined;
|
|
2631
|
+
let scanResult;
|
|
2632
|
+
const buildRuntimeMetadata = (finalFailureReason) => ({
|
|
2633
|
+
diagnosticsVersion: 2,
|
|
2634
|
+
durationMs: Date.now() - startedAt,
|
|
2635
|
+
filePathsListed: Array.from(filePathsListed).slice(0, 100),
|
|
2636
|
+
filePathsRead: Array.from(filePathsRead).slice(0, 100),
|
|
2637
|
+
finalFailureReason,
|
|
2638
|
+
finishReasons,
|
|
2639
|
+
grepMatchCounts,
|
|
2640
|
+
grepPatterns,
|
|
2641
|
+
model: modelName,
|
|
2642
|
+
promptHash,
|
|
2643
|
+
reportToolCalled: Boolean(scanResult),
|
|
2644
|
+
scanComplete: scanResult?.scanComplete === true,
|
|
2645
|
+
scanRoot,
|
|
2646
|
+
streamErrors,
|
|
2647
|
+
streamPartTypeCounts,
|
|
2648
|
+
textDeltaChars,
|
|
2649
|
+
toolCallCounts,
|
|
2650
|
+
});
|
|
2651
|
+
const failPolicyGateExecution = (message) => {
|
|
2652
|
+
const error = new Error(message);
|
|
2653
|
+
error.runtimeMetadataJson = buildRuntimeMetadata(message);
|
|
2654
|
+
throw error;
|
|
2655
|
+
};
|
|
2656
|
+
const scanToolsDeps = {
|
|
2657
|
+
appShell: this.appShell,
|
|
2658
|
+
features: this.config.features ?? {},
|
|
2659
|
+
integrationStore: this.integrationStore,
|
|
2660
|
+
llmProvider: llmProvider,
|
|
2661
|
+
};
|
|
2662
|
+
const tools = await buildSecurityScanTools(this.clark, scanToolsDeps);
|
|
2663
|
+
const result = aiStreamText({
|
|
2664
|
+
messages: [
|
|
2665
|
+
{ content: systemPrompt, role: "system" },
|
|
2666
|
+
{
|
|
2667
|
+
content: "Begin your security review of this application now.",
|
|
2668
|
+
role: "user",
|
|
2669
|
+
},
|
|
2670
|
+
],
|
|
2671
|
+
model,
|
|
2672
|
+
tools,
|
|
2673
|
+
stopWhen: stepCountIs(30),
|
|
2674
|
+
});
|
|
2675
|
+
for await (const part of result.fullStream) {
|
|
2676
|
+
const partType = typeof part.type === "string"
|
|
2677
|
+
? part.type
|
|
2678
|
+
: "unknown";
|
|
2679
|
+
streamPartTypeCounts[partType] =
|
|
2680
|
+
(streamPartTypeCounts[partType] ?? 0) + 1;
|
|
2681
|
+
if (partType === "error") {
|
|
2682
|
+
const streamError = serializePolicyGateStreamError(part.error, part);
|
|
2683
|
+
if (streamErrors.length < MAX_POLICY_GATE_STREAM_ERRORS) {
|
|
2684
|
+
streamErrors.push(streamError);
|
|
2685
|
+
}
|
|
2686
|
+
logger.warn("[ai-service] Policy gate stream error", { streamError });
|
|
2687
|
+
}
|
|
2688
|
+
const textDelta = part.textDelta ??
|
|
2689
|
+
part.text ??
|
|
2690
|
+
part.delta;
|
|
2691
|
+
if (typeof textDelta === "string") {
|
|
2692
|
+
textDeltaChars += textDelta.length;
|
|
2693
|
+
}
|
|
2694
|
+
const finishReason = part.finishReason;
|
|
2695
|
+
if (typeof finishReason === "string") {
|
|
2696
|
+
finishReasons.push(finishReason);
|
|
2697
|
+
}
|
|
2698
|
+
if (part.type === "tool-call") {
|
|
2699
|
+
toolCallCounts[part.toolName] =
|
|
2700
|
+
(toolCallCounts[part.toolName] ?? 0) + 1;
|
|
2701
|
+
const input = part.input ??
|
|
2702
|
+
part.args;
|
|
2703
|
+
if (input && typeof input === "object") {
|
|
2704
|
+
const record = input;
|
|
2705
|
+
if (part.toolName === "build_readFile" &&
|
|
2706
|
+
typeof record.filePath === "string") {
|
|
2707
|
+
filePathsRead.add(record.filePath);
|
|
2708
|
+
}
|
|
2709
|
+
if (part.toolName === "grep" && typeof record.pattern === "string") {
|
|
2710
|
+
grepPatterns.push(record.pattern);
|
|
2711
|
+
const toolCallId = typeof part.toolCallId === "string"
|
|
2712
|
+
? part.toolCallId
|
|
2713
|
+
: `grep-${grepPatterns.length}`;
|
|
2714
|
+
grepPatternByToolCallId.set(toolCallId, record.pattern);
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
if (part.type === "tool-result") {
|
|
2719
|
+
const output = part.output ??
|
|
2720
|
+
part.result;
|
|
2721
|
+
if (output && typeof output === "object") {
|
|
2722
|
+
const record = output;
|
|
2723
|
+
if (part.toolName === "build_listFiles" &&
|
|
2724
|
+
Array.isArray(record.files)) {
|
|
2725
|
+
for (const file of record.files) {
|
|
2726
|
+
if (typeof file === "string") {
|
|
2727
|
+
filePathsListed.add(file);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
if (part.toolName === "grep" && typeof record.matches === "number") {
|
|
2732
|
+
const toolCallId = typeof part.toolCallId === "string"
|
|
2733
|
+
? part.toolCallId
|
|
2734
|
+
: `grep-${grepMatchCounts.length + 1}`;
|
|
2735
|
+
grepMatchCounts.push({
|
|
2736
|
+
matches: record.matches,
|
|
2737
|
+
pattern: grepPatternByToolCallId.get(toolCallId) ??
|
|
2738
|
+
grepPatterns[grepPatterns.length - 1] ??
|
|
2739
|
+
"",
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
if (part.type === "tool-call" && part.toolName === "reportReviewRun") {
|
|
2745
|
+
const input = part.input ??
|
|
2746
|
+
part.args;
|
|
2747
|
+
const parsedInput = securityScanResultSchema.safeParse(input);
|
|
2748
|
+
if (!parsedInput.success) {
|
|
2749
|
+
logger.error("[ai-service] Invalid policy gate reportReviewRun input", getErrorMeta(parsedInput.error));
|
|
2750
|
+
failPolicyGateExecution("Invalid policy gate reportReviewRun input");
|
|
2751
|
+
}
|
|
2752
|
+
scanResult = parsedInput.data;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
if (!scanResult) {
|
|
2756
|
+
logger.warn("[ai-service] Policy gate agent did not call reportReviewRun");
|
|
2757
|
+
failPolicyGateExecution("Policy gate agent did not complete a report");
|
|
2758
|
+
}
|
|
2759
|
+
const completedScanResult = scanResult;
|
|
2760
|
+
if (!completedScanResult.scanComplete) {
|
|
2761
|
+
logger.warn("[ai-service] Policy gate agent reported an incomplete scan", {
|
|
2762
|
+
findingCount: completedScanResult.findings.length,
|
|
2763
|
+
scannedFiles: completedScanResult.scannedFiles,
|
|
2764
|
+
});
|
|
2765
|
+
failPolicyGateExecution("Policy gate agent did not complete a report");
|
|
2766
|
+
}
|
|
2767
|
+
logger.info(`[ai-service] Policy gate execution complete: ${completedScanResult.findings.length} findings`);
|
|
2768
|
+
return {
|
|
2769
|
+
...completedScanResult,
|
|
2770
|
+
runtimeMetadataJson: buildRuntimeMetadata(),
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2188
2773
|
handleUserChange(change) {
|
|
2189
2774
|
this.getLogger().info(`[ai-service] User changed: ${change.type}`, change);
|
|
2190
2775
|
this.clark.updateContext((context) => ({
|