@vybestack/llxprt-code-core 0.10.0 → 0.11.0-nightly.260728.ffaa8d5d7
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/src/config/config.d.ts +20 -0
- package/dist/src/config/config.js +143 -23
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/configBaseCore.d.ts +6 -2
- package/dist/src/config/configBaseCore.js +8 -0
- package/dist/src/config/configBaseCore.js.map +1 -1
- package/dist/src/config/configConstructor.d.ts +2 -0
- package/dist/src/config/configConstructor.js +9 -1
- package/dist/src/config/configConstructor.js.map +1 -1
- package/dist/src/config/configTestHarness.d.ts +8 -6
- package/dist/src/config/configTestHarness.js +10 -4
- package/dist/src/config/configTestHarness.js.map +1 -1
- package/dist/src/config/configTypes.d.ts +7 -2
- package/dist/src/config/configTypes.js +2 -0
- package/dist/src/config/configTypes.js.map +1 -1
- package/dist/src/config/liveTrustTransitionLifecycle.d.ts +35 -0
- package/dist/src/config/liveTrustTransitionLifecycle.js +180 -0
- package/dist/src/config/liveTrustTransitionLifecycle.js.map +1 -0
- package/dist/src/config/schedulerSingleton.d.ts +2 -2
- package/dist/src/config/schedulerSingleton.js.map +1 -1
- package/dist/src/core/compression/types.d.ts +48 -1
- package/dist/src/core/compression/types.js +41 -0
- package/dist/src/core/compression/types.js.map +1 -1
- package/dist/src/core/model-limits.json +96 -0
- package/dist/src/core/model-limits.schema.d.ts +123 -0
- package/dist/src/core/model-limits.schema.js +75 -0
- package/dist/src/core/model-limits.schema.js.map +1 -0
- package/dist/src/core/tokenLimits.d.ts +6 -8
- package/dist/src/core/tokenLimits.js +56 -132
- package/dist/src/core/tokenLimits.js.map +1 -1
- package/dist/src/core/turn.d.ts +20 -3
- package/dist/src/core/turn.js +20 -3
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/hooks/hookRegistry.d.ts +1 -1
- package/dist/src/hooks/hookRegistry.js +3 -1
- package/dist/src/hooks/hookRegistry.js.map +1 -1
- package/dist/src/hooks/hookRunner.d.ts +6 -0
- package/dist/src/hooks/hookRunner.js +22 -6
- package/dist/src/hooks/hookRunner.js.map +1 -1
- package/dist/src/hooks/hookSystem.d.ts +10 -1
- package/dist/src/hooks/hookSystem.js +69 -12
- package/dist/src/hooks/hookSystem.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/llm-types/geminiContent.d.ts +3 -0
- package/dist/src/policy/config.d.ts +4 -2
- package/dist/src/policy/config.js +22 -18
- package/dist/src/policy/config.js.map +1 -1
- package/dist/src/recording/ReplayEngine.js +39 -2
- package/dist/src/recording/ReplayEngine.js.map +1 -1
- package/dist/src/recording/SessionDiscovery.d.ts +8 -0
- package/dist/src/recording/SessionDiscovery.js +88 -3
- package/dist/src/recording/SessionDiscovery.js.map +1 -1
- package/dist/src/recording/SessionLockManager.d.ts +3 -0
- package/dist/src/recording/SessionLockManager.js +9 -3
- package/dist/src/recording/SessionLockManager.js.map +1 -1
- package/dist/src/recording/SessionRecordingService.d.ts +14 -1
- package/dist/src/recording/SessionRecordingService.js +27 -3
- package/dist/src/recording/SessionRecordingService.js.map +1 -1
- package/dist/src/recording/index.d.ts +4 -3
- package/dist/src/recording/index.js +4 -3
- package/dist/src/recording/index.js.map +1 -1
- package/dist/src/recording/replay-test-helpers.d.ts +5 -0
- package/dist/src/recording/replay-test-helpers.js +13 -0
- package/dist/src/recording/replay-test-helpers.js.map +1 -1
- package/dist/src/recording/resumeNotFoundMessages.d.ts +56 -0
- package/dist/src/recording/resumeNotFoundMessages.js +64 -0
- package/dist/src/recording/resumeNotFoundMessages.js.map +1 -0
- package/dist/src/recording/resumeSession.js +3 -2
- package/dist/src/recording/resumeSession.js.map +1 -1
- package/dist/src/recording/sessionManagement.d.ts +3 -0
- package/dist/src/recording/sessionManagement.js +38 -19
- package/dist/src/recording/sessionManagement.js.map +1 -1
- package/dist/src/recording/types.d.ts +42 -3
- package/dist/src/recording/types.js +1 -1
- package/dist/src/recording/types.js.map +1 -1
- package/dist/src/runtime/AgentRuntimeLoader.js +2 -1
- package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -1
- package/dist/src/scheduler/liveOutput.d.ts +10 -19
- package/dist/src/scheduler/liveOutput.js +21 -21
- package/dist/src/scheduler/liveOutput.js.map +1 -1
- package/dist/src/scheduler/types.d.ts +2 -2
- package/dist/src/services/history/ContentConverters.d.ts +4 -0
- package/dist/src/services/history/ContentConverters.js +77 -59
- package/dist/src/services/history/ContentConverters.js.map +1 -1
- package/dist/src/services/history/HistoryService.d.ts +13 -1
- package/dist/src/services/history/HistoryService.js +71 -0
- package/dist/src/services/history/HistoryService.js.map +1 -1
- package/dist/src/services/history/IContent.d.ts +10 -0
- package/dist/src/services/history/IContent.js.map +1 -1
- package/dist/src/skills/skillManager.d.ts +11 -1
- package/dist/src/skills/skillManager.js +21 -1
- package/dist/src/skills/skillManager.js.map +1 -1
- package/dist/src/test-utils/mock-tool.d.ts +3 -3
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/test-utils/tools.d.ts +2 -2
- package/dist/src/test-utils/tools.js.map +1 -1
- package/dist/src/tools-adapters/CoreShellToolHostAdapter.js +1 -6
- package/dist/src/tools-adapters/CoreShellToolHostAdapter.js.map +1 -1
- package/dist/src/utils/browser-profile-discovery.d.ts +40 -0
- package/dist/src/utils/browser-profile-discovery.js +251 -0
- package/dist/src/utils/browser-profile-discovery.js.map +1 -0
- package/dist/src/utils/env.d.ts +12 -0
- package/dist/src/utils/env.js +15 -0
- package/dist/src/utils/env.js.map +1 -0
- package/dist/src/utils/events.d.ts +10 -1
- package/dist/src/utils/events.js +22 -0
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/fileUtils.js +8 -1
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/retry.js +3 -4
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/secure-browser-launcher.d.ts +35 -1
- package/dist/src/utils/secure-browser-launcher.js +341 -1
- package/dist/src/utils/secure-browser-launcher.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +12 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -1
- package/dist/src/utils/thoughtUtils.d.ts +2 -0
- package/dist/src/utils/thoughtUtils.js.map +1 -1
- package/package.json +13 -9
- package/src/config/config.ts +161 -22
- package/src/config/configBaseCore.ts +10 -2
- package/src/config/configConstructor.ts +11 -0
- package/src/config/configTestHarness.ts +10 -4
- package/src/config/configTypes.ts +6 -1
- package/src/config/liveTrustTransitionLifecycle.ts +250 -0
- package/src/config/schedulerSingleton.ts +2 -5
- package/src/core/compression/types.ts +81 -1
- package/src/core/legacy-model-limits.expected.txt +96 -0
- package/src/core/model-limits.json +96 -0
- package/src/core/model-limits.schema.ts +87 -0
- package/src/core/tokenLimits.ts +73 -147
- package/src/core/turn.ts +20 -3
- package/src/hooks/hookRegistry.ts +3 -1
- package/src/hooks/hookRunner.ts +24 -7
- package/src/hooks/hookSystem.ts +73 -13
- package/src/index.ts +1 -0
- package/src/llm-types/geminiContent.ts +3 -0
- package/src/policy/config.ts +32 -19
- package/src/recording/ReplayEngine.ts +51 -2
- package/src/recording/SessionDiscovery.ts +99 -4
- package/src/recording/SessionLockManager.ts +10 -3
- package/src/recording/SessionRecordingService.ts +36 -3
- package/src/recording/index.ts +19 -2
- package/src/recording/replay-test-helpers.ts +14 -0
- package/src/recording/resumeNotFoundMessages.ts +75 -0
- package/src/recording/resumeSession.ts +3 -1
- package/src/recording/sessionManagement.ts +51 -21
- package/src/recording/types.ts +44 -2
- package/src/runtime/AgentRuntimeLoader.ts +3 -1
- package/src/scheduler/liveOutput.ts +25 -22
- package/src/scheduler/types.ts +5 -2
- package/src/services/history/ContentConverters.ts +91 -68
- package/src/services/history/HistoryService.ts +92 -1
- package/src/services/history/IContent.ts +13 -0
- package/src/skills/skillManager.ts +26 -1
- package/src/test-utils/mock-tool.ts +4 -3
- package/src/test-utils/tools.ts +4 -3
- package/src/tools-adapters/CoreShellToolHostAdapter.ts +1 -7
- package/src/utils/browser-profile-discovery.ts +358 -0
- package/src/utils/env.ts +15 -0
- package/src/utils/events.ts +31 -0
- package/src/utils/fileUtils.ts +11 -1
- package/src/utils/retry.ts +3 -4
- package/src/utils/secure-browser-launcher.ts +500 -2
- package/src/utils/terminalSerializer.ts +9 -0
- package/src/utils/thoughtUtils.ts +2 -0
package/src/core/tokenLimits.ts
CHANGED
|
@@ -4,123 +4,84 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { ModelLimitsCatalogSchema } from './model-limits.schema.js';
|
|
8
|
+
import type { ModelLimitsCatalog } from './model-limits.schema.js';
|
|
9
|
+
import catalogData from './model-limits.json' with { type: 'json' };
|
|
10
|
+
|
|
7
11
|
type Model = string;
|
|
8
12
|
type TokenCount = number;
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Lowered from 1M to 200K (issue #2270 / #2527): the overwhelmingly common
|
|
14
|
-
* real-world floor is 200K (Claude, many GLM/Gemini variants). Defaulting to
|
|
15
|
-
* 200K instead of 1M makes the unrecognized-model case fail-safe — proactive
|
|
16
|
-
* compression fires conservatively rather than silently overrunning a
|
|
17
|
-
* sub-200K backend.
|
|
18
|
-
*/
|
|
19
|
-
export const DEFAULT_TOKEN_LIMIT = 200_000;
|
|
20
|
-
|
|
21
|
-
const OPENAI_128K_PREFIXES = [
|
|
22
|
-
'o4-mini',
|
|
23
|
-
'gpt-4o-mini',
|
|
24
|
-
'gpt-4o-realtime',
|
|
25
|
-
'gpt-4o',
|
|
26
|
-
'gpt-4-turbo',
|
|
27
|
-
];
|
|
14
|
+
// Catalog corruption is a release/configuration error. Validate eagerly so an
|
|
15
|
+
// invalid package fails deterministically instead of silently using stale limits.
|
|
16
|
+
const CATALOG = ModelLimitsCatalogSchema.parse(catalogData);
|
|
28
17
|
|
|
29
|
-
const
|
|
18
|
+
export const DEFAULT_TOKEN_LIMIT: TokenCount = CATALOG.defaultLimit;
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
prefix
|
|
33
|
-
limit: TokenCount;
|
|
20
|
+
function matchesAnyPrefix(model: string, prefixes: readonly string[]): boolean {
|
|
21
|
+
return prefixes.some((prefix) => model.startsWith(prefix));
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{ prefix: 'gpt-3.5-turbo', limit: 16_385 },
|
|
39
|
-
// Gemini 2.x variants (experimental, preview, dated snapshots) — all 1M.
|
|
40
|
-
// Exact entries above take precedence, so the 32K image-generation variant
|
|
41
|
-
// is unaffected. Prevents the lowered DEFAULT_TOKEN_LIMIT from silently
|
|
42
|
-
// shrinking recognized Gemini variants (issue #2527).
|
|
43
|
-
{ prefix: 'gemini-2.5-pro', limit: 1_048_576 },
|
|
44
|
-
{ prefix: 'gemini-2.5-flash', limit: 1_048_576 },
|
|
45
|
-
{ prefix: 'gemini-2.0-flash', limit: 1_048_576 },
|
|
46
|
-
// GLM-5 family — 200K context window (z.ai API docs)
|
|
47
|
-
{ prefix: 'glm-5', limit: 200_000 },
|
|
48
|
-
// GLM-4 family — 128K context window (z.ai API docs)
|
|
49
|
-
{ prefix: 'glm-4', limit: 128_000 },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const EXACT_LIMITS: Record<string, TokenCount> = {
|
|
53
|
-
// GLM (z.ai) models — exact entries for commonly used variants.
|
|
54
|
-
// The GLM-5 family (glm-5, glm-5.1, glm-5.2, …) exposes a 200K context
|
|
55
|
-
// window; the GLM-4 family (glm-4, glm-4.5, glm-4.6, …) exposes 128K.
|
|
56
|
-
// Prefix-based fallbacks below cover future/dated variants.
|
|
57
|
-
'glm-5.2': 200_000,
|
|
58
|
-
'glm-5.1': 200_000,
|
|
59
|
-
'glm-5': 200_000,
|
|
60
|
-
'glm-4.6': 128_000,
|
|
61
|
-
'glm-4.5': 128_000,
|
|
62
|
-
'glm-4': 128_000,
|
|
63
|
-
|
|
64
|
-
// Gemini models
|
|
65
|
-
'gemini-1.5-pro': 2_097_152,
|
|
66
|
-
'gemini-1.5-flash': 1_048_576,
|
|
67
|
-
'gemini-2.5-pro-preview-05-06': 1_048_576,
|
|
68
|
-
'gemini-2.5-pro-preview-06-05': 1_048_576,
|
|
69
|
-
'gemini-2.5-pro': 1_048_576,
|
|
70
|
-
'gemini-2.5-flash-preview-05-20': 1_048_576,
|
|
71
|
-
'gemini-2.5-flash': 1_048_576,
|
|
72
|
-
'gemini-2.5-flash-lite': 1_048_576,
|
|
73
|
-
'gemini-2.0-flash': 1_048_576,
|
|
74
|
-
'gemini-2.0-flash-preview-image-generation': 32_000,
|
|
75
|
-
|
|
76
|
-
// Claude Opus 5 and Opus 4.6/4.7/4.8 default to the Claude Code /
|
|
77
|
-
// subscription 200K context window. The 1M window is API-only and
|
|
78
|
-
// plan-gated; override via /set or a profile (context-limit).
|
|
79
|
-
'claude-opus-5': 200_000,
|
|
80
|
-
'claude-opus-5-latest': 200_000,
|
|
81
|
-
'claude-opus-4-8': 200_000,
|
|
82
|
-
'claude-opus-4-7': 200_000,
|
|
83
|
-
'claude-opus-4-6': 200_000,
|
|
84
|
-
'claude-opus-4-latest': 200_000,
|
|
85
|
-
'claude-sonnet-4-6': 200_000,
|
|
86
|
-
'claude-sonnet-4-latest': 400_000,
|
|
87
|
-
// Claude Sonnet 5 defaults to the Claude Code / subscription 200K context
|
|
88
|
-
// window. The advertised 1M window is API-only and plan-gated; override via
|
|
89
|
-
// /set or a profile (context-limit).
|
|
90
|
-
'claude-sonnet-5': 200_000,
|
|
91
|
-
'claude-sonnet-5-latest': 200_000,
|
|
92
|
-
// Claude Fable 5 defaults to the Claude Code / subscription 200K context
|
|
93
|
-
// window. The advertised 1M window is API-only and plan-gated; override via
|
|
94
|
-
// /set or a profile (context-limit). Mirrors getContextWindowForModel.
|
|
95
|
-
'claude-fable-5': 200_000,
|
|
96
|
-
'claude-fable-5-latest': 200_000,
|
|
97
|
-
'claude-3-7-opus-20250115': 300_000,
|
|
98
|
-
'claude-3-7-sonnet-20250115': 300_000,
|
|
99
|
-
'claude-3-opus-20240229': 200_000,
|
|
100
|
-
'claude-3-sonnet-20240229': 200_000,
|
|
101
|
-
'claude-3-haiku-20240307': 200_000,
|
|
102
|
-
'claude-3.5-sonnet-20240620': 200_000,
|
|
103
|
-
'claude-3.5-sonnet-20241022': 200_000,
|
|
104
|
-
'claude-3.5-haiku-20241022': 200_000,
|
|
105
|
-
'claude-3-5-sonnet-20241022': 200_000,
|
|
106
|
-
'claude-3-5-haiku-20241022': 200_000,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
function matchesAnyPrefix(model: string, prefixes: string[]): boolean {
|
|
110
|
-
return prefixes.some((prefix) => model.startsWith(prefix));
|
|
24
|
+
function assertUnreachableRule(rule: never): never {
|
|
25
|
+
throw new Error(`Unsupported model-limit rule: ${JSON.stringify(rule)}`);
|
|
111
26
|
}
|
|
112
27
|
|
|
113
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a single catalog's ordered rules against a model id + provider
|
|
30
|
+
* prefix. Exported so that the case-insensitive normalization can be tested
|
|
31
|
+
* with any catalog (including mixed-case fixtures) without type assertions.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveOrderedRuleFromCatalog(
|
|
34
|
+
catalog: ModelLimitsCatalog,
|
|
114
35
|
modelWithoutPrefix: string,
|
|
36
|
+
providerPrefix: string,
|
|
115
37
|
): TokenCount | undefined {
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
-
|
|
38
|
+
for (const rule of catalog.orderedRules) {
|
|
39
|
+
switch (rule.type) {
|
|
40
|
+
case 'substring':
|
|
41
|
+
if (modelWithoutPrefix.includes(rule.substring)) {
|
|
42
|
+
return rule.limit;
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
case 'substringOrProviderPrefix':
|
|
46
|
+
if (
|
|
47
|
+
modelWithoutPrefix.includes(rule.substring) ||
|
|
48
|
+
providerPrefix === rule.providerPrefix
|
|
49
|
+
) {
|
|
50
|
+
return rule.limit;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case 'prefixGroup':
|
|
54
|
+
if (matchesAnyPrefix(modelWithoutPrefix, rule.prefixes)) {
|
|
55
|
+
return rule.limit;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case 'substringCaseInsensitive':
|
|
59
|
+
if (
|
|
60
|
+
modelWithoutPrefix
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.includes(rule.substring.toLowerCase())
|
|
63
|
+
) {
|
|
64
|
+
return rule.limit;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
return assertUnreachableRule(rule);
|
|
119
69
|
}
|
|
120
70
|
}
|
|
121
71
|
return undefined;
|
|
122
72
|
}
|
|
123
73
|
|
|
74
|
+
function resolveOrderedRule(
|
|
75
|
+
modelWithoutPrefix: string,
|
|
76
|
+
providerPrefix: string,
|
|
77
|
+
): TokenCount | undefined {
|
|
78
|
+
return resolveOrderedRuleFromCatalog(
|
|
79
|
+
CATALOG,
|
|
80
|
+
modelWithoutPrefix,
|
|
81
|
+
providerPrefix,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
124
85
|
export function tokenLimit(
|
|
125
86
|
model: Model,
|
|
126
87
|
userContextLimit?: number,
|
|
@@ -137,57 +98,22 @@ export function tokenLimit(
|
|
|
137
98
|
colonIndex !== -1 ? model.slice(colonIndex + 1) : model;
|
|
138
99
|
|
|
139
100
|
// Check exact model matches first
|
|
140
|
-
|
|
141
|
-
|
|
101
|
+
const exactLimit = CATALOG.exactLimits[modelWithoutPrefix];
|
|
102
|
+
if (typeof exactLimit === 'number') {
|
|
103
|
+
return exactLimit;
|
|
142
104
|
}
|
|
143
105
|
|
|
144
106
|
// Check prefix-based limits
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Codex models default to 256K context (per composition/aliases/codex.config).
|
|
151
|
-
// variant has a smaller 128K window and must be checked first since its ID
|
|
152
|
-
// also contains the substring "codex". Non-suffixed codex model IDs
|
|
153
|
-
// (gpt-5.5, gpt-5.4, gpt-5.2, gpt-5.1) are only resolvable when the
|
|
154
|
-
// provider prefix is explicitly "codex" — a bare ID is ambiguous and could
|
|
155
|
-
// belong to the regular OpenAI provider.
|
|
156
|
-
if (modelWithoutPrefix.includes('codex-spark')) {
|
|
157
|
-
return 131_072;
|
|
158
|
-
}
|
|
159
|
-
if (modelWithoutPrefix.includes('codex') || providerPrefix === 'codex') {
|
|
160
|
-
return 262_144;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Check OpenAI 200K models (includes o3, o1 series)
|
|
164
|
-
if (matchesAnyPrefix(modelWithoutPrefix, OPENAI_200K_PREFIXES)) {
|
|
165
|
-
return 200_000;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check OpenAI 128K models
|
|
169
|
-
if (matchesAnyPrefix(modelWithoutPrefix, OPENAI_128K_PREFIXES)) {
|
|
170
|
-
return 128_000;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Claude Sonnet 5 dated snapshot variants (e.g. claude-sonnet-5-YYYYMMDD)
|
|
174
|
-
// are not individually exact-keyed above; resolve them to the same
|
|
175
|
-
// subscription-safe 200K default as the bare alias and -latest. Mirrors
|
|
176
|
-
// getContextWindowForModel in the anthropic package.
|
|
177
|
-
if (modelWithoutPrefix.toLowerCase().includes('claude-sonnet-5')) {
|
|
178
|
-
return 200_000;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Claude Opus 5 dated snapshot variants (e.g. claude-opus-5-YYYYMMDD)
|
|
182
|
-
// resolve to the same subscription-safe 200K default.
|
|
183
|
-
if (modelWithoutPrefix.toLowerCase().includes('claude-opus-5')) {
|
|
184
|
-
return 200_000;
|
|
107
|
+
for (const { prefix, limit } of CATALOG.prefixLimits) {
|
|
108
|
+
if (modelWithoutPrefix.startsWith(prefix)) {
|
|
109
|
+
return limit;
|
|
110
|
+
}
|
|
185
111
|
}
|
|
186
112
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
return
|
|
113
|
+
// Check ordered data-driven rules (codex, OpenAI groups, Claude substrings)
|
|
114
|
+
const orderedLimit = resolveOrderedRule(modelWithoutPrefix, providerPrefix);
|
|
115
|
+
if (orderedLimit !== undefined) {
|
|
116
|
+
return orderedLimit;
|
|
191
117
|
}
|
|
192
118
|
|
|
193
119
|
return DEFAULT_TOKEN_LIMIT;
|
package/src/core/turn.ts
CHANGED
|
@@ -199,10 +199,19 @@ export enum CompressionStatus {
|
|
|
199
199
|
/** The compression failed due to an error counting tokens */
|
|
200
200
|
COMPRESSION_FAILED_TOKEN_COUNT_ERROR = 3,
|
|
201
201
|
|
|
202
|
-
/**
|
|
203
|
-
|
|
202
|
+
/**
|
|
203
|
+
* The compression failed because the model returned an empty summary.
|
|
204
|
+
* Explicit value to avoid colliding with NOOP (issue #2602).
|
|
205
|
+
*/
|
|
206
|
+
COMPRESSION_FAILED_EMPTY_SUMMARY = 7,
|
|
204
207
|
|
|
205
|
-
/**
|
|
208
|
+
/**
|
|
209
|
+
* Structural compression was a no-op: the strategy determined it could not
|
|
210
|
+
* produce a valid compressed candidate (e.g. no valid middle after split /
|
|
211
|
+
* tool-boundary / last-user-prompt preservation) and returned the history
|
|
212
|
+
* unchanged. Distinct serialized value from COMPRESSION_FAILED_EMPTY_SUMMARY
|
|
213
|
+
* (issue #2602).
|
|
214
|
+
*/
|
|
206
215
|
NOOP = 4,
|
|
207
216
|
|
|
208
217
|
/** Compression ran recently and did not reduce tokens further */
|
|
@@ -224,6 +233,14 @@ export enum PerformCompressionResult {
|
|
|
224
233
|
SKIPPED_EMPTY = 'skipped_empty',
|
|
225
234
|
/** Compression skipped due to cooldown after repeated failures */
|
|
226
235
|
SKIPPED_COOLDOWN = 'skipped_cooldown',
|
|
236
|
+
/**
|
|
237
|
+
* Structural compression was a no-op: the configured strategy (and, for
|
|
238
|
+
* middle-out, the one-shot fallback) determined it could not produce a valid
|
|
239
|
+
* compressed candidate and returned history unchanged. Distinct from skips
|
|
240
|
+
* (which never attempted) and from FAILED (which attempted and threw).
|
|
241
|
+
* (issue #2602)
|
|
242
|
+
*/
|
|
243
|
+
NOOP = 'noop',
|
|
227
244
|
/** Compression was attempted but all strategies failed */
|
|
228
245
|
FAILED = 'failed',
|
|
229
246
|
}
|
|
@@ -55,9 +55,11 @@ export class HookRegistry {
|
|
|
55
55
|
/**
|
|
56
56
|
* Initialize the registry by processing hooks from config
|
|
57
57
|
*/
|
|
58
|
-
async initialize(): Promise<void> {
|
|
58
|
+
async initialize(signal?: AbortSignal): Promise<void> {
|
|
59
|
+
signal?.throwIfAborted();
|
|
59
60
|
this.entries = [];
|
|
60
61
|
this.processHooksFromConfig();
|
|
62
|
+
signal?.throwIfAborted();
|
|
61
63
|
|
|
62
64
|
debugLogger.log(
|
|
63
65
|
`Hook registry initialized with ${this.entries.length} hook entries`,
|
package/src/hooks/hookRunner.ts
CHANGED
|
@@ -299,7 +299,7 @@ export class HookRunner {
|
|
|
299
299
|
|
|
300
300
|
const child = this.spawnHookProcess(hookConfig, input);
|
|
301
301
|
|
|
302
|
-
const
|
|
302
|
+
const timeoutControl = this.setupKillTimeout(child, timeout, () => {
|
|
303
303
|
timedOut = true;
|
|
304
304
|
});
|
|
305
305
|
|
|
@@ -314,7 +314,7 @@ export class HookRunner {
|
|
|
314
314
|
});
|
|
315
315
|
|
|
316
316
|
child.on('close', (exitCode) => {
|
|
317
|
-
|
|
317
|
+
timeoutControl.clear();
|
|
318
318
|
const duration = Date.now() - startTime;
|
|
319
319
|
if (timedOut) {
|
|
320
320
|
resolve(
|
|
@@ -342,7 +342,7 @@ export class HookRunner {
|
|
|
342
342
|
});
|
|
343
343
|
|
|
344
344
|
child.on('error', (error) => {
|
|
345
|
-
|
|
345
|
+
timeoutControl.clear();
|
|
346
346
|
resolve(
|
|
347
347
|
this.errorResult(
|
|
348
348
|
hookConfig,
|
|
@@ -373,20 +373,37 @@ export class HookRunner {
|
|
|
373
373
|
};
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Sends SIGTERM after `timeout`, then escalates to SIGKILL if the child is
|
|
378
|
+
* still running. Do not use `child.killed` for escalation — Node sets that
|
|
379
|
+
* when a signal is *sent*, not when the process exits, so SIGTERM-ignoring
|
|
380
|
+
* hooks would never receive SIGKILL and could hang forever.
|
|
381
|
+
*/
|
|
376
382
|
private setupKillTimeout(
|
|
377
383
|
child: ReturnType<typeof spawn>,
|
|
378
384
|
timeout: number,
|
|
379
385
|
onTimeout: () => void,
|
|
380
|
-
):
|
|
381
|
-
|
|
386
|
+
): { clear: () => void } {
|
|
387
|
+
let forceKillHandle: NodeJS.Timeout | undefined;
|
|
388
|
+
const timeoutHandle = setTimeout(() => {
|
|
382
389
|
onTimeout();
|
|
383
390
|
child.kill('SIGTERM');
|
|
384
|
-
setTimeout(() => {
|
|
385
|
-
|
|
391
|
+
forceKillHandle = setTimeout(() => {
|
|
392
|
+
// Still running only when both exit indicators remain null.
|
|
393
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
386
394
|
child.kill('SIGKILL');
|
|
387
395
|
}
|
|
388
396
|
}, 5000);
|
|
389
397
|
}, timeout);
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
clear: () => {
|
|
401
|
+
clearTimeout(timeoutHandle);
|
|
402
|
+
if (forceKillHandle !== undefined) {
|
|
403
|
+
clearTimeout(forceKillHandle);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
};
|
|
390
407
|
}
|
|
391
408
|
|
|
392
409
|
private timeoutResult(
|
package/src/hooks/hookSystem.ts
CHANGED
|
@@ -53,6 +53,14 @@ export class HookSystem {
|
|
|
53
53
|
private readonly runner: HookRunner;
|
|
54
54
|
private readonly aggregator: HookAggregator;
|
|
55
55
|
private eventHandler: HookEventHandler | null = null;
|
|
56
|
+
private initializationPromise: Promise<void> | undefined;
|
|
57
|
+
private initializationGeneration = 0;
|
|
58
|
+
private readonly initializationSignals = new Map<
|
|
59
|
+
number,
|
|
60
|
+
AbortSignal | undefined
|
|
61
|
+
>();
|
|
62
|
+
private lifecycleGeneration = 0;
|
|
63
|
+
private readonly disposalController = new AbortController();
|
|
56
64
|
|
|
57
65
|
/**
|
|
58
66
|
* @plan PLAN-20250218-HOOKSYSTEM.P03
|
|
@@ -97,22 +105,65 @@ export class HookSystem {
|
|
|
97
105
|
* @requirement:HOOK-003 - Calls HookRegistry.initialize() to load hooks from config
|
|
98
106
|
* @requirement:HOOK-008 - Called by trigger functions on first event fire
|
|
99
107
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
initialize(signal?: AbortSignal): Promise<void> {
|
|
109
|
+
if (this.lifecycleGeneration > 0) {
|
|
110
|
+
return Promise.reject(new Error('HookSystem has been disposed.'));
|
|
111
|
+
}
|
|
112
|
+
if (signal?.aborted === true) {
|
|
113
|
+
return Promise.reject(signal.reason);
|
|
114
|
+
}
|
|
115
|
+
const generation = ++this.initializationGeneration;
|
|
116
|
+
this.initializationSignals.set(generation, signal);
|
|
117
|
+
if (this.initializationPromise !== undefined) {
|
|
118
|
+
return this.initializationPromise;
|
|
119
|
+
}
|
|
120
|
+
const initialization = this.runInitializationGenerations().finally(() => {
|
|
121
|
+
if (this.initializationPromise === initialization) {
|
|
122
|
+
this.initializationPromise = undefined;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
this.initializationPromise = initialization;
|
|
126
|
+
return initialization;
|
|
127
|
+
}
|
|
102
128
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
private async runInitializationGenerations(): Promise<void> {
|
|
130
|
+
let completedGeneration = 0;
|
|
131
|
+
while (completedGeneration < this.initializationGeneration) {
|
|
132
|
+
const generation = this.initializationGeneration;
|
|
133
|
+
const requestedSignal = this.initializationSignals.get(generation);
|
|
134
|
+
const signal = requestedSignal
|
|
135
|
+
? AbortSignal.any([requestedSignal, this.disposalController.signal])
|
|
136
|
+
: this.disposalController.signal;
|
|
137
|
+
try {
|
|
138
|
+
await this.initializeGeneration(signal);
|
|
139
|
+
completedGeneration = generation;
|
|
140
|
+
} finally {
|
|
141
|
+
this.deleteInitializationSignalsThrough(generation);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
110
145
|
|
|
111
|
-
|
|
112
|
-
|
|
146
|
+
private deleteInitializationSignalsThrough(generation: number): void {
|
|
147
|
+
for (const queuedGeneration of this.initializationSignals.keys()) {
|
|
148
|
+
if (queuedGeneration <= generation) {
|
|
149
|
+
this.initializationSignals.delete(queuedGeneration);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
113
153
|
|
|
114
|
-
|
|
115
|
-
|
|
154
|
+
private async initializeGeneration(signal?: AbortSignal): Promise<void> {
|
|
155
|
+
const lifecycleGeneration = this.lifecycleGeneration;
|
|
156
|
+
if (lifecycleGeneration > 0) {
|
|
157
|
+
throw new Error('HookSystem has been disposed.');
|
|
158
|
+
}
|
|
159
|
+
signal?.throwIfAborted();
|
|
160
|
+
debugLogger.debug('Initializing HookSystem');
|
|
161
|
+
this.disposeEventHandler();
|
|
162
|
+
await this.registry.initialize(signal);
|
|
163
|
+
if (lifecycleGeneration !== this.lifecycleGeneration) {
|
|
164
|
+
throw new Error('HookSystem has been disposed.');
|
|
165
|
+
}
|
|
166
|
+
signal?.throwIfAborted();
|
|
116
167
|
this.eventHandler = new HookEventHandler(
|
|
117
168
|
this.config,
|
|
118
169
|
this.registry,
|
|
@@ -387,6 +438,15 @@ export class HookSystem {
|
|
|
387
438
|
* @requirement DELTA-HEVT-004
|
|
388
439
|
*/
|
|
389
440
|
dispose(): void {
|
|
441
|
+
this.lifecycleGeneration++;
|
|
442
|
+
this.initializationGeneration++;
|
|
443
|
+
this.disposalController.abort(new Error('HookSystem has been disposed.'));
|
|
444
|
+
this.initializationSignals.clear();
|
|
445
|
+
this.disposeEventHandler();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private disposeEventHandler(): void {
|
|
390
449
|
this.eventHandler?.dispose();
|
|
450
|
+
this.eventHandler = null;
|
|
391
451
|
}
|
|
392
452
|
}
|
package/src/index.ts
CHANGED
|
@@ -146,6 +146,7 @@ export {
|
|
|
146
146
|
} from './utils/generateContentResponseUtilities.js';
|
|
147
147
|
export * from './utils/filesearch/fileSearch.js';
|
|
148
148
|
export * from './utils/secure-browser-launcher.js';
|
|
149
|
+
export * from './utils/browser-profile-discovery.js';
|
|
149
150
|
export * from './utils/errorParsing.js';
|
|
150
151
|
export * from './utils/ignorePatterns.js';
|
|
151
152
|
export { INITIAL_HISTORY_LENGTH } from './utils/environmentContext.js';
|
|
@@ -61,6 +61,9 @@ export interface GeminiPartExtension {
|
|
|
61
61
|
* May also contain arbitrary user-configured field names (issue #2488).
|
|
62
62
|
*/
|
|
63
63
|
llxprtSourceField?: string;
|
|
64
|
+
llxprtThoughtBlockId?: string;
|
|
65
|
+
llxprtThoughtBlockStatus?: 'delta' | 'complete';
|
|
66
|
+
llxprtThoughtIsHidden?: boolean;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
package/src/policy/config.ts
CHANGED
|
@@ -78,13 +78,15 @@ export async function createPolicyEngineConfig(
|
|
|
78
78
|
export async function createPolicyEngineConfig(
|
|
79
79
|
settings: PolicySettings,
|
|
80
80
|
approvalMode: ApprovalMode,
|
|
81
|
-
defaultPoliciesDir
|
|
81
|
+
defaultPoliciesDir: string | undefined,
|
|
82
|
+
trustedFolder: boolean,
|
|
82
83
|
): Promise<PolicyEngineConfig>;
|
|
83
84
|
|
|
84
85
|
export async function createPolicyEngineConfig(
|
|
85
86
|
configOrSettings: PolicyConfigSource | PolicySettings,
|
|
86
87
|
approvalModeParam?: ApprovalMode,
|
|
87
88
|
defaultPoliciesDir?: string,
|
|
89
|
+
trustedFolder?: boolean,
|
|
88
90
|
): Promise<PolicyEngineConfig> {
|
|
89
91
|
const isPolicyConfigSource =
|
|
90
92
|
typeof (configOrSettings as PolicyConfigSource).getApprovalMode ===
|
|
@@ -97,6 +99,7 @@ export async function createPolicyEngineConfig(
|
|
|
97
99
|
configOrSettings as PolicySettings,
|
|
98
100
|
approvalModeParam!,
|
|
99
101
|
defaultPoliciesDir,
|
|
102
|
+
trustedFolder ?? false,
|
|
100
103
|
);
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -150,7 +153,8 @@ async function loadUserPolicyRules(
|
|
|
150
153
|
async function buildSettingsRules(
|
|
151
154
|
settings: PolicySettings,
|
|
152
155
|
approvalMode: ApprovalMode,
|
|
153
|
-
defaultPoliciesDir
|
|
156
|
+
defaultPoliciesDir: string | undefined,
|
|
157
|
+
trustedFolder: boolean,
|
|
154
158
|
): Promise<PolicyEngineConfig> {
|
|
155
159
|
const policyDirs = getPolicyDirectories(defaultPoliciesDir);
|
|
156
160
|
|
|
@@ -167,7 +171,9 @@ async function buildSettingsRules(
|
|
|
167
171
|
addMcpExcludedRules(settings, rules);
|
|
168
172
|
addToolsExcludedRules(settings, rules);
|
|
169
173
|
addToolsAllowedRules(settings, rules);
|
|
170
|
-
|
|
174
|
+
if (trustedFolder) {
|
|
175
|
+
addMcpTrustedRules(settings, rules);
|
|
176
|
+
}
|
|
171
177
|
addMcpAllowedRules(settings, rules);
|
|
172
178
|
|
|
173
179
|
return {
|
|
@@ -194,7 +200,7 @@ function addMcpExcludedRules(
|
|
|
194
200
|
if (settings.mcp?.excluded) {
|
|
195
201
|
for (const serverName of settings.mcp.excluded) {
|
|
196
202
|
rules.push({
|
|
197
|
-
|
|
203
|
+
toolNamePrefix: `${serverName}__`,
|
|
198
204
|
decision: PolicyDecision.DENY,
|
|
199
205
|
priority: 2.9,
|
|
200
206
|
source: 'Settings (MCP Excluded)',
|
|
@@ -293,24 +299,31 @@ function normalizeToolName(toolName: string): string {
|
|
|
293
299
|
return toolName;
|
|
294
300
|
}
|
|
295
301
|
|
|
302
|
+
export const MCP_TRUSTED_POLICY_SOURCE = 'Settings (MCP Trusted)';
|
|
303
|
+
|
|
304
|
+
export function buildMcpTrustedRules(
|
|
305
|
+
settings: PolicySettings,
|
|
306
|
+
): readonly PolicyRule[] {
|
|
307
|
+
return Object.entries(settings.mcpServers ?? {}).flatMap(
|
|
308
|
+
([serverName, serverConfig]) =>
|
|
309
|
+
serverConfig.trust === true
|
|
310
|
+
? [
|
|
311
|
+
{
|
|
312
|
+
toolNamePrefix: `${serverName}__`,
|
|
313
|
+
decision: PolicyDecision.ALLOW,
|
|
314
|
+
priority: 2.2,
|
|
315
|
+
source: MCP_TRUSTED_POLICY_SOURCE,
|
|
316
|
+
},
|
|
317
|
+
]
|
|
318
|
+
: [],
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
296
322
|
function addMcpTrustedRules(
|
|
297
323
|
settings: PolicySettings,
|
|
298
324
|
rules: PolicyRule[],
|
|
299
325
|
): void {
|
|
300
|
-
|
|
301
|
-
for (const [serverName, serverConfig] of Object.entries(
|
|
302
|
-
settings.mcpServers,
|
|
303
|
-
)) {
|
|
304
|
-
if (serverConfig.trust === true) {
|
|
305
|
-
rules.push({
|
|
306
|
-
toolName: `${serverName}__*`,
|
|
307
|
-
decision: PolicyDecision.ALLOW,
|
|
308
|
-
priority: 2.2,
|
|
309
|
-
source: 'Settings (MCP Trusted)',
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
326
|
+
rules.push(...buildMcpTrustedRules(settings));
|
|
314
327
|
}
|
|
315
328
|
|
|
316
329
|
function addMcpAllowedRules(
|
|
@@ -320,7 +333,7 @@ function addMcpAllowedRules(
|
|
|
320
333
|
if (settings.mcp?.allowed) {
|
|
321
334
|
for (const serverName of settings.mcp.allowed) {
|
|
322
335
|
rules.push({
|
|
323
|
-
|
|
336
|
+
toolNamePrefix: `${serverName}__`,
|
|
324
337
|
decision: PolicyDecision.ALLOW,
|
|
325
338
|
priority: 2.1,
|
|
326
339
|
source: 'Settings (MCP Allowed)',
|