@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/recording/types.ts
CHANGED
|
@@ -25,12 +25,14 @@
|
|
|
25
25
|
|
|
26
26
|
import { type IContent } from '../services/history/IContent.js';
|
|
27
27
|
|
|
28
|
+
export const SESSION_TITLE_MAX_LENGTH = 120;
|
|
29
|
+
|
|
28
30
|
// ---------------------------------------------------------------------------
|
|
29
31
|
// Event type discriminator
|
|
30
32
|
// ---------------------------------------------------------------------------
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* The
|
|
35
|
+
* The event types that can appear in a session JSONL file.
|
|
34
36
|
*/
|
|
35
37
|
export type SessionEventType =
|
|
36
38
|
| 'session_start'
|
|
@@ -39,6 +41,7 @@ export type SessionEventType =
|
|
|
39
41
|
| 'rewind'
|
|
40
42
|
| 'provider_switch'
|
|
41
43
|
| 'session_event'
|
|
44
|
+
| 'session_metadata'
|
|
42
45
|
| 'directories_changed';
|
|
43
46
|
|
|
44
47
|
// ---------------------------------------------------------------------------
|
|
@@ -74,6 +77,8 @@ export interface SessionStartPayload {
|
|
|
74
77
|
sessionId: string;
|
|
75
78
|
projectHash: string;
|
|
76
79
|
workspaceDirs: string[];
|
|
80
|
+
/** Effective absolute working directory, when recorded by newer clients. */
|
|
81
|
+
cwd?: string;
|
|
77
82
|
provider: string;
|
|
78
83
|
model: string;
|
|
79
84
|
/** ISO-8601 timestamp of when the session started. */
|
|
@@ -122,6 +127,19 @@ export interface SessionEventPayload {
|
|
|
122
127
|
message: string;
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Payload for the `session_metadata` event — session-level metadata updates
|
|
132
|
+
* (currently only the human-readable title). The title is tri-state:
|
|
133
|
+
* - `undefined` (field absent): legacy event, no title assertion.
|
|
134
|
+
* - `null`: explicit untitled (the caller asserts there is no meaningful title).
|
|
135
|
+
* - `string`: a concrete title.
|
|
136
|
+
*
|
|
137
|
+
* This is a typed first-class recording event, NOT a `session_event` sentinel.
|
|
138
|
+
*/
|
|
139
|
+
export interface SessionMetadataPayload {
|
|
140
|
+
readonly title?: string | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
/**
|
|
126
144
|
* Payload for the `directories_changed` event.
|
|
127
145
|
*/
|
|
@@ -141,6 +159,8 @@ export interface SessionRecordingServiceConfig {
|
|
|
141
159
|
projectHash: string;
|
|
142
160
|
chatsDir: string;
|
|
143
161
|
workspaceDirs: string[];
|
|
162
|
+
/** Effective absolute working directory for lifecycle discovery. */
|
|
163
|
+
cwd?: string;
|
|
144
164
|
provider: string;
|
|
145
165
|
model: string;
|
|
146
166
|
}
|
|
@@ -151,7 +171,14 @@ export interface SessionRecordingServiceConfig {
|
|
|
151
171
|
|
|
152
172
|
/**
|
|
153
173
|
* Metadata extracted from a session's `session_start` event and updated
|
|
154
|
-
* by subsequent `provider_switch` / `directories_changed`
|
|
174
|
+
* by subsequent `provider_switch` / `directories_changed` / `session_metadata`
|
|
175
|
+
* events.
|
|
176
|
+
*
|
|
177
|
+
* The title is tri-state:
|
|
178
|
+
* - `undefined`: no `session_metadata` event seen (legacy file). Callers fall
|
|
179
|
+
* back to first-human-text heuristics.
|
|
180
|
+
* - `null`: explicit untitled (`session_metadata` asserted no title).
|
|
181
|
+
* - `string`: a concrete title.
|
|
155
182
|
*/
|
|
156
183
|
export interface SessionMetadata {
|
|
157
184
|
sessionId: string;
|
|
@@ -159,7 +186,9 @@ export interface SessionMetadata {
|
|
|
159
186
|
provider: string;
|
|
160
187
|
model: string;
|
|
161
188
|
workspaceDirs: string[];
|
|
189
|
+
cwd?: string;
|
|
162
190
|
startTime: string;
|
|
191
|
+
title?: string | null;
|
|
163
192
|
}
|
|
164
193
|
|
|
165
194
|
/**
|
|
@@ -199,4 +228,17 @@ export interface SessionSummary {
|
|
|
199
228
|
fileSize: number;
|
|
200
229
|
provider: string;
|
|
201
230
|
model: string;
|
|
231
|
+
cwd?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Tri-state title from a persisted `session_metadata` event:
|
|
234
|
+
* - `undefined`: no `session_metadata` event seen (legacy fallback).
|
|
235
|
+
* - `null`: explicit untitled.
|
|
236
|
+
* - `string`: a concrete title.
|
|
237
|
+
*/
|
|
238
|
+
title?: string | null;
|
|
239
|
+
/**
|
|
240
|
+
* Immutable creation timestamp extracted from `session_start.startTime`.
|
|
241
|
+
* Used for stable ordering independent of file `lastModified` mutations.
|
|
242
|
+
*/
|
|
243
|
+
createdAt?: string;
|
|
202
244
|
}
|
|
@@ -109,6 +109,7 @@ function hydrateContentGeneratorConfig(
|
|
|
109
109
|
|
|
110
110
|
type ToolGovernance = {
|
|
111
111
|
allowed: Set<string>;
|
|
112
|
+
allowedExplicit: boolean;
|
|
112
113
|
disabled: Set<string>;
|
|
113
114
|
excluded: Set<string>;
|
|
114
115
|
};
|
|
@@ -128,6 +129,7 @@ function buildToolGovernance(
|
|
|
128
129
|
allowed: new Set(
|
|
129
130
|
(allowedRaw ?? []).map((tool) => normalizeToolName(tool) ?? tool),
|
|
130
131
|
),
|
|
132
|
+
allowedExplicit: allowedRaw !== undefined,
|
|
131
133
|
disabled: new Set(
|
|
132
134
|
(disabledRaw ?? []).map((tool) => normalizeToolName(tool) ?? tool),
|
|
133
135
|
),
|
|
@@ -148,7 +150,7 @@ function isToolPermitted(
|
|
|
148
150
|
if (governance.disabled.has(canonical)) {
|
|
149
151
|
return false;
|
|
150
152
|
}
|
|
151
|
-
if (governance.
|
|
153
|
+
if (governance.allowedExplicit && !governance.allowed.has(canonical)) {
|
|
152
154
|
return false;
|
|
153
155
|
}
|
|
154
156
|
return true;
|
|
@@ -4,36 +4,39 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
AnsiOutput,
|
|
9
|
+
LiveOutputUpdate,
|
|
10
|
+
} from '../utils/terminalSerializer.js';
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
|
-
* Accumulates a live-output
|
|
13
|
+
* Accumulates a live-output update onto an existing accumulated value using
|
|
14
|
+
* the explicit tagged {@link LiveOutputUpdate} protocol.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - **`
|
|
16
|
-
* Each chunk supersedes the previous one, so the latest snapshot replaces
|
|
17
|
-
* the old value.
|
|
18
|
-
* - **`string`** (e.g. the task tool streaming subagent text) — an incremental
|
|
19
|
-
* delta. Each chunk must be appended to the existing text so the display
|
|
20
|
-
* grows rather than showing only the latest token.
|
|
16
|
+
* - **`append`** (text deltas) — concatenated onto the existing value when it
|
|
17
|
+
* is a string; otherwise the prior non-string value is dropped and the
|
|
18
|
+
* delta starts a fresh string.
|
|
19
|
+
* - **`replace`** (terminal-buffer snapshots) — supersedes any prior value.
|
|
21
20
|
*
|
|
22
21
|
* The `existing` parameter is typed `unknown` because callers may pass values
|
|
23
22
|
* drawn from a broader display union (e.g. `ToolResultDisplay`, which also
|
|
24
|
-
* includes `FileDiff` / `FileRead`).
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Note: the append-vs-replace distinction is currently inferred from the
|
|
28
|
-
* payload type. Migrating to an explicit tagged update protocol (e.g.
|
|
29
|
-
* `{ mode: 'append'; data: string } | { mode: 'replace'; data: AnsiOutput }`)
|
|
30
|
-
* is tracked in follow-up #2586.
|
|
23
|
+
* includes `FileDiff` / `FileRead`). The returned type is the narrow
|
|
24
|
+
* `string | AnsiOutput` union the live-output channel actually produces.
|
|
31
25
|
*/
|
|
32
26
|
export function accumulateLiveOutput(
|
|
33
27
|
existing: unknown,
|
|
34
|
-
|
|
28
|
+
update: LiveOutputUpdate,
|
|
35
29
|
): string | AnsiOutput {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
switch (update.mode) {
|
|
31
|
+
case 'append':
|
|
32
|
+
return typeof existing === 'string'
|
|
33
|
+
? existing + update.data
|
|
34
|
+
: update.data;
|
|
35
|
+
case 'replace':
|
|
36
|
+
return update.data;
|
|
37
|
+
default: {
|
|
38
|
+
const _exhaustive: never = update;
|
|
39
|
+
return _exhaustive;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
39
42
|
}
|
package/src/scheduler/types.ts
CHANGED
|
@@ -22,7 +22,10 @@ import type {
|
|
|
22
22
|
ToolCallResponseInfo,
|
|
23
23
|
} from '../core/turn.js';
|
|
24
24
|
import type { ToolConfirmationOutcome } from '@vybestack/llxprt-code-tools';
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
AnsiOutput,
|
|
27
|
+
LiveOutputUpdate,
|
|
28
|
+
} from '../utils/terminalSerializer.js';
|
|
26
29
|
import type { SerializableConfirmationDetails } from '../confirmation-bus/types.js';
|
|
27
30
|
|
|
28
31
|
/**
|
|
@@ -142,7 +145,7 @@ export type ConfirmHandler = (
|
|
|
142
145
|
|
|
143
146
|
export type OutputUpdateHandler = (
|
|
144
147
|
toolCallId: string,
|
|
145
|
-
|
|
148
|
+
update: LiveOutputUpdate,
|
|
146
149
|
) => void;
|
|
147
150
|
|
|
148
151
|
export type AllToolCallsCompleteHandler = (
|
|
@@ -73,77 +73,92 @@ export class ContentConverters {
|
|
|
73
73
|
return 'model';
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
private static toolCallBlockToPart(
|
|
77
|
+
toolCall: Extract<ContentBlock, { type: 'tool_call' }>,
|
|
78
|
+
): GeminiContentPart {
|
|
79
|
+
this.logger.debug('Converting tool_call block to functionCall:', {
|
|
80
|
+
id: toolCall.id,
|
|
81
|
+
name: toolCall.name,
|
|
82
|
+
hasParameters: ContentConverters.hasLegacyTruthyValue(
|
|
83
|
+
toolCall.parameters,
|
|
84
|
+
),
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
functionCall: {
|
|
88
|
+
name: toolCall.name,
|
|
89
|
+
args: toolCall.parameters as Record<string, unknown>,
|
|
90
|
+
id: toolCall.id,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private static toolResponseBlockToPart(
|
|
96
|
+
toolResponse: Extract<ContentBlock, { type: 'tool_response' }>,
|
|
97
|
+
): GeminiContentPart {
|
|
98
|
+
this.logger.debug('Converting tool_response block to functionResponse:', {
|
|
99
|
+
callId: toolResponse.callId,
|
|
100
|
+
toolName: toolResponse.toolName,
|
|
101
|
+
hasResult: ContentConverters.hasLegacyTruthyValue(toolResponse.result),
|
|
102
|
+
hasError: ContentConverters.hasLegacyTruthyValue(toolResponse.error),
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
functionResponse: {
|
|
106
|
+
name: toolResponse.toolName,
|
|
107
|
+
response: toolResponse.result as Record<string, unknown>,
|
|
108
|
+
id: toolResponse.callId,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static thinkingBlockToPart(
|
|
114
|
+
thinkingBlock: Extract<ContentBlock, { type: 'thinking' }>,
|
|
115
|
+
): GeminiContentPart {
|
|
116
|
+
const thinkingPart: GeminiContentPart = {
|
|
117
|
+
thought: true,
|
|
118
|
+
text: thinkingBlock.thought,
|
|
119
|
+
};
|
|
120
|
+
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.signature)) {
|
|
121
|
+
thinkingPart.thoughtSignature = thinkingBlock.signature;
|
|
122
|
+
}
|
|
123
|
+
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.sourceField)) {
|
|
124
|
+
thinkingPart.llxprtSourceField = thinkingBlock.sourceField;
|
|
125
|
+
}
|
|
126
|
+
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.streamId)) {
|
|
127
|
+
thinkingPart.llxprtThoughtBlockId = thinkingBlock.streamId;
|
|
128
|
+
}
|
|
129
|
+
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.streamStatus)) {
|
|
130
|
+
thinkingPart.llxprtThoughtBlockStatus = thinkingBlock.streamStatus;
|
|
131
|
+
}
|
|
132
|
+
if (thinkingBlock.isHidden !== undefined) {
|
|
133
|
+
thinkingPart.llxprtThoughtIsHidden = thinkingBlock.isHidden;
|
|
134
|
+
}
|
|
135
|
+
return thinkingPart;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static codeBlockToPart(
|
|
139
|
+
codeBlock: Extract<ContentBlock, { type: 'code' }>,
|
|
140
|
+
): GeminiContentPart {
|
|
141
|
+
const codeText = codeBlock.language
|
|
142
|
+
? `\`\`\`${codeBlock.language}\n${codeBlock.code}\n\`\`\``
|
|
143
|
+
: codeBlock.code;
|
|
144
|
+
return { text: codeText };
|
|
145
|
+
}
|
|
146
|
+
|
|
76
147
|
/** Convert a single IContent block to a Gemini Part. */
|
|
77
148
|
private static blockToPart(block: ContentBlock): GeminiContentPart | null {
|
|
78
149
|
switch (block.type) {
|
|
79
|
-
case 'text':
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
case '
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
hasParameters: ContentConverters.hasLegacyTruthyValue(
|
|
89
|
-
toolCall.parameters,
|
|
90
|
-
),
|
|
91
|
-
});
|
|
92
|
-
return {
|
|
93
|
-
functionCall: {
|
|
94
|
-
name: toolCall.name,
|
|
95
|
-
args: toolCall.parameters as Record<string, unknown>,
|
|
96
|
-
id: toolCall.id,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
case 'tool_response': {
|
|
101
|
-
const toolResponse = block;
|
|
102
|
-
this.logger.debug(
|
|
103
|
-
'Converting tool_response block to functionResponse:',
|
|
104
|
-
{
|
|
105
|
-
callId: toolResponse.callId,
|
|
106
|
-
toolName: toolResponse.toolName,
|
|
107
|
-
hasResult: ContentConverters.hasLegacyTruthyValue(
|
|
108
|
-
toolResponse.result,
|
|
109
|
-
),
|
|
110
|
-
hasError: ContentConverters.hasLegacyTruthyValue(
|
|
111
|
-
toolResponse.error,
|
|
112
|
-
),
|
|
113
|
-
},
|
|
114
|
-
);
|
|
115
|
-
return {
|
|
116
|
-
functionResponse: {
|
|
117
|
-
name: toolResponse.toolName,
|
|
118
|
-
response: toolResponse.result as Record<string, unknown>,
|
|
119
|
-
id: toolResponse.callId,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
case 'thinking': {
|
|
124
|
-
const thinkingBlock = block;
|
|
125
|
-
const thinkingPart: GeminiContentPart = {
|
|
126
|
-
thought: true,
|
|
127
|
-
text: thinkingBlock.thought,
|
|
128
|
-
};
|
|
129
|
-
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.signature)) {
|
|
130
|
-
thinkingPart.thoughtSignature = thinkingBlock.signature;
|
|
131
|
-
}
|
|
132
|
-
if (ContentConverters.hasLegacyTruthyValue(thinkingBlock.sourceField)) {
|
|
133
|
-
thinkingPart.llxprtSourceField = thinkingBlock.sourceField;
|
|
134
|
-
}
|
|
135
|
-
return thinkingPart;
|
|
136
|
-
}
|
|
137
|
-
case 'media': {
|
|
150
|
+
case 'text':
|
|
151
|
+
return { text: block.text };
|
|
152
|
+
case 'tool_call':
|
|
153
|
+
return this.toolCallBlockToPart(block);
|
|
154
|
+
case 'tool_response':
|
|
155
|
+
return this.toolResponseBlockToPart(block);
|
|
156
|
+
case 'thinking':
|
|
157
|
+
return this.thinkingBlockToPart(block);
|
|
158
|
+
case 'media':
|
|
138
159
|
return null;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const codeBlock = block;
|
|
142
|
-
const codeText = codeBlock.language
|
|
143
|
-
? `\`\`\`${codeBlock.language}\n${codeBlock.code}\n\`\`\``
|
|
144
|
-
: codeBlock.code;
|
|
145
|
-
return { text: codeText };
|
|
146
|
-
}
|
|
160
|
+
case 'code':
|
|
161
|
+
return this.codeBlockToPart(block);
|
|
147
162
|
default:
|
|
148
163
|
return null;
|
|
149
164
|
}
|
|
@@ -201,12 +216,20 @@ export class ContentConverters {
|
|
|
201
216
|
const thinkingBlock: ThinkingBlock = {
|
|
202
217
|
type: 'thinking',
|
|
203
218
|
thought: part.text ?? '',
|
|
204
|
-
isHidden: true,
|
|
205
219
|
sourceField,
|
|
206
220
|
};
|
|
221
|
+
if (part.llxprtThoughtIsHidden !== undefined) {
|
|
222
|
+
thinkingBlock.isHidden = part.llxprtThoughtIsHidden;
|
|
223
|
+
}
|
|
207
224
|
if (part.thoughtSignature) {
|
|
208
225
|
thinkingBlock.signature = part.thoughtSignature;
|
|
209
226
|
}
|
|
227
|
+
if (part.llxprtThoughtBlockId) {
|
|
228
|
+
thinkingBlock.streamId = part.llxprtThoughtBlockId;
|
|
229
|
+
}
|
|
230
|
+
if (part.llxprtThoughtBlockStatus) {
|
|
231
|
+
thinkingBlock.streamStatus = part.llxprtThoughtBlockStatus;
|
|
232
|
+
}
|
|
210
233
|
return thinkingBlock;
|
|
211
234
|
}
|
|
212
235
|
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type IContent,
|
|
19
|
+
type ToolCallBlock,
|
|
20
|
+
type ToolResponseBlock,
|
|
21
|
+
} from './IContent.js';
|
|
18
22
|
import { EventEmitter } from 'events';
|
|
19
23
|
// @plan:PLAN-20260603-ISSUE1584.P05 RuntimeTokenizerFactory used for injection path
|
|
20
24
|
import type { RuntimeTokenizerFactory } from '../../runtime/contracts/RuntimeTokenizerFactory.js';
|
|
@@ -437,6 +441,93 @@ export class HistoryService
|
|
|
437
441
|
await this.recalculateTotalTokens();
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Immutably replace a single tool_response block with a replacement
|
|
446
|
+
* tool_response block, preserving callId/toolName invariants.
|
|
447
|
+
*
|
|
448
|
+
* Unlike a generic block-replacement API, this method enforces structural
|
|
449
|
+
* invariants that compression/truncation callers rely on:
|
|
450
|
+
* - The target block at (entryIndex, blockIndex) MUST be a tool_response.
|
|
451
|
+
* - The replacement MUST be a tool_response.
|
|
452
|
+
* - The replacement MUST carry the same callId and toolName as the target.
|
|
453
|
+
* This prevents accidental corruption of tool-call/response pairing.
|
|
454
|
+
*/
|
|
455
|
+
async replaceToolResponseBlock(
|
|
456
|
+
entryIndex: number,
|
|
457
|
+
blockIndex: number,
|
|
458
|
+
replacement: ToolResponseBlock,
|
|
459
|
+
modelName?: string,
|
|
460
|
+
): Promise<boolean> {
|
|
461
|
+
if (
|
|
462
|
+
!Number.isInteger(entryIndex) ||
|
|
463
|
+
entryIndex < 0 ||
|
|
464
|
+
entryIndex >= this.history.length
|
|
465
|
+
) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
const entry = this.history[entryIndex];
|
|
469
|
+
if (
|
|
470
|
+
!Number.isInteger(blockIndex) ||
|
|
471
|
+
blockIndex < 0 ||
|
|
472
|
+
blockIndex >= entry.blocks.length
|
|
473
|
+
) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const target = entry.blocks[blockIndex];
|
|
478
|
+
if (target.type !== 'tool_response') {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
// Runtime invariant: the replacement MUST be a tool_response at runtime,
|
|
482
|
+
// even though the TypeScript type already constrains it. A malformed
|
|
483
|
+
// object with matching callId/toolName but wrong type (or missing type)
|
|
484
|
+
// could slip through at runtime and corrupt tool-call/response pairing.
|
|
485
|
+
const replacementType = (replacement as { type?: unknown }).type;
|
|
486
|
+
if (replacementType !== 'tool_response') {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
target.callId !== replacement.callId ||
|
|
491
|
+
target.toolName !== replacement.toolName
|
|
492
|
+
) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const newBlocks = [...entry.blocks];
|
|
497
|
+
newBlocks[blockIndex] = replacement;
|
|
498
|
+
const previousEntry = this.history[entryIndex];
|
|
499
|
+
const previousTotalTokens = this.totalTokens;
|
|
500
|
+
this.history[entryIndex] = { ...entry, blocks: newBlocks };
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
await this.recalculateTotalTokens(modelName);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
// Restore BOTH invariants: the history array AND the token accounting.
|
|
506
|
+
// recalculateTotalTokens may have already mutated this.totalTokens to
|
|
507
|
+
// reflect the replacement content before a listener/event error aborted
|
|
508
|
+
// the emit. Leaving totalTokens stale would corrupt the token budget.
|
|
509
|
+
this.history[entryIndex] = previousEntry;
|
|
510
|
+
this.totalTokens = previousTotalTokens;
|
|
511
|
+
// Best-effort notification so healthy listeners observe the rollback.
|
|
512
|
+
// A broken listener that originally caused the failure must not mask
|
|
513
|
+
// the original error.
|
|
514
|
+
try {
|
|
515
|
+
this.emit('tokensUpdated', {
|
|
516
|
+
totalTokens: this.getTotalTokens(),
|
|
517
|
+
addedTokens: 0,
|
|
518
|
+
contentId: null,
|
|
519
|
+
});
|
|
520
|
+
} catch (emitError) {
|
|
521
|
+
this.logger.debug(
|
|
522
|
+
'tokensUpdated emit during rollback failed; original error preserved',
|
|
523
|
+
emitError,
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
|
|
440
531
|
/**
|
|
441
532
|
* Return a read-only typed view of the backing history array.
|
|
442
533
|
*
|
|
@@ -55,6 +55,13 @@ export interface ContentMetadata {
|
|
|
55
55
|
/** Unique identifier for this content */
|
|
56
56
|
id?: string;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Whether this AI turn was persisted server-side (e.g. OpenAI Responses
|
|
60
|
+
* API store=true). When true the id is safe to reference via
|
|
61
|
+
* previous_response_id for stateful conversations (#207).
|
|
62
|
+
*/
|
|
63
|
+
responsesStored?: boolean;
|
|
64
|
+
|
|
58
65
|
/** Provider that generated this content */
|
|
59
66
|
provider?: string;
|
|
60
67
|
|
|
@@ -244,6 +251,12 @@ export interface ThinkingBlock {
|
|
|
244
251
|
/** Signature for Anthropic extended thinking */
|
|
245
252
|
signature?: string;
|
|
246
253
|
|
|
254
|
+
/** Provider-scoped stream identity for replacing incremental thinking updates */
|
|
255
|
+
streamId?: string;
|
|
256
|
+
|
|
257
|
+
/** Whether this block is an incremental update or the completed thinking block */
|
|
258
|
+
streamStatus?: 'delta' | 'complete';
|
|
259
|
+
|
|
247
260
|
/** Base64-encoded reasoning content (for OpenAI Codex/Responses API) */
|
|
248
261
|
encryptedContent?: string;
|
|
249
262
|
|
|
@@ -108,7 +108,17 @@ export class SkillManager {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Discovers skills from built-in, extension, user and workspace locations.
|
|
111
|
-
*
|
|
111
|
+
*
|
|
112
|
+
* Load order (later loads win for same-named skills):
|
|
113
|
+
* 1. Built-in
|
|
114
|
+
* 2. Extensions
|
|
115
|
+
* 3. User `<OS-standard-config-dir>/skills/`
|
|
116
|
+
* 4. User `~/.agents/skills/` (cross-tool standard, https://agentskills.io)
|
|
117
|
+
* 5. Workspace `.llxprt/skills/`
|
|
118
|
+
* 6. Workspace `.agents/skills/` (highest precedence)
|
|
119
|
+
*
|
|
120
|
+
* The overall Workspace > User > Extension > Built-in precedence is
|
|
121
|
+
* preserved, while `.agents/skills/` wins within each user or workspace tier.
|
|
112
122
|
*/
|
|
113
123
|
async discoverSkills(
|
|
114
124
|
storage: Storage,
|
|
@@ -143,6 +153,16 @@ export class SkillManager {
|
|
|
143
153
|
'user',
|
|
144
154
|
);
|
|
145
155
|
this.addSkillsWithPrecedence(userSkills);
|
|
156
|
+
try {
|
|
157
|
+
const userAgentSkills = await loadSkillsFromDir(
|
|
158
|
+
Storage.getUserAgentSkillsDir(),
|
|
159
|
+
'user',
|
|
160
|
+
);
|
|
161
|
+
this.addSkillsWithPrecedence(userAgentSkills);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
164
|
+
debugLogger.warn(`Skipping user .agents/skills discovery: ${reason}`);
|
|
165
|
+
}
|
|
146
166
|
|
|
147
167
|
// 4. Workspace skills (highest precedence)
|
|
148
168
|
const projectSkills = await loadSkillsFromDir(
|
|
@@ -150,6 +170,11 @@ export class SkillManager {
|
|
|
150
170
|
'project',
|
|
151
171
|
);
|
|
152
172
|
this.addSkillsWithPrecedence(projectSkills);
|
|
173
|
+
const projectAgentSkills = await loadSkillsFromDir(
|
|
174
|
+
storage.getProjectAgentSkillsDir(),
|
|
175
|
+
'project',
|
|
176
|
+
);
|
|
177
|
+
this.addSkillsWithPrecedence(projectAgentSkills);
|
|
153
178
|
}
|
|
154
179
|
|
|
155
180
|
/**
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ToolCallConfirmationDetails,
|
|
10
10
|
ToolInvocation,
|
|
11
11
|
ToolResult,
|
|
12
|
+
LiveOutputUpdate,
|
|
12
13
|
} from '@vybestack/llxprt-code-tools';
|
|
13
14
|
import {
|
|
14
15
|
BaseDeclarativeTool,
|
|
@@ -33,7 +34,7 @@ interface MockToolOptions {
|
|
|
33
34
|
execute?: (
|
|
34
35
|
params: { [key: string]: unknown },
|
|
35
36
|
signal: AbortSignal,
|
|
36
|
-
updateOutput?: (
|
|
37
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
37
38
|
) => Promise<ToolResult>;
|
|
38
39
|
params?: object;
|
|
39
40
|
messageBus?: IToolMessageBus;
|
|
@@ -64,7 +65,7 @@ class MockToolInvocation extends BaseToolInvocation<
|
|
|
64
65
|
|
|
65
66
|
execute(
|
|
66
67
|
signal: AbortSignal,
|
|
67
|
-
updateOutput?: (
|
|
68
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
68
69
|
): Promise<ToolResult> {
|
|
69
70
|
return this.tool.executeFn(this.params, signal, updateOutput);
|
|
70
71
|
}
|
|
@@ -86,7 +87,7 @@ class MockToolInvocation extends BaseToolInvocation<
|
|
|
86
87
|
type ExecuteFn = (
|
|
87
88
|
params: { [key: string]: unknown },
|
|
88
89
|
signal: AbortSignal,
|
|
89
|
-
updateOutput?: (
|
|
90
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
90
91
|
) => Promise<ToolResult>;
|
|
91
92
|
|
|
92
93
|
type ExecuteMock = ReturnType<typeof vi.fn<ExecuteFn>>;
|
package/src/test-utils/tools.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ToolCallConfirmationDetails,
|
|
13
13
|
type ToolInvocation,
|
|
14
14
|
type ToolResult,
|
|
15
|
+
type LiveOutputUpdate,
|
|
15
16
|
Kind,
|
|
16
17
|
} from '@vybestack/llxprt-code-tools';
|
|
17
18
|
import { MessageBus } from '../confirmation-bus/message-bus.js';
|
|
@@ -61,7 +62,7 @@ async function executeMockTool(
|
|
|
61
62
|
tool: { name: string; executeFn: ToolSpy },
|
|
62
63
|
params: Record<string, unknown>,
|
|
63
64
|
abortSignal: AbortSignal,
|
|
64
|
-
updateOutput?: (
|
|
65
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
65
66
|
): Promise<ToolResult> {
|
|
66
67
|
const result = await tool.executeFn(params, abortSignal, updateOutput);
|
|
67
68
|
if (isToolResultLike(result)) {
|
|
@@ -87,7 +88,7 @@ class MockToolInvocation extends BaseToolInvocation<
|
|
|
87
88
|
|
|
88
89
|
async execute(
|
|
89
90
|
abortSignal: AbortSignal,
|
|
90
|
-
updateOutput?: (
|
|
91
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
91
92
|
): Promise<ToolResult> {
|
|
92
93
|
return executeMockTool(this.tool, this.params, abortSignal, updateOutput);
|
|
93
94
|
}
|
|
@@ -168,7 +169,7 @@ export class MockModifiableToolInvocation extends BaseToolInvocation<
|
|
|
168
169
|
|
|
169
170
|
async execute(
|
|
170
171
|
abortSignal: AbortSignal,
|
|
171
|
-
updateOutput?: (
|
|
172
|
+
updateOutput?: (update: LiveOutputUpdate) => void,
|
|
172
173
|
): Promise<ToolResult> {
|
|
173
174
|
return executeMockTool(this.tool, this.params, abortSignal, updateOutput);
|
|
174
175
|
}
|
|
@@ -224,13 +224,7 @@ export class CoreShellToolHostAdapter implements IShellToolHost {
|
|
|
224
224
|
private mapOutputEvent(event: ShellOutputEvent): ToolsShellOutputEvent {
|
|
225
225
|
switch (event.type) {
|
|
226
226
|
case 'data':
|
|
227
|
-
return {
|
|
228
|
-
type: 'data',
|
|
229
|
-
chunk:
|
|
230
|
-
typeof event.chunk === 'string'
|
|
231
|
-
? event.chunk
|
|
232
|
-
: JSON.stringify(event.chunk),
|
|
233
|
-
};
|
|
227
|
+
return { type: 'data', chunk: event.chunk };
|
|
234
228
|
case 'binary_detected':
|
|
235
229
|
return { type: 'binary_detected' };
|
|
236
230
|
case 'binary_progress':
|