pi-ui-extend 1.0.41 → 1.0.45
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/README.md +50 -13
- package/dist/app/app.d.ts +13 -0
- package/dist/app/app.js +158 -8
- package/dist/app/cli/install.js +2 -2
- package/dist/app/commands/command-controller.d.ts +4 -0
- package/dist/app/commands/command-controller.js +11 -0
- package/dist/app/commands/command-git-actions.d.ts +25 -0
- package/dist/app/commands/command-git-actions.js +381 -0
- package/dist/app/commands/command-host.d.ts +4 -0
- package/dist/app/commands/command-host.js +5 -2
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +57 -35
- package/dist/app/commands/command-navigation-actions.d.ts +1 -0
- package/dist/app/commands/command-navigation-actions.js +16 -3
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +15 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +70 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- package/dist/app/input/autocomplete-controller.js +61 -34
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/input/input-controller.d.ts +2 -1
- package/dist/app/input/input-controller.js +7 -2
- package/dist/app/input/prompt-enhancer-controller.js +53 -35
- package/dist/app/input/voice-controller.d.ts +41 -45
- package/dist/app/input/voice-controller.js +351 -419
- package/dist/app/model/model-usage-controller.js +17 -7
- package/dist/app/model/model-usage-status.d.ts +4 -1
- package/dist/app/model/model-usage-status.js +270 -46
- package/dist/app/popup/menu-items-controller.d.ts +13 -3
- package/dist/app/popup/menu-items-controller.js +37 -21
- package/dist/app/popup/popup-action-controller.d.ts +12 -2
- package/dist/app/popup/popup-action-controller.js +77 -24
- package/dist/app/popup/popup-menu-controller.d.ts +36 -14
- package/dist/app/popup/popup-menu-controller.js +239 -69
- package/dist/app/rendering/dcp-stats.d.ts +9 -4
- package/dist/app/rendering/dcp-stats.js +40 -425
- package/dist/app/rendering/editor-panels.js +10 -4
- package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
- package/dist/app/rendering/popup-menu-renderer.js +42 -37
- package/dist/app/rendering/render-controller.js +23 -2
- package/dist/app/rendering/status-line-renderer.d.ts +5 -0
- package/dist/app/rendering/status-line-renderer.js +41 -5
- package/dist/app/rendering/tab-line-renderer.js +26 -20
- package/dist/app/runtime.d.ts +12 -1
- package/dist/app/runtime.js +120 -12
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +17 -7
- package/dist/app/screen/status-controller.d.ts +4 -0
- package/dist/app/screen/status-controller.js +5 -0
- package/dist/app/session/lazy-session-manager.js +34 -0
- package/dist/app/session/session-event-controller.d.ts +1 -0
- package/dist/app/session/session-event-controller.js +10 -1
- package/dist/app/session/session-history.d.ts +1 -0
- package/dist/app/session/session-history.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +7 -1
- package/dist/app/session/session-lifecycle-controller.js +16 -1
- package/dist/app/session/tabs-controller.d.ts +26 -1
- package/dist/app/session/tabs-controller.js +454 -147
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
- package/dist/app/subagents/subagents-widget-controller.js +6 -0
- package/dist/app/types.d.ts +16 -1
- package/dist/app/workspace/workspace-actions-controller.js +10 -2
- package/dist/app/workspace/workspace-undo.d.ts +1 -0
- package/dist/app/workspace/workspace-undo.js +1 -0
- package/dist/bundled-extensions/question/index.js +9 -1
- package/dist/bundled-extensions/question/remote.d.ts +4 -0
- package/dist/bundled-extensions/question/remote.js +33 -0
- package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
- package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
- package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
- package/dist/bundled-extensions/telegram-connector/config.js +87 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
- package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
- package/dist/bundled-extensions/telegram-connector/index.js +195 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
- package/dist/bundled-extensions/terminal-bell/index.js +0 -76
- package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
- package/dist/bundled-extensions/workspace-undo/index.js +191 -0
- package/dist/config.d.ts +21 -2
- package/dist/config.js +159 -32
- package/dist/default-pix-config.js +25 -5
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +88 -62
- package/dist/schemas/pi-tools-suite-schema.js +54 -83
- package/dist/schemas/pix-schema.d.ts +19 -2
- package/dist/schemas/pix-schema.js +47 -5
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +9 -1
- package/docs/desktop-mvp.md +22 -6
- package/docs/desktop-task-manager.md +148 -84
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +197 -84
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +47 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +48 -30
- package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
- package/external/pi-tools-suite/package.json +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
- package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +15 -71
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +160 -327
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +21 -5
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +40 -26
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +16 -9
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +55 -1
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +77 -3
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +222 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +206 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +7 -7
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +16 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +128 -49
- package/external/pi-tools-suite/src/dcp/commands.ts +32 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +177 -46
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +114 -17
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
- package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
- package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
- package/external/pi-tools-suite/src/dcp/index.ts +338 -66
- package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
- package/external/pi-tools-suite/src/dcp/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +93 -3
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
- package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -0
- package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
- package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +78 -22
- package/external/pi-tools-suite/src/index.ts +7 -1
- package/external/pi-tools-suite/src/lib/project.ts +36 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
- package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
- package/external/pi-tools-suite/src/resource-registry/index.ts +2551 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
- package/external/pi-tools-suite/src/todo/index.ts +21 -0
- package/external/pi-tools-suite/src/todo/todo.ts +19 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +47 -19
- package/package.json +10 -9
- package/schemas/pi-tools-suite.json +466 -287
- package/schemas/pix.json +129 -11
- package/schemas/tasks.json +131 -0
- package/skills/simplify/SKILL.md +33 -5
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -177
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- package/skills/playwright-cli/SKILL.md +0 -420
- package/skills/playwright-cli/references/element-attributes.md +0 -23
- package/skills/playwright-cli/references/playwright-tests.md +0 -50
- package/skills/playwright-cli/references/request-mocking.md +0 -87
- package/skills/playwright-cli/references/running-code.md +0 -241
- package/skills/playwright-cli/references/session-management.md +0 -273
- package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
- package/skills/playwright-cli/references/storage-state.md +0 -290
- package/skills/playwright-cli/references/test-generation.md +0 -142
- package/skills/playwright-cli/references/tracing.md +0 -154
- package/skills/playwright-cli/references/video-recording.md +0 -147
- package/skills/spec-lite/SKILL.md +0 -140
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
|
@@ -1,47 +1,63 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { classifyShellCommand, type ShellCommandClassification } from "../shell-command-policy.js";
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
|
-
|
|
6
|
+
contextGatewayAccountingLogDrain,
|
|
7
|
+
contextGatewayAccountingLogEnabled,
|
|
8
|
+
writeContextGatewayAccountingLog,
|
|
9
|
+
} from "./accounting-log.js";
|
|
10
|
+
import {
|
|
11
|
+
contextGatewayBudgetForClass,
|
|
5
12
|
loadContextGatewayConfig,
|
|
6
13
|
} from "./config.js";
|
|
7
|
-
import {
|
|
14
|
+
import { ContextGatewayEfficiencyTracker } from "./efficiency.js";
|
|
15
|
+
import { planContextGatewayEnforcement } from "./enforcement.js";
|
|
16
|
+
import { classifyContextGatewayTool, ContextGatewayTelemetry } from "./telemetry.js";
|
|
8
17
|
import type {
|
|
9
18
|
ContextGatewayEffectiveMode,
|
|
10
19
|
ContextGatewayMode,
|
|
11
20
|
ContextGatewayResolvedConfig,
|
|
12
21
|
ContextGatewayRuntimeState,
|
|
22
|
+
ContextGatewayToolClass,
|
|
13
23
|
} from "./types.js";
|
|
14
24
|
|
|
15
25
|
export interface RegisterContextGatewayOptions {
|
|
16
26
|
loadConfig?: () => ContextGatewayResolvedConfig;
|
|
17
27
|
telemetry?: ContextGatewayTelemetry;
|
|
28
|
+
efficiency?: ContextGatewayEfficiencyTracker;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export interface RegisteredContextGateway extends ContextGatewayRuntimeState {
|
|
32
|
+
readonly efficiency: ContextGatewayEfficiencyTracker;
|
|
21
33
|
setRuntimeMode(mode: ContextGatewayMode): { ok: boolean; message: string };
|
|
22
34
|
}
|
|
23
35
|
|
|
24
36
|
function formatStatus(runtime: RegisteredContextGateway): string {
|
|
25
37
|
const telemetry = runtime.telemetry.snapshot();
|
|
38
|
+
const efficiency = runtime.efficiency.snapshot();
|
|
26
39
|
return [
|
|
27
40
|
`Context Gateway requested=${runtime.requestedMode}, effective=${runtime.effectiveMode}.`,
|
|
28
|
-
`Observed results=${telemetry.results}, errors=${telemetry.errors}, upstreamTruncated=${telemetry.upstreamTruncatedResults}, overBudget=${telemetry.overBudgetResults}, unbound results=${telemetry.unboundResults}.`,
|
|
29
|
-
`Repeat
|
|
41
|
+
`Observed results=${telemetry.results}, errors=${telemetry.errors}, upstreamTruncated=${telemetry.upstreamTruncatedResults}, overBudget=${telemetry.overBudgetResults}, enforced=${telemetry.enforcedResults}, unbound results=${telemetry.unboundResults}.`,
|
|
42
|
+
`Repeat exact reads=${telemetry.repeatCandidateCount}, same-source different-range reads=${telemetry.sameSourceDifferentRangeCount}, retrieval calls=${telemetry.retrievalCalls}.`,
|
|
30
43
|
`Native policy results=${telemetry.nativePolicy.results}, refusals=${telemetry.nativePolicy.refusals}, full overrides=${telemetry.nativePolicy.fullOverrides}.`,
|
|
31
|
-
`
|
|
44
|
+
`Source contentBytes=${telemetry.contentBytes}, deliveredContentBytes=${telemetry.deliveredContentBytes}, actualBytesSaved=${telemetry.actualBytesSaved}, potentialBytesOverBudget=${telemetry.potentialBytesOverBudget}, detailsBytes=${telemetry.detailsBytes}.`,
|
|
45
|
+
`Efficiency gross≈${efficiency.grossEstimatedTokensSaved} tokens/${efficiency.grossBytesSaved} bytes, retrieval tax≈${efficiency.retrievalEstimatedTokens} tokens/${efficiency.retrievalBytes} bytes, conservative net≈${efficiency.conservativeNetEstimatedTokens} tokens/${efficiency.conservativeNetBytes} bytes.`,
|
|
46
|
+
`Provider attempts=${efficiency.providerAttempts}, completions=${efficiency.providerCompletions}, input=${efficiency.providerInputTokens}, cacheRead=${efficiency.providerCacheReadTokens}, cacheWrite=${efficiency.providerCacheWriteTokens}, output=${efficiency.providerOutputTokens}, total=${efficiency.providerTotalTokens}.`,
|
|
32
47
|
].join("\n");
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
function formatDoctor(runtime: RegisteredContextGateway): string {
|
|
36
51
|
const lines = [
|
|
37
52
|
`Context Gateway doctor: requested=${runtime.requestedMode}, effective=${runtime.effectiveMode}.`,
|
|
38
|
-
"
|
|
53
|
+
"Budgets: read=maxExactReadBytes; repo search/AST/structure=maxSearchBytes; other results=maxResultBytes; compact inline view=maxInlineBytes.",
|
|
54
|
+
"Enforce: recognised complete simple test/build output=bounded parser compact; over-budget web_search/web_fetch with structured raw details=bounded recoverable compact; unsupported/unsafe results remain passthrough.",
|
|
55
|
+
"Read: full passthrough even when over budget; exact recovery/archive is not wired, so irreversible read truncation is disabled.",
|
|
56
|
+
"Repo tools: producer-native compact is supported with repoDiscovery.profile=native-compact; Gateway never post-hoc slices repo output.",
|
|
39
57
|
"External paths: MCP=current Pix adapter unsupported; direct parent browser capture=unsupported.",
|
|
40
58
|
"Storage: disabled; no durable archive, index, quota reservation, or retrieval tools are active.",
|
|
59
|
+
`Accounting log: ${contextGatewayAccountingLogEnabled(runtime.config) ? "enabled" : "disabled"}; scalar-only JSONL with bounded rotation, no result bodies, paths, or tool arguments.`,
|
|
41
60
|
];
|
|
42
|
-
if (runtime.requestedMode === "enforce") {
|
|
43
|
-
lines.push("BLOCKED: enforce is unavailable in P01; effective mode is off.");
|
|
44
|
-
}
|
|
45
61
|
for (const issue of runtime.config.issues) lines.push(`Config issue: ${issue}`);
|
|
46
62
|
return lines.join("\n");
|
|
47
63
|
}
|
|
@@ -56,13 +72,48 @@ export function registerContextGateway(
|
|
|
56
72
|
): RegisteredContextGateway {
|
|
57
73
|
const config = (options.loadConfig ?? (() => loadContextGatewayConfig()))();
|
|
58
74
|
const telemetry = options.telemetry ?? new ContextGatewayTelemetry();
|
|
75
|
+
const efficiency = options.efficiency ?? new ContextGatewayEfficiencyTracker();
|
|
59
76
|
let requestedMode = config.mode;
|
|
60
|
-
let effectiveMode: ContextGatewayEffectiveMode = requestedMode
|
|
61
|
-
|
|
77
|
+
let effectiveMode: ContextGatewayEffectiveMode = requestedMode;
|
|
78
|
+
type InFlight = { toolClass: ContextGatewayToolClass; shell: ShellCommandClassification };
|
|
79
|
+
const inFlightToolCalls = new Map<string, InFlight>();
|
|
80
|
+
const runtimeId = randomUUID();
|
|
81
|
+
let accountingEpoch = 0;
|
|
82
|
+
let accountingEpochStartedAt = Date.now();
|
|
83
|
+
let accountingEpochActive = false;
|
|
84
|
+
|
|
85
|
+
const writeAccounting = (event: string, details: Record<string, unknown> = {}): void => {
|
|
86
|
+
writeContextGatewayAccountingLog(config, event, {
|
|
87
|
+
runtimeId,
|
|
88
|
+
sessionEpoch: accountingEpoch,
|
|
89
|
+
mode: effectiveMode,
|
|
90
|
+
...details,
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const accountingSummary = (): Record<string, unknown> => ({
|
|
95
|
+
durationMs: Math.max(0, Date.now() - accountingEpochStartedAt),
|
|
96
|
+
totals: efficiency.snapshot(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const finalizeAccountingEpoch = (reason: string): void => {
|
|
100
|
+
if (!accountingEpochActive || effectiveMode === "off") return;
|
|
101
|
+
writeAccounting("session.summary", { reason, ...accountingSummary() });
|
|
102
|
+
accountingEpochActive = false;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const beginAccountingEpoch = (reason: string): void => {
|
|
106
|
+
efficiency.reset();
|
|
107
|
+
accountingEpoch += 1;
|
|
108
|
+
accountingEpochStartedAt = Date.now();
|
|
109
|
+
accountingEpochActive = effectiveMode !== "off";
|
|
110
|
+
if (accountingEpochActive) writeAccounting("session.start", { reason });
|
|
111
|
+
};
|
|
62
112
|
|
|
63
113
|
const resetRuntimeTelemetry = (): void => {
|
|
64
114
|
inFlightToolCalls.clear();
|
|
65
115
|
telemetry.reset();
|
|
116
|
+
efficiency.clearTransientBindings();
|
|
66
117
|
};
|
|
67
118
|
|
|
68
119
|
const runtime: RegisteredContextGateway = {
|
|
@@ -70,55 +121,182 @@ export function registerContextGateway(
|
|
|
70
121
|
get effectiveMode() { return effectiveMode; },
|
|
71
122
|
config,
|
|
72
123
|
telemetry,
|
|
124
|
+
efficiency,
|
|
73
125
|
setRuntimeMode(mode) {
|
|
74
|
-
if (mode === "enforce") {
|
|
75
|
-
return { ok: false, message: CONTEXT_GATEWAY_ENFORCE_UNAVAILABLE };
|
|
76
|
-
}
|
|
77
126
|
if (mode !== effectiveMode && inFlightToolCalls.size > 0) {
|
|
78
127
|
return {
|
|
79
128
|
ok: false,
|
|
80
129
|
message: `Context Gateway mode change requires a safe boundary; ${inFlightToolCalls.size} tool call(s) are still in flight.`,
|
|
81
130
|
};
|
|
82
131
|
}
|
|
132
|
+
const previousMode = effectiveMode;
|
|
133
|
+
if (previousMode !== mode) finalizeAccountingEpoch("mode-change");
|
|
83
134
|
requestedMode = mode;
|
|
84
135
|
effectiveMode = mode;
|
|
85
136
|
telemetry.clearTransientBindings();
|
|
137
|
+
efficiency.clearTransientBindings();
|
|
138
|
+
if (previousMode !== mode) {
|
|
139
|
+
writeContextGatewayAccountingLog(config, "mode.change", {
|
|
140
|
+
runtimeId,
|
|
141
|
+
sessionEpoch: accountingEpoch,
|
|
142
|
+
from: previousMode,
|
|
143
|
+
to: mode,
|
|
144
|
+
});
|
|
145
|
+
beginAccountingEpoch("mode-change");
|
|
146
|
+
}
|
|
86
147
|
return { ok: true, message: `Context Gateway runtime mode set to ${mode}; config was not changed.` };
|
|
87
148
|
},
|
|
88
149
|
};
|
|
89
150
|
|
|
90
151
|
pi.on("tool_call", async (event) => {
|
|
91
|
-
if (typeof event.toolCallId === "string"
|
|
92
|
-
|
|
152
|
+
if (typeof event.toolCallId === "string" && typeof event.toolName === "string") {
|
|
153
|
+
const toolClass = classifyContextGatewayTool(event.toolName);
|
|
154
|
+
inFlightToolCalls.set(event.toolCallId, {
|
|
155
|
+
toolClass,
|
|
156
|
+
shell: toolClass === "shell"
|
|
157
|
+
? classifyShellCommand(event.input)
|
|
158
|
+
: { scope: "unknown", kind: "unknown" },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (effectiveMode === "observe" || effectiveMode === "enforce") {
|
|
162
|
+
telemetry.recordToolCall(event);
|
|
163
|
+
const accountingCall = efficiency.recordToolCall(event);
|
|
164
|
+
if (accountingCall) writeAccounting("tool.call", { ...accountingCall });
|
|
165
|
+
}
|
|
93
166
|
return undefined;
|
|
94
167
|
});
|
|
95
168
|
|
|
96
169
|
pi.on("tool_result", async (event) => {
|
|
97
170
|
try {
|
|
98
|
-
if (effectiveMode === "
|
|
171
|
+
if (effectiveMode === "off") return undefined;
|
|
172
|
+
const binding = typeof event.toolCallId === "string"
|
|
173
|
+
? inFlightToolCalls.get(event.toolCallId)
|
|
174
|
+
: undefined;
|
|
175
|
+
const classBudget = binding
|
|
176
|
+
? contextGatewayBudgetForClass(binding.toolClass, config.budgets)
|
|
177
|
+
: config.budgets.maxResultBytes;
|
|
178
|
+
const enforcement = effectiveMode === "enforce" && binding
|
|
179
|
+
? planContextGatewayEnforcement({
|
|
180
|
+
event,
|
|
181
|
+
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : undefined,
|
|
182
|
+
toolClass: binding.toolClass,
|
|
183
|
+
shell: binding.shell,
|
|
184
|
+
budgetBytes: classBudget,
|
|
185
|
+
maxInlineBytes: Math.min(
|
|
186
|
+
config.budgets.maxInlineBytes,
|
|
187
|
+
classBudget,
|
|
188
|
+
),
|
|
189
|
+
})
|
|
190
|
+
: undefined;
|
|
191
|
+
const observation = telemetry.recordToolResult(event, config.budgets, {
|
|
192
|
+
maxInlineBytes: config.budgets.maxInlineBytes,
|
|
193
|
+
...(enforcement ? {
|
|
194
|
+
delivery: {
|
|
195
|
+
representation: enforcement.representation,
|
|
196
|
+
contentBytes: enforcement.contentBytes,
|
|
197
|
+
textBytes: enforcement.textBytes,
|
|
198
|
+
},
|
|
199
|
+
} : {}),
|
|
200
|
+
});
|
|
201
|
+
const accountingResult = efficiency.recordToolResult(
|
|
202
|
+
event,
|
|
203
|
+
observation,
|
|
204
|
+
enforcement?.content ?? event.content,
|
|
205
|
+
);
|
|
206
|
+
if (accountingResult) {
|
|
207
|
+
const efficiencySnapshot = efficiency.snapshot();
|
|
208
|
+
writeAccounting("tool.result", {
|
|
209
|
+
...accountingResult,
|
|
210
|
+
cumulative: {
|
|
211
|
+
grossBytesSaved: efficiencySnapshot.grossBytesSaved,
|
|
212
|
+
grossEstimatedTokensSaved: efficiencySnapshot.grossEstimatedTokensSaved,
|
|
213
|
+
retrievalBytes: efficiencySnapshot.retrievalBytes,
|
|
214
|
+
retrievalEstimatedTokens: efficiencySnapshot.retrievalEstimatedTokens,
|
|
215
|
+
conservativeNetBytes: efficiencySnapshot.conservativeNetBytes,
|
|
216
|
+
conservativeNetEstimatedTokens: efficiencySnapshot.conservativeNetEstimatedTokens,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (enforcement && enforcement.representation !== "passthrough") {
|
|
221
|
+
const originalDetails = event.details && typeof event.details === "object" && !Array.isArray(event.details)
|
|
222
|
+
? event.details as Record<string, unknown>
|
|
223
|
+
: {};
|
|
224
|
+
return {
|
|
225
|
+
content: enforcement.content as any,
|
|
226
|
+
details: {
|
|
227
|
+
...originalDetails,
|
|
228
|
+
contextGateway: {
|
|
229
|
+
version: 1,
|
|
230
|
+
representation: enforcement.representation,
|
|
231
|
+
sourceContentBytes: enforcement.sourceContentBytes,
|
|
232
|
+
deliveredContentBytes: enforcement.contentBytes,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
99
237
|
} finally {
|
|
100
238
|
if (typeof event.toolCallId === "string") inFlightToolCalls.delete(event.toolCallId);
|
|
101
239
|
}
|
|
102
240
|
return undefined;
|
|
103
241
|
});
|
|
104
242
|
|
|
243
|
+
pi.on("before_provider_request", async (_event, ctx) => {
|
|
244
|
+
if (effectiveMode === "off") return undefined;
|
|
245
|
+
const attempt = efficiency.recordProviderAttempt();
|
|
246
|
+
const model = (ctx as any)?.model;
|
|
247
|
+
writeAccounting("provider.request", {
|
|
248
|
+
attempt,
|
|
249
|
+
provider: typeof model?.provider === "string" ? model.provider : undefined,
|
|
250
|
+
model: typeof model?.id === "string" ? model.id : undefined,
|
|
251
|
+
});
|
|
252
|
+
return undefined;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
pi.on("message_end", async (event) => {
|
|
256
|
+
if (effectiveMode === "off" || event.message?.role !== "assistant") return;
|
|
257
|
+
const completion = efficiency.recordProviderCompletion(event.message as any);
|
|
258
|
+
const snapshot = efficiency.snapshot();
|
|
259
|
+
writeAccounting("provider.completion", {
|
|
260
|
+
...completion,
|
|
261
|
+
cumulative: {
|
|
262
|
+
providerInputTokens: snapshot.providerInputTokens,
|
|
263
|
+
providerOutputTokens: snapshot.providerOutputTokens,
|
|
264
|
+
providerCacheReadTokens: snapshot.providerCacheReadTokens,
|
|
265
|
+
providerCacheWriteTokens: snapshot.providerCacheWriteTokens,
|
|
266
|
+
providerTotalTokens: snapshot.providerTotalTokens,
|
|
267
|
+
providerCost: snapshot.providerCost,
|
|
268
|
+
grossEstimatedTokensSaved: snapshot.grossEstimatedTokensSaved,
|
|
269
|
+
retrievalEstimatedTokens: snapshot.retrievalEstimatedTokens,
|
|
270
|
+
conservativeNetEstimatedTokens: snapshot.conservativeNetEstimatedTokens,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
105
275
|
pi.on("session_start", async () => {
|
|
276
|
+
finalizeAccountingEpoch("session-start-reset");
|
|
106
277
|
resetRuntimeTelemetry();
|
|
278
|
+
beginAccountingEpoch("session-start");
|
|
107
279
|
return undefined;
|
|
108
280
|
});
|
|
109
281
|
|
|
110
282
|
pi.on("session_tree", async () => {
|
|
283
|
+
finalizeAccountingEpoch("session-tree");
|
|
111
284
|
resetRuntimeTelemetry();
|
|
285
|
+
beginAccountingEpoch("session-tree");
|
|
112
286
|
return undefined;
|
|
113
287
|
});
|
|
114
288
|
|
|
115
289
|
pi.on("session_shutdown", async () => {
|
|
290
|
+
finalizeAccountingEpoch("session-shutdown");
|
|
116
291
|
resetRuntimeTelemetry();
|
|
292
|
+
efficiency.reset();
|
|
293
|
+
accountingEpochActive = false;
|
|
294
|
+
await contextGatewayAccountingLogDrain();
|
|
117
295
|
return undefined;
|
|
118
296
|
});
|
|
119
297
|
|
|
120
298
|
pi.registerCommand("context-gateway", {
|
|
121
|
-
description: "Show Context Gateway status/doctor output or change
|
|
299
|
+
description: "Show Context Gateway status/doctor output or change runtime-only off/observe/enforce mode.",
|
|
122
300
|
handler: async (rawArgs, ctx) => {
|
|
123
301
|
const args = rawArgs.trim().split(/\s+/).filter(Boolean);
|
|
124
302
|
const action = (args[0] ?? "status").toLowerCase();
|
|
@@ -127,7 +305,7 @@ export function registerContextGateway(
|
|
|
127
305
|
return;
|
|
128
306
|
}
|
|
129
307
|
if (action === "doctor") {
|
|
130
|
-
notify(ctx, formatDoctor(runtime), runtime.
|
|
308
|
+
notify(ctx, formatDoctor(runtime), runtime.config.issues.length > 0 ? "warning" : "info");
|
|
131
309
|
return;
|
|
132
310
|
}
|
|
133
311
|
if (action === "mode") {
|
|
@@ -152,7 +330,16 @@ export default function contextGatewayExtension(pi: ExtensionAPI): void {
|
|
|
152
330
|
}
|
|
153
331
|
|
|
154
332
|
export { accountContextGatewayParts } from "./accounting.js";
|
|
333
|
+
export {
|
|
334
|
+
contextGatewayAccountingLogDrain,
|
|
335
|
+
contextGatewayAccountingLogEnabled,
|
|
336
|
+
contextGatewayAccountingLogMaxBackups,
|
|
337
|
+
contextGatewayAccountingLogMaxBytes,
|
|
338
|
+
contextGatewayAccountingLogPath,
|
|
339
|
+
writeContextGatewayAccountingLog,
|
|
340
|
+
} from "./accounting-log.js";
|
|
155
341
|
export { loadContextGatewayConfig } from "./config.js";
|
|
342
|
+
export { ContextGatewayEfficiencyTracker, estimateContextGatewayTokens } from "./efficiency.js";
|
|
156
343
|
export { normalizeRedundantTruncationMetadata } from "./metadata-normalization.js";
|
|
157
344
|
export { STORELESS_CAPABILITIES, storelessCapability } from "./storeless-capabilities.js";
|
|
158
345
|
export { parseTestBuildOutput, planProspectiveTestOutputDelivery } from "./test-output-parser.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type StorelessCapabilityStatus = "supported" | "limited" | "unsupported";
|
|
2
|
-
export type StorelessCapabilityStrategy = "native-passthrough" | "metadata-only" | "pure-parser-candidate" | "none";
|
|
2
|
+
export type StorelessCapabilityStrategy = "native-passthrough" | "metadata-only" | "pure-parser-candidate" | "pure-parser-compact" | "raw-session-recoverable-compact" | "none";
|
|
3
3
|
|
|
4
4
|
export interface StorelessCapabilityEntry {
|
|
5
5
|
surface:
|
|
@@ -13,7 +13,7 @@ export interface StorelessCapabilityEntry {
|
|
|
13
13
|
| "mcp-direct";
|
|
14
14
|
status: StorelessCapabilityStatus;
|
|
15
15
|
strategy: StorelessCapabilityStrategy;
|
|
16
|
-
lifetime: "current-result" | "producer-managed" | "visual-message" | "none";
|
|
16
|
+
lifetime: "current-result" | "producer-managed" | "raw-session" | "visual-message" | "none";
|
|
17
17
|
reason: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -27,9 +27,9 @@ export const STORELESS_CAPABILITIES: readonly StorelessCapabilityEntry[] = Objec
|
|
|
27
27
|
{
|
|
28
28
|
surface: "test-build",
|
|
29
29
|
status: "limited",
|
|
30
|
-
strategy: "pure-parser-
|
|
30
|
+
strategy: "pure-parser-compact",
|
|
31
31
|
lifetime: "current-result",
|
|
32
|
-
reason: "Bun/TAP/bounded TypeScript
|
|
32
|
+
reason: "Enforce compacts only recognised complete simple Bun/TAP/bounded TypeScript output; partial, compound, truncated, or unknown results remain passthrough.",
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
surface: "mutation-lsp",
|
|
@@ -41,9 +41,9 @@ export const STORELESS_CAPABILITIES: readonly StorelessCapabilityEntry[] = Objec
|
|
|
41
41
|
{
|
|
42
42
|
surface: "web-document",
|
|
43
43
|
status: "limited",
|
|
44
|
-
strategy: "
|
|
45
|
-
lifetime: "
|
|
46
|
-
reason: "
|
|
44
|
+
strategy: "raw-session-recoverable-compact",
|
|
45
|
+
lifetime: "raw-session",
|
|
46
|
+
reason: "In enforce mode, over-budget web_search/web_fetch results are compacted only when the producer supplied full structured details. Those raw details remain in the session toolResult and are recoverable by toolCallId through session-recovery; unsupported web shapes remain passthrough.",
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
surface: "structured-json",
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { classifyShellCommand } from "../shell-command-policy.js";
|
|
2
3
|
|
|
3
4
|
import { accountContextGatewayParts } from "./accounting.js";
|
|
5
|
+
import { contextGatewayBudgetForClass } from "./config.js";
|
|
4
6
|
import { parseTestBuildOutput, planProspectiveTestOutputDelivery } from "./test-output-parser.js";
|
|
5
7
|
import type {
|
|
8
|
+
ContextGatewayBudgets,
|
|
6
9
|
ContextGatewayClassTelemetry,
|
|
7
10
|
ContextGatewayNativePolicyReason,
|
|
8
11
|
ContextGatewayNativePolicyTelemetry,
|
|
@@ -40,6 +43,7 @@ const NATIVE_POLICY_REASONS = new Set<ContextGatewayNativePolicyReason>([
|
|
|
40
43
|
type CallBinding = {
|
|
41
44
|
toolClass: ContextGatewayToolClass;
|
|
42
45
|
inputFingerprint?: string;
|
|
46
|
+
readSourceFingerprint?: string;
|
|
43
47
|
shellCommandScope?: "simple" | "compound" | "unknown";
|
|
44
48
|
};
|
|
45
49
|
|
|
@@ -60,12 +64,15 @@ function emptyCounters(): ContextGatewayClassTelemetry {
|
|
|
60
64
|
results: 0,
|
|
61
65
|
errors: 0,
|
|
62
66
|
contentBytes: 0,
|
|
67
|
+
deliveredContentBytes: 0,
|
|
63
68
|
textBytes: 0,
|
|
64
69
|
imageBytes: 0,
|
|
65
70
|
detailsBytes: 0,
|
|
66
71
|
upstreamTruncatedResults: 0,
|
|
67
72
|
overBudgetResults: 0,
|
|
68
73
|
potentialBytesOverBudget: 0,
|
|
74
|
+
enforcedResults: 0,
|
|
75
|
+
actualBytesSaved: 0,
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -92,13 +99,46 @@ function inputFingerprint(toolName: string, input: unknown): string {
|
|
|
92
99
|
return createHash("sha256").update(`${toolName}\u0000${encoded}`).digest("hex");
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
function
|
|
96
|
-
if (
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
function normalizedReadPath(value: unknown): string | undefined {
|
|
103
|
+
if (typeof value !== "string") return undefined;
|
|
104
|
+
const raw = value.trim().replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
105
|
+
if (!raw) return undefined;
|
|
106
|
+
const prefix = raw.startsWith("/") ? "/" : /^[A-Za-z]:\//.test(raw) ? raw.slice(0, 3).toLowerCase() : "";
|
|
107
|
+
const body = prefix === "/" ? raw.slice(1) : prefix ? raw.slice(3) : raw;
|
|
108
|
+
const stack: string[] = [];
|
|
109
|
+
for (const part of body.split("/")) {
|
|
110
|
+
if (!part || part === ".") continue;
|
|
111
|
+
if (part === "..") {
|
|
112
|
+
if (stack.length > 0 && stack.at(-1) !== "..") stack.pop();
|
|
113
|
+
else if (!prefix) stack.push(part);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
stack.push(part);
|
|
117
|
+
}
|
|
118
|
+
return `${prefix}${stack.join("/")}` || prefix || ".";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizedReadRangeValue(value: unknown): number | undefined {
|
|
122
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
|
|
123
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) {
|
|
124
|
+
const parsed = Number(value);
|
|
125
|
+
if (Number.isSafeInteger(parsed)) return parsed;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function contextGatewayReadIdentity(input: unknown): { exact?: string; source?: string } {
|
|
131
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
132
|
+
const record = input as Record<string, unknown>;
|
|
133
|
+
const path = normalizedReadPath(record.path ?? record.file ?? record.filePath);
|
|
134
|
+
if (!path) return {};
|
|
135
|
+
const source = inputFingerprint("read-source", { path });
|
|
136
|
+
const exact = inputFingerprint("read-exact", {
|
|
137
|
+
path,
|
|
138
|
+
offset: normalizedReadRangeValue(record.offset ?? record.start ?? record.line),
|
|
139
|
+
limit: normalizedReadRangeValue(record.limit ?? record.lines ?? record.count),
|
|
140
|
+
});
|
|
141
|
+
return { source, exact };
|
|
102
142
|
}
|
|
103
143
|
|
|
104
144
|
function byteLengthJson(value: unknown): number {
|
|
@@ -209,19 +249,31 @@ export function classifyContextGatewayTool(toolName: string): ContextGatewayTool
|
|
|
209
249
|
|
|
210
250
|
export function observeToolResult(
|
|
211
251
|
event: ToolResultLike,
|
|
212
|
-
|
|
213
|
-
options: {
|
|
252
|
+
budget: number | ContextGatewayBudgets,
|
|
253
|
+
options: {
|
|
254
|
+
toolClass?: ContextGatewayToolClass;
|
|
255
|
+
shellCommandScope?: "simple" | "compound" | "unknown";
|
|
256
|
+
maxInlineBytes?: number;
|
|
257
|
+
delivery?: { representation: "passthrough" | "test-build-compact" | "web-recoverable-compact"; contentBytes: number; textBytes: number };
|
|
258
|
+
} = {},
|
|
214
259
|
): ContextGatewayObservation {
|
|
215
260
|
const toolName = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
216
261
|
const sizes = contentSizes(event.content);
|
|
217
262
|
const detailsBytes = byteLengthJson(event.details);
|
|
218
|
-
const potentialBytesOverBudget = Math.max(0, sizes.contentBytes - maxResultBytes);
|
|
219
263
|
const toolClass = options.toolClass ?? classifyContextGatewayTool(toolName);
|
|
264
|
+
const budgetBytes = typeof budget === "number" ? budget : contextGatewayBudgetForClass(toolClass, budget);
|
|
265
|
+
const potentialBytesOverBudget = Math.max(0, sizes.contentBytes - budgetBytes);
|
|
220
266
|
const completeness = isTruncated(event.details) ? "upstream-truncated" : "unknown";
|
|
267
|
+
const delivery = options.delivery ?? {
|
|
268
|
+
representation: "passthrough" as const,
|
|
269
|
+
contentBytes: sizes.contentBytes,
|
|
270
|
+
textBytes: sizes.textBytes,
|
|
271
|
+
};
|
|
221
272
|
const observation: ContextGatewayObservation = {
|
|
222
273
|
version: 1,
|
|
223
274
|
toolClass,
|
|
224
275
|
outcome: event.isError === true ? "error" : "success",
|
|
276
|
+
budgetBytes,
|
|
225
277
|
source: {
|
|
226
278
|
kind: "tool-result-boundary",
|
|
227
279
|
completeness,
|
|
@@ -231,13 +283,10 @@ export function observeToolResult(
|
|
|
231
283
|
detailsBytes,
|
|
232
284
|
},
|
|
233
285
|
view: { kind: "text-content", textBytes: sizes.textBytes },
|
|
234
|
-
delivery
|
|
235
|
-
|
|
236
|
-
contentBytes: sizes.contentBytes,
|
|
237
|
-
textBytes: sizes.textBytes,
|
|
238
|
-
},
|
|
239
|
-
overBudget: sizes.contentBytes > maxResultBytes,
|
|
286
|
+
delivery,
|
|
287
|
+
overBudget: sizes.contentBytes > budgetBytes,
|
|
240
288
|
potentialBytesOverBudget,
|
|
289
|
+
actualBytesSaved: Math.max(0, sizes.contentBytes - delivery.contentBytes),
|
|
241
290
|
};
|
|
242
291
|
if (toolClass === "shell") {
|
|
243
292
|
const parsed = parseTestBuildOutput({
|
|
@@ -246,7 +295,11 @@ export function observeToolResult(
|
|
|
246
295
|
upstreamTruncated: completeness === "upstream-truncated",
|
|
247
296
|
});
|
|
248
297
|
const commandScope = options.shellCommandScope ?? "unknown";
|
|
249
|
-
const prospective = planProspectiveTestOutputDelivery(
|
|
298
|
+
const prospective = planProspectiveTestOutputDelivery(
|
|
299
|
+
parsed,
|
|
300
|
+
options.maxInlineBytes ?? budgetBytes,
|
|
301
|
+
{ commandScope },
|
|
302
|
+
);
|
|
250
303
|
observation.testOutput = {
|
|
251
304
|
parserVersion: 1,
|
|
252
305
|
classification: parsed.classification,
|
|
@@ -268,8 +321,10 @@ export class ContextGatewayTelemetry {
|
|
|
268
321
|
private byClass: Partial<Record<ContextGatewayToolClass, ContextGatewayClassTelemetry>> = {};
|
|
269
322
|
private bindings = new Map<string, CallBinding>();
|
|
270
323
|
private seenInputFingerprints = new Set<string>();
|
|
324
|
+
private seenReadSourceFingerprints = new Set<string>();
|
|
271
325
|
private unboundResults = 0;
|
|
272
326
|
private repeatCandidateCount = 0;
|
|
327
|
+
private sameSourceDifferentRangeCount = 0;
|
|
273
328
|
private retrievalCalls = 0;
|
|
274
329
|
private nativePolicy: ContextGatewayNativePolicyTelemetry = {
|
|
275
330
|
results: 0,
|
|
@@ -286,27 +341,39 @@ export class ContextGatewayTelemetry {
|
|
|
286
341
|
if (normalizedToolName === "artifact_read" || normalizedToolName === "artifact_search") {
|
|
287
342
|
this.retrievalCalls += 1;
|
|
288
343
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
: undefined;
|
|
344
|
+
const readFingerprint = normalizedToolName === "read" ? contextGatewayReadIdentity(event.input) : {};
|
|
345
|
+
const fingerprint = readFingerprint.exact;
|
|
292
346
|
if (fingerprint) {
|
|
293
347
|
if (this.seenInputFingerprints.has(fingerprint)) this.repeatCandidateCount += 1;
|
|
294
|
-
else
|
|
348
|
+
else {
|
|
349
|
+
if (readFingerprint.source && this.seenReadSourceFingerprints.has(readFingerprint.source)) {
|
|
350
|
+
this.sameSourceDifferentRangeCount += 1;
|
|
351
|
+
}
|
|
352
|
+
this.seenInputFingerprints.add(fingerprint);
|
|
353
|
+
}
|
|
295
354
|
}
|
|
355
|
+
if (readFingerprint.source) this.seenReadSourceFingerprints.add(readFingerprint.source);
|
|
356
|
+
const toolClass = classifyContextGatewayTool(event.toolName);
|
|
296
357
|
this.bindings.set(event.toolCallId, {
|
|
297
|
-
toolClass
|
|
358
|
+
toolClass,
|
|
298
359
|
inputFingerprint: fingerprint,
|
|
299
|
-
|
|
300
|
-
|
|
360
|
+
readSourceFingerprint: readFingerprint.source,
|
|
361
|
+
...(toolClass === "shell"
|
|
362
|
+
? { shellCommandScope: classifyShellCommand(event.input).scope }
|
|
301
363
|
: {}),
|
|
302
364
|
});
|
|
303
365
|
}
|
|
304
366
|
|
|
305
|
-
recordToolResult(
|
|
367
|
+
recordToolResult(
|
|
368
|
+
event: ToolResultLike,
|
|
369
|
+
budget: number | ContextGatewayBudgets,
|
|
370
|
+
options: { delivery?: { representation: "passthrough" | "test-build-compact" | "web-recoverable-compact"; contentBytes: number; textBytes: number }; maxInlineBytes?: number } = {},
|
|
371
|
+
): ContextGatewayObservation {
|
|
306
372
|
const binding = typeof event.toolCallId === "string" ? this.bindings.get(event.toolCallId) : undefined;
|
|
307
|
-
const observation = observeToolResult(event,
|
|
373
|
+
const observation = observeToolResult(event, budget, {
|
|
308
374
|
...(binding ? { toolClass: binding.toolClass } : {}),
|
|
309
375
|
...(binding?.shellCommandScope ? { shellCommandScope: binding.shellCommandScope } : {}),
|
|
376
|
+
...options,
|
|
310
377
|
});
|
|
311
378
|
if (typeof event.toolCallId !== "string" || !binding) {
|
|
312
379
|
this.unboundResults += 1;
|
|
@@ -357,8 +424,8 @@ export class ContextGatewayTelemetry {
|
|
|
357
424
|
}
|
|
358
425
|
|
|
359
426
|
/** Backward-compatible convenience for observation-only callers without a tool_call event. */
|
|
360
|
-
record(event: ToolResultLike,
|
|
361
|
-
const observation = observeToolResult(event,
|
|
427
|
+
record(event: ToolResultLike, budget: number | ContextGatewayBudgets): ContextGatewayObservation {
|
|
428
|
+
const observation = observeToolResult(event, budget);
|
|
362
429
|
this.aggregateObservation(event, observation);
|
|
363
430
|
return observation;
|
|
364
431
|
}
|
|
@@ -368,8 +435,10 @@ export class ContextGatewayTelemetry {
|
|
|
368
435
|
this.byClass = {};
|
|
369
436
|
this.bindings.clear();
|
|
370
437
|
this.seenInputFingerprints.clear();
|
|
438
|
+
this.seenReadSourceFingerprints.clear();
|
|
371
439
|
this.unboundResults = 0;
|
|
372
440
|
this.repeatCandidateCount = 0;
|
|
441
|
+
this.sameSourceDifferentRangeCount = 0;
|
|
373
442
|
this.retrievalCalls = 0;
|
|
374
443
|
this.nativePolicy = { results: 0, refusals: 0, fullOverrides: 0, byReason: {} };
|
|
375
444
|
this.testOutput = emptyTestOutputTelemetry();
|
|
@@ -383,6 +452,7 @@ export class ContextGatewayTelemetry {
|
|
|
383
452
|
pendingCalls: this.bindings.size,
|
|
384
453
|
unboundResults: this.unboundResults,
|
|
385
454
|
repeatCandidateCount: this.repeatCandidateCount,
|
|
455
|
+
sameSourceDifferentRangeCount: this.sameSourceDifferentRangeCount,
|
|
386
456
|
retrievalCalls: this.retrievalCalls,
|
|
387
457
|
nativePolicy: {
|
|
388
458
|
...this.nativePolicy,
|
|
@@ -404,12 +474,15 @@ export class ContextGatewayTelemetry {
|
|
|
404
474
|
target.results += 1;
|
|
405
475
|
if (observation.outcome === "error") target.errors += 1;
|
|
406
476
|
target.contentBytes += observation.source.contentBytes;
|
|
477
|
+
target.deliveredContentBytes += observation.delivery.contentBytes;
|
|
407
478
|
target.textBytes += observation.source.textBytes;
|
|
408
479
|
target.imageBytes += observation.source.imageBytes;
|
|
409
480
|
target.detailsBytes += observation.source.detailsBytes;
|
|
410
481
|
if (observation.source.completeness === "upstream-truncated") target.upstreamTruncatedResults += 1;
|
|
411
482
|
if (observation.overBudget) target.overBudgetResults += 1;
|
|
412
483
|
target.potentialBytesOverBudget += observation.potentialBytesOverBudget;
|
|
484
|
+
if (observation.delivery.representation !== "passthrough") target.enforcedResults += 1;
|
|
485
|
+
target.actualBytesSaved += observation.actualBytesSaved;
|
|
413
486
|
}
|
|
414
487
|
}
|
|
415
488
|
|