@vellumai/assistant 0.4.19 → 0.4.21
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/package.json +1 -1
- package/src/__tests__/system-prompt.test.ts +2 -7
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/agent/loop.ts +324 -163
- package/src/config/bundled-skills/app-builder/SKILL.md +7 -5
- package/src/config/bundled-skills/app-builder/TOOLS.json +2 -2
- package/src/config/system-prompt.ts +563 -539
- package/src/daemon/session-surfaces.ts +28 -0
- package/src/daemon/session.ts +255 -191
- package/src/daemon/tool-side-effects.ts +3 -13
- package/src/security/secure-keys.ts +27 -3
- package/src/tools/apps/definitions.ts +5 -0
- package/src/tools/apps/executors.ts +18 -22
- package/src/__tests__/response-tier.test.ts +0 -195
- package/src/daemon/response-tier.ts +0 -250
package/src/daemon/session.ts
CHANGED
|
@@ -15,81 +15,94 @@
|
|
|
15
15
|
* - session-usage.ts — recordUsage
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { AgentLoop, type ResolvedSystemPrompt } from
|
|
19
|
-
import type {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
33
|
-
import { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
34
|
-
import type { UserDecision } from '../permissions/types.js';
|
|
35
|
-
import type { Message } from '../providers/types.js';
|
|
36
|
-
import type { Provider } from '../providers/types.js';
|
|
37
|
-
import type { AuthContext } from '../runtime/auth/types.js';
|
|
38
|
-
import * as approvalOverrides from '../runtime/session-approval-overrides.js';
|
|
39
|
-
import { ToolExecutor } from '../tools/executor.js';
|
|
40
|
-
import type { AssistantAttachmentDraft } from './assistant-attachments.js';
|
|
41
|
-
import type { AssistantActivityState, ConfirmationStateChanged } from './ipc-contract/messages.js';
|
|
42
|
-
import type { ServerMessage, SurfaceData,SurfaceType, UsageStats, UserMessageAttachment } from './ipc-protocol.js';
|
|
18
|
+
import { AgentLoop, type ResolvedSystemPrompt } from "../agent/loop.js";
|
|
19
|
+
import type {
|
|
20
|
+
TurnChannelContext,
|
|
21
|
+
TurnInterfaceContext,
|
|
22
|
+
} from "../channels/types.js";
|
|
23
|
+
import { getConfig } from "../config/loader.js";
|
|
24
|
+
import { buildSystemPrompt } from "../config/system-prompt.js";
|
|
25
|
+
import { ContextWindowManager } from "../context/window-manager.js";
|
|
26
|
+
import { EventBus } from "../events/bus.js";
|
|
27
|
+
import type { AssistantDomainEvents } from "../events/domain-events.js";
|
|
28
|
+
import { createToolAuditListener } from "../events/tool-audit-listener.js";
|
|
29
|
+
import { createToolDomainEventPublisher } from "../events/tool-domain-event-publisher.js";
|
|
30
|
+
import { registerToolMetricsLoggingListener } from "../events/tool-metrics-listener.js";
|
|
31
|
+
import { registerToolNotificationListener } from "../events/tool-notification-listener.js";
|
|
43
32
|
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} from
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
33
|
+
registerToolProfilingListener,
|
|
34
|
+
ToolProfiler,
|
|
35
|
+
} from "../events/tool-profiling-listener.js";
|
|
36
|
+
import { registerToolTraceListener } from "../events/tool-trace-listener.js";
|
|
37
|
+
import { getHookManager } from "../hooks/manager.js";
|
|
38
|
+
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
39
|
+
import { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
40
|
+
import type { UserDecision } from "../permissions/types.js";
|
|
41
|
+
import type { Message } from "../providers/types.js";
|
|
42
|
+
import type { Provider } from "../providers/types.js";
|
|
43
|
+
import type { AuthContext } from "../runtime/auth/types.js";
|
|
44
|
+
import * as approvalOverrides from "../runtime/session-approval-overrides.js";
|
|
45
|
+
import { ToolExecutor } from "../tools/executor.js";
|
|
46
|
+
import type { AssistantAttachmentDraft } from "./assistant-attachments.js";
|
|
47
|
+
import type {
|
|
48
|
+
AssistantActivityState,
|
|
49
|
+
ConfirmationStateChanged,
|
|
50
|
+
} from "./ipc-contract/messages.js";
|
|
51
|
+
import type {
|
|
52
|
+
ServerMessage,
|
|
53
|
+
SurfaceData,
|
|
54
|
+
SurfaceType,
|
|
55
|
+
UsageStats,
|
|
56
|
+
UserMessageAttachment,
|
|
57
|
+
} from "./ipc-protocol.js";
|
|
58
|
+
import { runAgentLoopImpl } from "./session-agent-loop.js";
|
|
59
|
+
import { ConflictGate } from "./session-conflict-gate.js";
|
|
53
60
|
import {
|
|
54
61
|
type HistorySessionContext,
|
|
55
62
|
regenerate as regenerateImpl,
|
|
56
63
|
undo as undoImpl,
|
|
57
|
-
} from
|
|
64
|
+
} from "./session-history.js";
|
|
58
65
|
import {
|
|
59
66
|
abortSession,
|
|
60
67
|
disposeSession,
|
|
61
68
|
loadFromDb as loadFromDbImpl,
|
|
62
|
-
} from
|
|
69
|
+
} from "./session-lifecycle.js";
|
|
63
70
|
import {
|
|
64
71
|
enqueueMessage as enqueueMessageImpl,
|
|
65
72
|
persistUserMessage as persistUserMessageImpl,
|
|
66
73
|
redirectToSecurePrompt as redirectToSecurePromptImpl,
|
|
67
74
|
type RedirectToSecurePromptOptions,
|
|
68
|
-
} from
|
|
75
|
+
} from "./session-messaging.js";
|
|
69
76
|
// Extracted modules
|
|
70
|
-
import { registerSessionNotifiers } from
|
|
77
|
+
import { registerSessionNotifiers } from "./session-notifiers.js";
|
|
71
78
|
import {
|
|
72
79
|
drainQueue as drainQueueImpl,
|
|
73
80
|
processMessage as processMessageImpl,
|
|
74
81
|
type ProcessSessionContext,
|
|
75
|
-
} from
|
|
76
|
-
import type {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
} from "./session-process.js";
|
|
83
|
+
import type {
|
|
84
|
+
QueueDrainReason,
|
|
85
|
+
QueueMetrics,
|
|
86
|
+
} from "./session-queue-manager.js";
|
|
87
|
+
import { MessageQueue } from "./session-queue-manager.js";
|
|
88
|
+
import type {
|
|
89
|
+
ChannelCapabilities,
|
|
90
|
+
GuardianRuntimeContext,
|
|
91
|
+
} from "./session-runtime-assembly.js";
|
|
92
|
+
import type { SkillProjectionCache } from "./session-skill-tools.js";
|
|
80
93
|
import {
|
|
81
94
|
createSurfaceMutex,
|
|
82
95
|
handleSurfaceAction as handleSurfaceActionImpl,
|
|
83
96
|
handleSurfaceUndo as handleSurfaceUndoImpl,
|
|
84
|
-
} from
|
|
97
|
+
} from "./session-surfaces.js";
|
|
85
98
|
import {
|
|
86
99
|
buildToolDefinitions,
|
|
87
100
|
createResolveToolsCallback,
|
|
88
101
|
createToolExecutor,
|
|
89
102
|
type ToolSetupContext,
|
|
90
|
-
} from
|
|
91
|
-
import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from
|
|
92
|
-
import { TraceEmitter } from
|
|
103
|
+
} from "./session-tool-setup.js";
|
|
104
|
+
import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./session-workspace.js";
|
|
105
|
+
import { TraceEmitter } from "./trace-emitter.js";
|
|
93
106
|
|
|
94
107
|
export interface SessionMemoryPolicy {
|
|
95
108
|
scopeId: string;
|
|
@@ -97,14 +110,19 @@ export interface SessionMemoryPolicy {
|
|
|
97
110
|
strictSideEffects: boolean;
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
export const DEFAULT_MEMORY_POLICY: Readonly<SessionMemoryPolicy> =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
export const DEFAULT_MEMORY_POLICY: Readonly<SessionMemoryPolicy> =
|
|
114
|
+
Object.freeze({
|
|
115
|
+
scopeId: "default",
|
|
116
|
+
includeDefaultFallback: false,
|
|
117
|
+
strictSideEffects: false,
|
|
118
|
+
});
|
|
105
119
|
|
|
106
|
-
export { findLastUndoableUserMessageIndex } from
|
|
107
|
-
export {
|
|
120
|
+
export { findLastUndoableUserMessageIndex } from "./session-history.js";
|
|
121
|
+
export {
|
|
122
|
+
MAX_QUEUE_DEPTH,
|
|
123
|
+
type QueueDrainReason,
|
|
124
|
+
type QueuePolicy,
|
|
125
|
+
} from "./session-queue-manager.js";
|
|
108
126
|
|
|
109
127
|
export class Session {
|
|
110
128
|
public readonly conversationId: string;
|
|
@@ -128,7 +146,11 @@ export class Session {
|
|
|
128
146
|
/** @internal */ coreToolNames: Set<string>;
|
|
129
147
|
/** @internal */ readonly skillProjectionState = new Map<string, string>();
|
|
130
148
|
/** @internal */ readonly skillProjectionCache: SkillProjectionCache = {};
|
|
131
|
-
/** @internal */ usageStats: UsageStats = {
|
|
149
|
+
/** @internal */ usageStats: UsageStats = {
|
|
150
|
+
inputTokens: 0,
|
|
151
|
+
outputTokens: 0,
|
|
152
|
+
estimatedCost: 0,
|
|
153
|
+
};
|
|
132
154
|
/** @internal */ readonly systemPrompt: string;
|
|
133
155
|
/** @internal */ contextWindowManager: ContextWindowManager;
|
|
134
156
|
/** @internal */ contextCompactedMessageCount = 0;
|
|
@@ -145,18 +167,41 @@ export class Session {
|
|
|
145
167
|
/** @internal */ channelCapabilities?: ChannelCapabilities;
|
|
146
168
|
/** @internal */ guardianContext?: GuardianRuntimeContext;
|
|
147
169
|
/** @internal */ authContext?: AuthContext;
|
|
148
|
-
/** @internal */ loadedHistoryTrustClass?: GuardianRuntimeContext[
|
|
170
|
+
/** @internal */ loadedHistoryTrustClass?: GuardianRuntimeContext["trustClass"];
|
|
149
171
|
/** @internal */ voiceCallControlPrompt?: string;
|
|
150
172
|
/** @internal */ assistantId?: string;
|
|
151
|
-
/** @internal */ commandIntent?: {
|
|
173
|
+
/** @internal */ commandIntent?: {
|
|
174
|
+
type: string;
|
|
175
|
+
payload?: string;
|
|
176
|
+
languageCode?: string;
|
|
177
|
+
};
|
|
152
178
|
/** @internal */ surfaceActionRequestIds = new Set<string>();
|
|
153
|
-
/** @internal */ pendingSurfaceActions = new Map<
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
/** @internal */ pendingSurfaceActions = new Map<
|
|
180
|
+
string,
|
|
181
|
+
{ surfaceType: SurfaceType }
|
|
182
|
+
>();
|
|
183
|
+
/** @internal */ lastSurfaceAction = new Map<
|
|
184
|
+
string,
|
|
185
|
+
{ actionId: string; data?: Record<string, unknown> }
|
|
186
|
+
>();
|
|
187
|
+
/** @internal */ surfaceState = new Map<
|
|
188
|
+
string,
|
|
189
|
+
{ surfaceType: SurfaceType; data: SurfaceData; title?: string }
|
|
190
|
+
>();
|
|
156
191
|
/** @internal */ surfaceUndoStacks = new Map<string, string[]>();
|
|
157
192
|
/** @internal */ withSurface = createSurfaceMutex();
|
|
158
|
-
/** @internal */ currentTurnSurfaces: Array<{
|
|
159
|
-
|
|
193
|
+
/** @internal */ currentTurnSurfaces: Array<{
|
|
194
|
+
surfaceId: string;
|
|
195
|
+
surfaceType: SurfaceType;
|
|
196
|
+
title?: string;
|
|
197
|
+
data: SurfaceData;
|
|
198
|
+
actions?: Array<{ id: string; label: string; style?: string }>;
|
|
199
|
+
display?: string;
|
|
200
|
+
}> = [];
|
|
201
|
+
/** @internal */ onEscalateToComputerUse?: (
|
|
202
|
+
task: string,
|
|
203
|
+
sourceSessionId: string,
|
|
204
|
+
) => boolean;
|
|
160
205
|
/** @internal */ workspaceTopLevelContext: string | null = null;
|
|
161
206
|
/** @internal */ workspaceTopLevelDirty = true;
|
|
162
207
|
public readonly traceEmitter: TraceEmitter;
|
|
@@ -166,7 +211,8 @@ export class Session {
|
|
|
166
211
|
public lastAssistantAttachments: AssistantAttachmentDraft[] = [];
|
|
167
212
|
public lastAttachmentWarnings: string[] = [];
|
|
168
213
|
/** @internal */ currentTurnChannelContext: TurnChannelContext | null = null;
|
|
169
|
-
/** @internal */ currentTurnInterfaceContext: TurnInterfaceContext | null =
|
|
214
|
+
/** @internal */ currentTurnInterfaceContext: TurnInterfaceContext | null =
|
|
215
|
+
null;
|
|
170
216
|
/** @internal */ activityVersion = 0;
|
|
171
217
|
/**
|
|
172
218
|
* Optional callback invoked whenever a server-authoritative state signal
|
|
@@ -193,7 +239,9 @@ export class Session {
|
|
|
193
239
|
this.provider = provider;
|
|
194
240
|
this.workingDir = workingDir;
|
|
195
241
|
this.sendToClient = sendToClient;
|
|
196
|
-
this.memoryPolicy = memoryPolicy
|
|
242
|
+
this.memoryPolicy = memoryPolicy
|
|
243
|
+
? { ...memoryPolicy }
|
|
244
|
+
: { ...DEFAULT_MEMORY_POLICY };
|
|
197
245
|
this.traceEmitter = new TraceEmitter(conversationId, sendToClient);
|
|
198
246
|
this.prompter = new PermissionPrompter(sendToClient);
|
|
199
247
|
this.prompter.setOnStateChanged((requestId, state, source) => {
|
|
@@ -206,10 +254,20 @@ export class Session {
|
|
|
206
254
|
source,
|
|
207
255
|
});
|
|
208
256
|
// Emit activity state transitions for confirmation lifecycle
|
|
209
|
-
if (state ===
|
|
210
|
-
this.emitActivityState(
|
|
211
|
-
|
|
212
|
-
|
|
257
|
+
if (state === "pending") {
|
|
258
|
+
this.emitActivityState(
|
|
259
|
+
"awaiting_confirmation",
|
|
260
|
+
"confirmation_requested",
|
|
261
|
+
"assistant_turn",
|
|
262
|
+
);
|
|
263
|
+
} else if (state === "timed_out") {
|
|
264
|
+
this.emitActivityState(
|
|
265
|
+
"thinking",
|
|
266
|
+
"confirmation_resolved",
|
|
267
|
+
"assistant_turn",
|
|
268
|
+
undefined,
|
|
269
|
+
"Resuming after timeout",
|
|
270
|
+
);
|
|
213
271
|
}
|
|
214
272
|
});
|
|
215
273
|
this.secretPrompter = new SecretPrompter(sendToClient);
|
|
@@ -221,12 +279,18 @@ export class Session {
|
|
|
221
279
|
this.executor = new ToolExecutor(this.prompter);
|
|
222
280
|
this.profiler = new ToolProfiler();
|
|
223
281
|
registerToolMetricsLoggingListener(this.eventBus);
|
|
224
|
-
registerToolNotificationListener(this.eventBus, (msg) =>
|
|
282
|
+
registerToolNotificationListener(this.eventBus, (msg) =>
|
|
283
|
+
this.sendToClient(msg),
|
|
284
|
+
);
|
|
225
285
|
registerToolTraceListener(this.eventBus, this.traceEmitter);
|
|
226
286
|
registerToolProfilingListener(this.eventBus, this.profiler);
|
|
227
287
|
const auditToolLifecycleEvent = createToolAuditListener();
|
|
228
|
-
const publishToolDomainEvent = createToolDomainEventPublisher(
|
|
229
|
-
|
|
288
|
+
const publishToolDomainEvent = createToolDomainEventPublisher(
|
|
289
|
+
this.eventBus,
|
|
290
|
+
);
|
|
291
|
+
const handleToolLifecycleEvent = (
|
|
292
|
+
event: import("../tools/types.js").ToolLifecycleEvent,
|
|
293
|
+
) => {
|
|
230
294
|
auditToolLifecycleEvent(event);
|
|
231
295
|
return publishToolDomainEvent(event);
|
|
232
296
|
};
|
|
@@ -247,105 +311,31 @@ export class Session {
|
|
|
247
311
|
const resolveTools = createResolveToolsCallback(toolDefs, this);
|
|
248
312
|
|
|
249
313
|
const configuredMaxTokens = maxTokens;
|
|
250
|
-
// When a systemPromptOverride was provided,
|
|
251
|
-
//
|
|
314
|
+
// When a systemPromptOverride was provided, use it as-is; otherwise
|
|
315
|
+
// rebuild the full prompt each turn (picks up any workspace file changes).
|
|
252
316
|
const hasSystemPromptOverride = systemPrompt !== buildSystemPrompt();
|
|
253
317
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
'<inbound_actor_context>',
|
|
263
|
-
'<voice_call_control>',
|
|
264
|
-
'<workspace_top_level>',
|
|
265
|
-
'<active_workspace>',
|
|
266
|
-
'<active_dynamic_page>',
|
|
267
|
-
'<dynamic-profile-context>',
|
|
268
|
-
'<memory_recall',
|
|
269
|
-
'<memory source=',
|
|
270
|
-
'<memory',
|
|
271
|
-
'<system_notice>',
|
|
272
|
-
'<interface_turn_context>',
|
|
273
|
-
];
|
|
274
|
-
|
|
275
|
-
// Track the last user-message tier so tool-use continuation turns
|
|
276
|
-
// (where user text is empty — only tool_result blocks) inherit it
|
|
277
|
-
// instead of falling to 'low'.
|
|
278
|
-
let lastUserMessageTier: import('./response-tier.js').ResponseTier = 'high';
|
|
279
|
-
let sessionTierHint: SessionTierHint | null = null;
|
|
280
|
-
const recentUserTexts: string[] = []; // circular buffer, max 3
|
|
281
|
-
const MAX_RECENT_TEXTS = 3;
|
|
282
|
-
|
|
283
|
-
const resolveSystemPromptCallback = (history: import('../providers/types.js').Message[]): ResolvedSystemPrompt => {
|
|
284
|
-
// Extract last user message text, ignoring runtime-injected context blocks
|
|
285
|
-
const lastUserMsg = [...history].reverse().find((m) => m.role === 'user');
|
|
286
|
-
let userText = '';
|
|
287
|
-
let isToolResultOnly = false;
|
|
288
|
-
if (lastUserMsg) {
|
|
289
|
-
const _hasToolResult = lastUserMsg.content.some((b) => b.type === 'tool_result');
|
|
290
|
-
for (const block of lastUserMsg.content) {
|
|
291
|
-
if (block.type === 'text') {
|
|
292
|
-
const trimmed = block.text.trimStart();
|
|
293
|
-
if (!INJECTED_PREFIXES.some((p) => trimmed.startsWith(p))) {
|
|
294
|
-
userText += block.text;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
// Inherit previous tier when there's no real user text — either
|
|
299
|
-
// tool_result-only messages or system nudges where all text was
|
|
300
|
-
// filtered out as injected context.
|
|
301
|
-
isToolResultOnly = userText.trim().length === 0;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
let tier: import('./response-tier.js').ResponseTier;
|
|
305
|
-
|
|
306
|
-
if (isToolResultOnly) {
|
|
307
|
-
// Tool-use continuation: inherit previous tier
|
|
308
|
-
tier = lastUserMessageTier;
|
|
309
|
-
} else {
|
|
310
|
-
const classification = classifyResponseTierDetailed(userText, this.turnCount);
|
|
311
|
-
tier = resolveWithHint(classification, sessionTierHint, this.turnCount);
|
|
312
|
-
lastUserMessageTier = tier;
|
|
313
|
-
|
|
314
|
-
// Update recent user texts buffer
|
|
315
|
-
const trimmedText = userText.trim();
|
|
316
|
-
if (trimmedText) {
|
|
317
|
-
if (recentUserTexts.length >= MAX_RECENT_TEXTS) {
|
|
318
|
-
recentUserTexts.shift();
|
|
319
|
-
}
|
|
320
|
-
recentUserTexts.push(trimmedText);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Fire background Haiku classification when confidence is low
|
|
324
|
-
if (classification.confidence === 'low') {
|
|
325
|
-
void classifyResponseTierAsync([...recentUserTexts]).then((asyncTier) => {
|
|
326
|
-
if (asyncTier) {
|
|
327
|
-
sessionTierHint = {
|
|
328
|
-
tier: asyncTier,
|
|
329
|
-
turn: this.turnCount,
|
|
330
|
-
timestamp: Date.now(),
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const model = tierModel(tier, provider.name);
|
|
338
|
-
return {
|
|
339
|
-
systemPrompt: hasSystemPromptOverride ? systemPrompt : buildSystemPrompt(tier),
|
|
340
|
-
maxTokens: tierMaxTokens(tier, configuredMaxTokens),
|
|
341
|
-
model,
|
|
318
|
+
const resolveSystemPromptCallback = (
|
|
319
|
+
_history: import("../providers/types.js").Message[],
|
|
320
|
+
): ResolvedSystemPrompt => {
|
|
321
|
+
const resolved = {
|
|
322
|
+
systemPrompt: hasSystemPromptOverride
|
|
323
|
+
? systemPrompt
|
|
324
|
+
: buildSystemPrompt(),
|
|
325
|
+
maxTokens: configuredMaxTokens,
|
|
342
326
|
};
|
|
327
|
+
return resolved;
|
|
343
328
|
};
|
|
344
329
|
|
|
345
330
|
this.agentLoop = new AgentLoop(
|
|
346
331
|
provider,
|
|
347
332
|
systemPrompt,
|
|
348
|
-
{
|
|
333
|
+
{
|
|
334
|
+
maxTokens,
|
|
335
|
+
maxInputTokens: config.contextWindow.maxInputTokens,
|
|
336
|
+
thinking: config.thinking,
|
|
337
|
+
maxToolUseTurns: config.maxToolUseTurns,
|
|
338
|
+
},
|
|
349
339
|
toolDefs.length > 0 ? toolDefs : undefined,
|
|
350
340
|
toolDefs.length > 0 ? toolExecutor : undefined,
|
|
351
341
|
resolveTools,
|
|
@@ -357,7 +347,7 @@ export class Session {
|
|
|
357
347
|
config.contextWindow,
|
|
358
348
|
);
|
|
359
349
|
|
|
360
|
-
void getHookManager().trigger(
|
|
350
|
+
void getHookManager().trigger("session-start", {
|
|
361
351
|
sessionId: this.conversationId,
|
|
362
352
|
workingDir: this.workingDir,
|
|
363
353
|
});
|
|
@@ -375,7 +365,10 @@ export class Session {
|
|
|
375
365
|
await this.loadFromDb();
|
|
376
366
|
}
|
|
377
367
|
|
|
378
|
-
updateClient(
|
|
368
|
+
updateClient(
|
|
369
|
+
sendToClient: (msg: ServerMessage) => void,
|
|
370
|
+
hasNoClient = false,
|
|
371
|
+
): void {
|
|
379
372
|
this.sendToClient = sendToClient;
|
|
380
373
|
this.hasNoClient = hasNoClient;
|
|
381
374
|
this.prompter.updateSender(sendToClient);
|
|
@@ -403,7 +396,9 @@ export class Session {
|
|
|
403
396
|
this.sandboxOverride = enabled;
|
|
404
397
|
}
|
|
405
398
|
|
|
406
|
-
setEscalationHandler(
|
|
399
|
+
setEscalationHandler(
|
|
400
|
+
handler: (task: string, sourceSessionId: string) => boolean,
|
|
401
|
+
): void {
|
|
407
402
|
this.onEscalateToComputerUse = handler;
|
|
408
403
|
}
|
|
409
404
|
|
|
@@ -437,8 +432,16 @@ export class Session {
|
|
|
437
432
|
|
|
438
433
|
// ── Messaging ────────────────────────────────────────────────────
|
|
439
434
|
|
|
440
|
-
redirectToSecurePrompt(
|
|
441
|
-
|
|
435
|
+
redirectToSecurePrompt(
|
|
436
|
+
detectedTypes: string[],
|
|
437
|
+
options?: RedirectToSecurePromptOptions,
|
|
438
|
+
): void {
|
|
439
|
+
redirectToSecurePromptImpl(
|
|
440
|
+
this.conversationId,
|
|
441
|
+
this.secretPrompter,
|
|
442
|
+
detectedTypes,
|
|
443
|
+
options,
|
|
444
|
+
);
|
|
442
445
|
}
|
|
443
446
|
|
|
444
447
|
enqueueMessage(
|
|
@@ -452,7 +455,18 @@ export class Session {
|
|
|
452
455
|
options?: { isInteractive?: boolean },
|
|
453
456
|
displayContent?: string,
|
|
454
457
|
): { queued: boolean; rejected?: boolean; requestId: string } {
|
|
455
|
-
return enqueueMessageImpl(
|
|
458
|
+
return enqueueMessageImpl(
|
|
459
|
+
this,
|
|
460
|
+
content,
|
|
461
|
+
attachments,
|
|
462
|
+
onEvent,
|
|
463
|
+
requestId,
|
|
464
|
+
activeSurfaceId,
|
|
465
|
+
currentPage,
|
|
466
|
+
metadata,
|
|
467
|
+
options,
|
|
468
|
+
displayContent,
|
|
469
|
+
);
|
|
456
470
|
}
|
|
457
471
|
|
|
458
472
|
getQueueDepth(): number {
|
|
@@ -498,7 +512,7 @@ export class Session {
|
|
|
498
512
|
selectedScope?: string,
|
|
499
513
|
decisionContext?: string,
|
|
500
514
|
emissionContext?: {
|
|
501
|
-
source?: ConfirmationStateChanged[
|
|
515
|
+
source?: ConfirmationStateChanged["source"];
|
|
502
516
|
causedByRequestId?: string;
|
|
503
517
|
decisionText?: string;
|
|
504
518
|
},
|
|
@@ -525,42 +539,62 @@ export class Session {
|
|
|
525
539
|
// Emit authoritative confirmation state and activity transition centrally
|
|
526
540
|
// so ALL callers (IPC handlers, /v1/confirm, channel bridges) get
|
|
527
541
|
// consistent events without duplicating emission logic.
|
|
528
|
-
const resolvedState =
|
|
529
|
-
|
|
530
|
-
|
|
542
|
+
const resolvedState =
|
|
543
|
+
decision === "deny" || decision === "always_deny"
|
|
544
|
+
? ("denied" as const)
|
|
545
|
+
: ("approved" as const);
|
|
531
546
|
this.emitConfirmationStateChanged({
|
|
532
547
|
sessionId: this.conversationId,
|
|
533
548
|
requestId,
|
|
534
549
|
state: resolvedState,
|
|
535
|
-
source: emissionContext?.source ??
|
|
536
|
-
...(emissionContext?.causedByRequestId
|
|
537
|
-
|
|
550
|
+
source: emissionContext?.source ?? "button",
|
|
551
|
+
...(emissionContext?.causedByRequestId
|
|
552
|
+
? { causedByRequestId: emissionContext.causedByRequestId }
|
|
553
|
+
: {}),
|
|
554
|
+
...(emissionContext?.decisionText
|
|
555
|
+
? { decisionText: emissionContext.decisionText }
|
|
556
|
+
: {}),
|
|
538
557
|
});
|
|
539
|
-
this.emitActivityState(
|
|
558
|
+
this.emitActivityState(
|
|
559
|
+
"thinking",
|
|
560
|
+
"confirmation_resolved",
|
|
561
|
+
"assistant_turn",
|
|
562
|
+
undefined,
|
|
563
|
+
"Resuming after approval",
|
|
564
|
+
);
|
|
540
565
|
}
|
|
541
566
|
|
|
542
|
-
handleSecretResponse(
|
|
567
|
+
handleSecretResponse(
|
|
568
|
+
requestId: string,
|
|
569
|
+
value?: string,
|
|
570
|
+
delivery?: "store" | "transient_send",
|
|
571
|
+
): void {
|
|
543
572
|
this.secretPrompter.resolveSecret(requestId, value, delivery);
|
|
544
573
|
}
|
|
545
574
|
|
|
546
575
|
// ── Server-authoritative state signals ─────────────────────────────
|
|
547
576
|
|
|
548
|
-
emitConfirmationStateChanged(
|
|
549
|
-
|
|
577
|
+
emitConfirmationStateChanged(
|
|
578
|
+
params: Omit<ConfirmationStateChanged, "type">,
|
|
579
|
+
): void {
|
|
580
|
+
const msg: ServerMessage = {
|
|
581
|
+
type: "confirmation_state_changed",
|
|
582
|
+
...params,
|
|
583
|
+
} as ServerMessage;
|
|
550
584
|
this.sendToClient(msg);
|
|
551
585
|
this.onStateSignal?.(msg);
|
|
552
586
|
}
|
|
553
587
|
|
|
554
588
|
emitActivityState(
|
|
555
|
-
phase: AssistantActivityState[
|
|
556
|
-
reason: AssistantActivityState[
|
|
557
|
-
anchor: AssistantActivityState[
|
|
589
|
+
phase: AssistantActivityState["phase"],
|
|
590
|
+
reason: AssistantActivityState["reason"],
|
|
591
|
+
anchor: AssistantActivityState["anchor"] = "assistant_turn",
|
|
558
592
|
requestId?: string,
|
|
559
593
|
statusText?: string,
|
|
560
594
|
): void {
|
|
561
595
|
this.activityVersion++;
|
|
562
596
|
const msg: ServerMessage = {
|
|
563
|
-
type:
|
|
597
|
+
type: "assistant_activity_state",
|
|
564
598
|
sessionId: this.conversationId,
|
|
565
599
|
activityVersion: this.activityVersion,
|
|
566
600
|
phase,
|
|
@@ -597,7 +631,9 @@ export class Session {
|
|
|
597
631
|
this.assistantId = assistantId ?? undefined;
|
|
598
632
|
}
|
|
599
633
|
|
|
600
|
-
setCommandIntent(
|
|
634
|
+
setCommandIntent(
|
|
635
|
+
intent: { type: string; payload?: string; languageCode?: string } | null,
|
|
636
|
+
): void {
|
|
601
637
|
this.commandIntent = intent ?? undefined;
|
|
602
638
|
}
|
|
603
639
|
|
|
@@ -631,7 +667,14 @@ export class Session {
|
|
|
631
667
|
if (!this.processing) {
|
|
632
668
|
await this.ensureActorScopedHistory();
|
|
633
669
|
}
|
|
634
|
-
return persistUserMessageImpl(
|
|
670
|
+
return persistUserMessageImpl(
|
|
671
|
+
this,
|
|
672
|
+
content,
|
|
673
|
+
attachments,
|
|
674
|
+
requestId,
|
|
675
|
+
metadata,
|
|
676
|
+
displayContent,
|
|
677
|
+
);
|
|
635
678
|
}
|
|
636
679
|
|
|
637
680
|
// ── Agent Loop ───────────────────────────────────────────────────
|
|
@@ -640,13 +683,17 @@ export class Session {
|
|
|
640
683
|
content: string,
|
|
641
684
|
userMessageId: string,
|
|
642
685
|
onEvent: (msg: ServerMessage) => void,
|
|
643
|
-
options?: {
|
|
686
|
+
options?: {
|
|
687
|
+
skipPreMessageRollback?: boolean;
|
|
688
|
+
isInteractive?: boolean;
|
|
689
|
+
isUserMessage?: boolean;
|
|
690
|
+
titleText?: string;
|
|
691
|
+
},
|
|
644
692
|
): Promise<void> {
|
|
645
693
|
return runAgentLoopImpl(this, content, userMessageId, onEvent, options);
|
|
646
694
|
}
|
|
647
695
|
|
|
648
|
-
|
|
649
|
-
drainQueue(reason: QueueDrainReason = 'loop_complete'): Promise<void> {
|
|
696
|
+
drainQueue(reason: QueueDrainReason = "loop_complete"): Promise<void> {
|
|
650
697
|
return drainQueueImpl(this as ProcessSessionContext, reason);
|
|
651
698
|
}
|
|
652
699
|
|
|
@@ -660,7 +707,17 @@ export class Session {
|
|
|
660
707
|
options?: { isInteractive?: boolean },
|
|
661
708
|
displayContent?: string,
|
|
662
709
|
): Promise<string> {
|
|
663
|
-
return processMessageImpl(
|
|
710
|
+
return processMessageImpl(
|
|
711
|
+
this as ProcessSessionContext,
|
|
712
|
+
content,
|
|
713
|
+
attachments,
|
|
714
|
+
onEvent,
|
|
715
|
+
requestId,
|
|
716
|
+
activeSurfaceId,
|
|
717
|
+
currentPage,
|
|
718
|
+
options,
|
|
719
|
+
displayContent,
|
|
720
|
+
);
|
|
664
721
|
}
|
|
665
722
|
|
|
666
723
|
// ── History ──────────────────────────────────────────────────────
|
|
@@ -673,13 +730,20 @@ export class Session {
|
|
|
673
730
|
return undoImpl(this as HistorySessionContext);
|
|
674
731
|
}
|
|
675
732
|
|
|
676
|
-
async regenerate(
|
|
733
|
+
async regenerate(
|
|
734
|
+
onEvent: (msg: ServerMessage) => void,
|
|
735
|
+
requestId?: string,
|
|
736
|
+
): Promise<void> {
|
|
677
737
|
return regenerateImpl(this as HistorySessionContext, onEvent, requestId);
|
|
678
738
|
}
|
|
679
739
|
|
|
680
740
|
// ── Surfaces ─────────────────────────────────────────────────────
|
|
681
741
|
|
|
682
|
-
handleSurfaceAction(
|
|
742
|
+
handleSurfaceAction(
|
|
743
|
+
surfaceId: string,
|
|
744
|
+
actionId: string,
|
|
745
|
+
data?: Record<string, unknown>,
|
|
746
|
+
): void {
|
|
683
747
|
handleSurfaceActionImpl(this, surfaceId, actionId, data);
|
|
684
748
|
}
|
|
685
749
|
|