@clinebot/core 0.0.7 → 0.0.11
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/auth/cline.d.ts +2 -0
- package/dist/auth/codex.d.ts +5 -1
- package/dist/auth/oca.d.ts +7 -1
- package/dist/auth/types.d.ts +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.node.js +124 -122
- package/dist/input/mention-enricher.d.ts +1 -0
- package/dist/providers/local-provider-service.d.ts +1 -1
- package/dist/runtime/session-runtime.d.ts +1 -1
- package/dist/session/default-session-manager.d.ts +13 -17
- package/dist/session/runtime-oauth-token-manager.d.ts +4 -2
- package/dist/session/session-agent-events.d.ts +15 -0
- package/dist/session/session-config-builder.d.ts +13 -0
- package/dist/session/session-manager.d.ts +2 -2
- package/dist/session/session-team-coordination.d.ts +12 -0
- package/dist/session/session-telemetry.d.ts +9 -0
- package/dist/session/unified-session-persistence-service.d.ts +12 -16
- package/dist/session/utils/helpers.d.ts +2 -2
- package/dist/session/utils/types.d.ts +2 -1
- package/dist/telemetry/core-events.d.ts +122 -0
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/executors/file-read.d.ts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/presets.d.ts +1 -1
- package/dist/tools/schemas.d.ts +46 -3
- package/dist/tools/types.d.ts +3 -3
- package/dist/types/config.d.ts +1 -1
- package/dist/types/provider-settings.d.ts +4 -4
- package/dist/types.d.ts +1 -1
- package/package.json +4 -3
- package/src/auth/cline.ts +35 -1
- package/src/auth/codex.ts +27 -2
- package/src/auth/oca.ts +31 -4
- package/src/auth/types.ts +3 -0
- package/src/index.ts +27 -0
- package/src/input/mention-enricher.test.ts +3 -0
- package/src/input/mention-enricher.ts +3 -0
- package/src/providers/local-provider-service.ts +6 -7
- package/src/runtime/hook-file-hooks.ts +11 -10
- package/src/runtime/session-runtime.ts +1 -1
- package/src/session/default-session-manager.e2e.test.ts +2 -1
- package/src/session/default-session-manager.test.ts +131 -0
- package/src/session/default-session-manager.ts +372 -602
- package/src/session/runtime-oauth-token-manager.ts +21 -14
- package/src/session/session-agent-events.ts +159 -0
- package/src/session/session-config-builder.ts +111 -0
- package/src/session/session-host.ts +13 -0
- package/src/session/session-manager.ts +2 -2
- package/src/session/session-team-coordination.ts +198 -0
- package/src/session/session-telemetry.ts +95 -0
- package/src/session/unified-session-persistence-service.test.ts +81 -0
- package/src/session/unified-session-persistence-service.ts +470 -469
- package/src/session/utils/helpers.ts +14 -4
- package/src/session/utils/types.ts +2 -1
- package/src/storage/provider-settings-legacy-migration.ts +3 -3
- package/src/telemetry/core-events.ts +344 -0
- package/src/tools/definitions.test.ts +121 -7
- package/src/tools/definitions.ts +60 -24
- package/src/tools/executors/file-read.test.ts +29 -5
- package/src/tools/executors/file-read.ts +17 -6
- package/src/tools/index.ts +2 -0
- package/src/tools/presets.ts +1 -1
- package/src/tools/schemas.ts +65 -5
- package/src/tools/types.ts +7 -3
- package/src/types/config.ts +1 -1
- package/src/types/provider-settings.ts +6 -6
- package/src/types.ts +1 -1
|
@@ -13,36 +13,28 @@ import {
|
|
|
13
13
|
type ToolApprovalRequest,
|
|
14
14
|
type ToolApprovalResult,
|
|
15
15
|
} from "@clinebot/agents";
|
|
16
|
-
import type {
|
|
16
|
+
import type { LlmsProviders } from "@clinebot/llms";
|
|
17
17
|
import {
|
|
18
|
-
formatUserInputBlock,
|
|
19
18
|
type ITelemetryService,
|
|
19
|
+
isLikelyAuthError,
|
|
20
20
|
normalizeUserInput,
|
|
21
21
|
} from "@clinebot/shared";
|
|
22
22
|
import { setHomeDirIfUnset } from "@clinebot/shared/storage";
|
|
23
23
|
import { nanoid } from "nanoid";
|
|
24
|
-
import { resolveAndLoadAgentPlugins } from "../agents/plugin-config-loader";
|
|
25
24
|
import { enrichPromptWithMentions } from "../input";
|
|
26
|
-
import {
|
|
27
|
-
createHookAuditHooks,
|
|
28
|
-
createHookConfigFileHooks,
|
|
29
|
-
mergeAgentHooks,
|
|
30
|
-
} from "../runtime/hook-file-hooks";
|
|
31
25
|
import { DefaultRuntimeBuilder } from "../runtime/runtime-builder";
|
|
32
26
|
import type { RuntimeBuilder } from "../runtime/session-runtime";
|
|
33
27
|
import { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
34
28
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
captureConversationTurnEvent,
|
|
30
|
+
captureModeSwitch,
|
|
31
|
+
captureSubagentExecution,
|
|
32
|
+
captureTaskCompleted,
|
|
33
|
+
} from "../telemetry/core-events";
|
|
38
34
|
import { createBuiltinTools, type ToolExecutors, ToolPresets } from "../tools";
|
|
39
35
|
import { SessionSource, type SessionStatus } from "../types/common";
|
|
40
36
|
import type { CoreSessionConfig } from "../types/config";
|
|
41
37
|
import type { CoreSessionEvent } from "../types/events";
|
|
42
|
-
import {
|
|
43
|
-
type ProviderSettings,
|
|
44
|
-
toProviderConfig,
|
|
45
|
-
} from "../types/provider-settings";
|
|
46
38
|
import type { SessionRecord } from "../types/sessions";
|
|
47
39
|
import type { RpcCoreSessionService } from "./rpc-session-service";
|
|
48
40
|
import {
|
|
@@ -50,7 +42,17 @@ import {
|
|
|
50
42
|
type RuntimeOAuthResolution,
|
|
51
43
|
RuntimeOAuthTokenManager,
|
|
52
44
|
} from "./runtime-oauth-token-manager";
|
|
45
|
+
import {
|
|
46
|
+
type AgentEventContext,
|
|
47
|
+
handleAgentEvent,
|
|
48
|
+
} from "./session-agent-events";
|
|
53
49
|
import { nowIso } from "./session-artifacts";
|
|
50
|
+
import {
|
|
51
|
+
buildEffectiveConfig,
|
|
52
|
+
buildResolvedProviderConfig,
|
|
53
|
+
resolveReasoningSettings,
|
|
54
|
+
resolveWorkspacePath,
|
|
55
|
+
} from "./session-config-builder";
|
|
54
56
|
import type {
|
|
55
57
|
SendSessionInput,
|
|
56
58
|
SessionAccumulatedUsage,
|
|
@@ -64,19 +66,27 @@ import type {
|
|
|
64
66
|
RootSessionArtifacts,
|
|
65
67
|
SessionRowShape,
|
|
66
68
|
} from "./session-service";
|
|
69
|
+
import {
|
|
70
|
+
buildTeamRunContinuationPrompt,
|
|
71
|
+
dispatchTeamEventToBackend,
|
|
72
|
+
emitTeamProgress,
|
|
73
|
+
formatModePrompt,
|
|
74
|
+
hasPendingTeamRunWork,
|
|
75
|
+
notifyTeamRunWaiters,
|
|
76
|
+
shouldAutoContinueTeamRuns,
|
|
77
|
+
trackTeamRunState,
|
|
78
|
+
waitForTeamRunUpdates,
|
|
79
|
+
} from "./session-team-coordination";
|
|
80
|
+
import {
|
|
81
|
+
emitMentionTelemetry,
|
|
82
|
+
emitSessionCreationTelemetry,
|
|
83
|
+
} from "./session-telemetry";
|
|
67
84
|
import {
|
|
68
85
|
extractWorkspaceMetadataFromSystemPrompt,
|
|
69
|
-
hasRuntimeHooks,
|
|
70
|
-
mergeAgentExtensions,
|
|
71
|
-
serializeAgentEvent,
|
|
72
86
|
toSessionRecord,
|
|
73
87
|
withLatestAssistantTurnMetadata,
|
|
74
88
|
} from "./utils/helpers";
|
|
75
|
-
import type {
|
|
76
|
-
ActiveSession,
|
|
77
|
-
PreparedTurnInput,
|
|
78
|
-
TeamRunUpdate,
|
|
79
|
-
} from "./utils/types";
|
|
89
|
+
import type { ActiveSession, PreparedTurnInput } from "./utils/types";
|
|
80
90
|
import {
|
|
81
91
|
accumulateUsageTotals,
|
|
82
92
|
createInitialAccumulatedUsage,
|
|
@@ -84,21 +94,6 @@ import {
|
|
|
84
94
|
|
|
85
95
|
type SessionBackend = CoreSessionService | RpcCoreSessionService;
|
|
86
96
|
|
|
87
|
-
export interface DefaultSessionManagerOptions {
|
|
88
|
-
distinctId: string;
|
|
89
|
-
sessionService: SessionBackend;
|
|
90
|
-
runtimeBuilder?: RuntimeBuilder;
|
|
91
|
-
createAgent?: (config: AgentConfig) => Agent;
|
|
92
|
-
defaultToolExecutors?: Partial<ToolExecutors>;
|
|
93
|
-
toolPolicies?: AgentConfig["toolPolicies"];
|
|
94
|
-
providerSettingsManager?: ProviderSettingsManager;
|
|
95
|
-
oauthTokenManager?: RuntimeOAuthTokenManager;
|
|
96
|
-
telemetry?: ITelemetryService;
|
|
97
|
-
requestToolApproval?: (
|
|
98
|
-
request: ToolApprovalRequest,
|
|
99
|
-
) => Promise<ToolApprovalResult>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
97
|
const MAX_SCAN_LIMIT = 5000;
|
|
103
98
|
const MAX_USER_FILE_BYTES = 20 * 1_000 * 1_024;
|
|
104
99
|
|
|
@@ -117,6 +112,21 @@ async function loadUserFileContent(path: string): Promise<string> {
|
|
|
117
112
|
return content;
|
|
118
113
|
}
|
|
119
114
|
|
|
115
|
+
export interface DefaultSessionManagerOptions {
|
|
116
|
+
distinctId: string;
|
|
117
|
+
sessionService: SessionBackend;
|
|
118
|
+
runtimeBuilder?: RuntimeBuilder;
|
|
119
|
+
createAgent?: (config: AgentConfig) => Agent;
|
|
120
|
+
defaultToolExecutors?: Partial<ToolExecutors>;
|
|
121
|
+
toolPolicies?: AgentConfig["toolPolicies"];
|
|
122
|
+
providerSettingsManager?: ProviderSettingsManager;
|
|
123
|
+
oauthTokenManager?: RuntimeOAuthTokenManager;
|
|
124
|
+
telemetry?: ITelemetryService;
|
|
125
|
+
requestToolApproval?: (
|
|
126
|
+
request: ToolApprovalRequest,
|
|
127
|
+
) => Promise<ToolApprovalResult>;
|
|
128
|
+
}
|
|
129
|
+
|
|
120
130
|
export class DefaultSessionManager implements SessionManager {
|
|
121
131
|
private readonly sessionService: SessionBackend;
|
|
122
132
|
private readonly runtimeBuilder: RuntimeBuilder;
|
|
@@ -132,6 +142,10 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
132
142
|
private readonly listeners = new Set<(event: CoreSessionEvent) => void>();
|
|
133
143
|
private readonly sessions = new Map<string, ActiveSession>();
|
|
134
144
|
private readonly usageBySession = new Map<string, SessionAccumulatedUsage>();
|
|
145
|
+
private readonly subAgentStarts = new Map<
|
|
146
|
+
string,
|
|
147
|
+
{ startedAt: number; rootSessionId: string }
|
|
148
|
+
>();
|
|
135
149
|
|
|
136
150
|
constructor(options: DefaultSessionManagerOptions) {
|
|
137
151
|
const homeDir = homedir();
|
|
@@ -148,52 +162,13 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
148
162
|
options.oauthTokenManager ??
|
|
149
163
|
new RuntimeOAuthTokenManager({
|
|
150
164
|
providerSettingsManager: this.providerSettingsManager,
|
|
165
|
+
telemetry: options.telemetry,
|
|
151
166
|
});
|
|
152
167
|
this.defaultTelemetry = options.telemetry;
|
|
153
168
|
this.defaultRequestToolApproval = options.requestToolApproval;
|
|
154
169
|
}
|
|
155
170
|
|
|
156
|
-
|
|
157
|
-
const stored = this.providerSettingsManager.getProviderSettings(providerId);
|
|
158
|
-
if (stored) {
|
|
159
|
-
return stored;
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
provider: providerId,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private buildResolvedProviderConfig(
|
|
167
|
-
config: CoreSessionConfig,
|
|
168
|
-
): LlmsProviders.ProviderConfig {
|
|
169
|
-
const settings = this.resolveStoredProviderSettings(config.providerId);
|
|
170
|
-
const mergedSettings: ProviderSettings = {
|
|
171
|
-
...settings,
|
|
172
|
-
provider: config.providerId,
|
|
173
|
-
model: config.modelId,
|
|
174
|
-
apiKey: config.apiKey ?? settings.apiKey,
|
|
175
|
-
baseUrl: config.baseUrl ?? settings.baseUrl,
|
|
176
|
-
headers: config.headers ?? settings.headers,
|
|
177
|
-
reasoning:
|
|
178
|
-
typeof config.thinking === "boolean" ||
|
|
179
|
-
typeof config.reasoningEffort === "string"
|
|
180
|
-
? {
|
|
181
|
-
...(settings.reasoning ?? {}),
|
|
182
|
-
...(typeof config.thinking === "boolean"
|
|
183
|
-
? { enabled: config.thinking }
|
|
184
|
-
: {}),
|
|
185
|
-
...(typeof config.reasoningEffort === "string"
|
|
186
|
-
? { effort: config.reasoningEffort }
|
|
187
|
-
: {}),
|
|
188
|
-
}
|
|
189
|
-
: settings.reasoning,
|
|
190
|
-
};
|
|
191
|
-
const providerConfig = toProviderConfig(mergedSettings);
|
|
192
|
-
if (config.knownModels) {
|
|
193
|
-
providerConfig.knownModels = config.knownModels;
|
|
194
|
-
}
|
|
195
|
-
return providerConfig;
|
|
196
|
-
}
|
|
171
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
197
172
|
|
|
198
173
|
async start(input: StartSessionInput): Promise<StartSessionResult> {
|
|
199
174
|
const source = input.source ?? SessionSource.CLI;
|
|
@@ -204,6 +179,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
204
179
|
? requestedSessionId
|
|
205
180
|
: `${Date.now()}_${nanoid(5)}`;
|
|
206
181
|
this.usageBySession.set(sessionId, createInitialAccumulatedUsage());
|
|
182
|
+
|
|
207
183
|
const sessionsDir =
|
|
208
184
|
((await this.invokeOptionalValue("ensureSessionsDir")) as
|
|
209
185
|
| string
|
|
@@ -213,11 +189,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
213
189
|
"session service method not available: ensureSessionsDir",
|
|
214
190
|
);
|
|
215
191
|
}
|
|
192
|
+
|
|
216
193
|
const sessionDir = join(sessionsDir, sessionId);
|
|
217
194
|
const transcriptPath = join(sessionDir, `${sessionId}.log`);
|
|
218
195
|
const hookPath = join(sessionDir, `${sessionId}.hooks.jsonl`);
|
|
219
196
|
const messagesPath = join(sessionDir, `${sessionId}.messages.json`);
|
|
220
197
|
const manifestPath = join(sessionDir, `${sessionId}.json`);
|
|
198
|
+
const workspacePath = resolveWorkspacePath(input.config);
|
|
199
|
+
|
|
221
200
|
const manifest = SessionManifestSchema.parse({
|
|
222
201
|
version: 1,
|
|
223
202
|
session_id: sessionId,
|
|
@@ -229,7 +208,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
229
208
|
provider: input.config.providerId,
|
|
230
209
|
model: input.config.modelId,
|
|
231
210
|
cwd: input.config.cwd,
|
|
232
|
-
workspace_root:
|
|
211
|
+
workspace_root: workspacePath,
|
|
233
212
|
team_name: input.config.teamName,
|
|
234
213
|
enable_tools: input.config.enableTools,
|
|
235
214
|
enable_spawn: input.config.enableSpawnAgent,
|
|
@@ -238,76 +217,50 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
238
217
|
messages_path: messagesPath,
|
|
239
218
|
});
|
|
240
219
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
rootSessionId: sessionId,
|
|
253
|
-
workspacePath: input.config.workspaceRoot ?? input.config.cwd,
|
|
254
|
-
});
|
|
255
|
-
const effectiveHooks = mergeAgentHooks([
|
|
256
|
-
input.config.hooks,
|
|
257
|
-
fileHooks,
|
|
258
|
-
auditHooks,
|
|
259
|
-
]);
|
|
260
|
-
const loadedPlugins = await resolveAndLoadAgentPlugins({
|
|
261
|
-
pluginPaths: input.config.pluginPaths,
|
|
262
|
-
workspacePath: input.config.workspaceRoot ?? input.config.cwd,
|
|
263
|
-
cwd: input.config.cwd,
|
|
264
|
-
});
|
|
265
|
-
const effectiveExtensions = mergeAgentExtensions(
|
|
266
|
-
input.config.extensions,
|
|
267
|
-
loadedPlugins.extensions,
|
|
220
|
+
const { config: effectiveConfig, pluginSandboxShutdown } =
|
|
221
|
+
await buildEffectiveConfig(
|
|
222
|
+
input,
|
|
223
|
+
hookPath,
|
|
224
|
+
sessionId,
|
|
225
|
+
this.defaultTelemetry,
|
|
226
|
+
);
|
|
227
|
+
const providerConfig = buildResolvedProviderConfig(
|
|
228
|
+
effectiveConfig,
|
|
229
|
+
this.providerSettingsManager,
|
|
230
|
+
resolveReasoningSettings,
|
|
268
231
|
);
|
|
269
|
-
const
|
|
270
|
-
...
|
|
271
|
-
hooks: effectiveHooks,
|
|
272
|
-
extensions: effectiveExtensions,
|
|
273
|
-
telemetry: input.config.telemetry ?? this.defaultTelemetry,
|
|
274
|
-
};
|
|
275
|
-
const providerConfig =
|
|
276
|
-
this.buildResolvedProviderConfig(effectiveConfigBase);
|
|
277
|
-
const effectiveConfig: CoreSessionConfig = {
|
|
278
|
-
...effectiveConfigBase,
|
|
232
|
+
const configWithProvider: CoreSessionConfig = {
|
|
233
|
+
...effectiveConfig,
|
|
279
234
|
providerConfig,
|
|
280
235
|
};
|
|
281
236
|
|
|
282
237
|
const runtime = this.runtimeBuilder.build({
|
|
283
|
-
config:
|
|
284
|
-
hooks:
|
|
285
|
-
extensions:
|
|
286
|
-
logger:
|
|
287
|
-
telemetry:
|
|
238
|
+
config: configWithProvider,
|
|
239
|
+
hooks: effectiveConfig.hooks,
|
|
240
|
+
extensions: effectiveConfig.extensions,
|
|
241
|
+
logger: configWithProvider.logger,
|
|
242
|
+
telemetry: configWithProvider.telemetry,
|
|
288
243
|
onTeamEvent: (event: TeamEvent) => {
|
|
289
244
|
void this.handleTeamEvent(sessionId, event);
|
|
290
|
-
|
|
245
|
+
configWithProvider.onTeamEvent?.(event);
|
|
291
246
|
},
|
|
292
|
-
createSpawnTool: () =>
|
|
247
|
+
createSpawnTool: () =>
|
|
248
|
+
this.createSpawnTool(configWithProvider, sessionId),
|
|
293
249
|
onTeamRestored: input.onTeamRestored,
|
|
294
250
|
userInstructionWatcher: input.userInstructionWatcher,
|
|
295
251
|
defaultToolExecutors:
|
|
296
252
|
input.defaultToolExecutors ?? this.defaultToolExecutors,
|
|
297
253
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
enableAgentTeams: effectiveConfig.enableAgentTeams,
|
|
309
|
-
},
|
|
310
|
-
});
|
|
254
|
+
|
|
255
|
+
const tools = [...runtime.tools, ...(configWithProvider.extraTools ?? [])];
|
|
256
|
+
emitSessionCreationTelemetry(
|
|
257
|
+
configWithProvider,
|
|
258
|
+
sessionId,
|
|
259
|
+
source,
|
|
260
|
+
requestedSessionId.length > 0,
|
|
261
|
+
workspacePath,
|
|
262
|
+
);
|
|
263
|
+
|
|
311
264
|
const agent = this.createAgentInstance({
|
|
312
265
|
providerId: providerConfig.providerId,
|
|
313
266
|
modelId: providerConfig.modelId,
|
|
@@ -316,67 +269,32 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
316
269
|
headers: providerConfig.headers,
|
|
317
270
|
knownModels: providerConfig.knownModels,
|
|
318
271
|
providerConfig,
|
|
319
|
-
thinking:
|
|
272
|
+
thinking: configWithProvider.thinking,
|
|
320
273
|
reasoningEffort:
|
|
321
|
-
|
|
322
|
-
systemPrompt:
|
|
323
|
-
maxIterations:
|
|
324
|
-
maxConsecutiveMistakes:
|
|
274
|
+
configWithProvider.reasoningEffort ?? providerConfig.reasoningEffort,
|
|
275
|
+
systemPrompt: configWithProvider.systemPrompt,
|
|
276
|
+
maxIterations: configWithProvider.maxIterations,
|
|
277
|
+
maxConsecutiveMistakes: configWithProvider.maxConsecutiveMistakes,
|
|
325
278
|
tools,
|
|
326
|
-
hooks:
|
|
327
|
-
extensions:
|
|
328
|
-
hookErrorMode:
|
|
279
|
+
hooks: effectiveConfig.hooks,
|
|
280
|
+
extensions: effectiveConfig.extensions,
|
|
281
|
+
hookErrorMode: configWithProvider.hookErrorMode,
|
|
329
282
|
initialMessages: input.initialMessages,
|
|
330
283
|
userFileContentLoader: loadUserFileContent,
|
|
331
284
|
toolPolicies: input.toolPolicies ?? this.defaultToolPolicies,
|
|
332
285
|
requestToolApproval:
|
|
333
286
|
input.requestToolApproval ?? this.defaultRequestToolApproval,
|
|
334
287
|
onConsecutiveMistakeLimitReached:
|
|
335
|
-
|
|
288
|
+
configWithProvider.onConsecutiveMistakeLimitReached,
|
|
336
289
|
completionGuard: runtime.completionGuard,
|
|
337
|
-
logger: runtime.logger ??
|
|
338
|
-
onEvent: (event: AgentEvent) =>
|
|
339
|
-
|
|
340
|
-
if (event.type === "usage" && liveSession?.turnUsageBaseline) {
|
|
341
|
-
this.usageBySession.set(
|
|
342
|
-
sessionId,
|
|
343
|
-
accumulateUsageTotals(liveSession.turnUsageBaseline, {
|
|
344
|
-
inputTokens: event.totalInputTokens,
|
|
345
|
-
outputTokens: event.totalOutputTokens,
|
|
346
|
-
totalCost: event.totalCost,
|
|
347
|
-
}),
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
if (event.type === "iteration_end") {
|
|
351
|
-
void this.invoke<void>(
|
|
352
|
-
"persistSessionMessages",
|
|
353
|
-
sessionId,
|
|
354
|
-
liveSession?.agent.getMessages() ?? [],
|
|
355
|
-
liveSession?.config.systemPrompt,
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
this.emit({
|
|
359
|
-
type: "agent_event",
|
|
360
|
-
payload: {
|
|
361
|
-
sessionId,
|
|
362
|
-
event,
|
|
363
|
-
},
|
|
364
|
-
});
|
|
365
|
-
this.emit({
|
|
366
|
-
type: "chunk",
|
|
367
|
-
payload: {
|
|
368
|
-
sessionId,
|
|
369
|
-
stream: "agent",
|
|
370
|
-
chunk: serializeAgentEvent(event),
|
|
371
|
-
ts: Date.now(),
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
},
|
|
290
|
+
logger: runtime.logger ?? configWithProvider.logger,
|
|
291
|
+
onEvent: (event: AgentEvent) =>
|
|
292
|
+
this.onAgentEvent(sessionId, configWithProvider, event),
|
|
375
293
|
});
|
|
376
294
|
|
|
377
295
|
const active: ActiveSession = {
|
|
378
296
|
sessionId,
|
|
379
|
-
config:
|
|
297
|
+
config: configWithProvider,
|
|
380
298
|
source,
|
|
381
299
|
startedAt,
|
|
382
300
|
pendingPrompt: manifest.prompt,
|
|
@@ -385,13 +303,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
385
303
|
started: false,
|
|
386
304
|
aborting: false,
|
|
387
305
|
interactive: input.interactive === true,
|
|
306
|
+
persistedMessages: input.initialMessages,
|
|
388
307
|
activeTeamRunIds: new Set<string>(),
|
|
389
308
|
pendingTeamRunUpdates: [],
|
|
390
309
|
teamRunWaiters: [],
|
|
391
|
-
pluginSandboxShutdown
|
|
310
|
+
pluginSandboxShutdown,
|
|
392
311
|
};
|
|
393
|
-
this.sessions.set(
|
|
394
|
-
this.emitStatus(
|
|
312
|
+
this.sessions.set(sessionId, active);
|
|
313
|
+
this.emitStatus(sessionId, "running");
|
|
395
314
|
|
|
396
315
|
let result: AgentResult | undefined;
|
|
397
316
|
try {
|
|
@@ -422,10 +341,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
422
341
|
}
|
|
423
342
|
|
|
424
343
|
async send(input: SendSessionInput): Promise<AgentResult | undefined> {
|
|
425
|
-
const session = this.
|
|
426
|
-
if (!session) {
|
|
427
|
-
throw new Error(`session not found: ${input.sessionId}`);
|
|
428
|
-
}
|
|
344
|
+
const session = this.getSessionOrThrow(input.sessionId);
|
|
429
345
|
session.config.telemetry?.capture({
|
|
430
346
|
event: "session.input_sent",
|
|
431
347
|
properties: {
|
|
@@ -455,37 +371,34 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
455
371
|
sessionId: string,
|
|
456
372
|
): Promise<SessionAccumulatedUsage | undefined> {
|
|
457
373
|
const usage = this.usageBySession.get(sessionId);
|
|
458
|
-
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
return { ...usage };
|
|
374
|
+
return usage ? { ...usage } : undefined;
|
|
462
375
|
}
|
|
463
376
|
|
|
464
|
-
async abort(sessionId: string): Promise<void> {
|
|
377
|
+
async abort(sessionId: string, reason?: unknown): Promise<void> {
|
|
465
378
|
const session = this.sessions.get(sessionId);
|
|
466
|
-
if (!session)
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
379
|
+
if (!session) return;
|
|
469
380
|
session.config.telemetry?.capture({
|
|
470
381
|
event: "session.aborted",
|
|
471
382
|
properties: { sessionId },
|
|
472
383
|
});
|
|
473
384
|
session.aborting = true;
|
|
474
|
-
|
|
385
|
+
(
|
|
386
|
+
session.agent as Agent & {
|
|
387
|
+
abort: (abortReason?: unknown) => void;
|
|
388
|
+
}
|
|
389
|
+
).abort(reason);
|
|
475
390
|
}
|
|
476
391
|
|
|
477
392
|
async stop(sessionId: string): Promise<void> {
|
|
478
393
|
const session = this.sessions.get(sessionId);
|
|
479
|
-
if (!session)
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
394
|
+
if (!session) return;
|
|
482
395
|
session.config.telemetry?.capture({
|
|
483
396
|
event: "session.stopped",
|
|
484
397
|
properties: { sessionId },
|
|
485
398
|
});
|
|
486
399
|
await this.shutdownSession(session, {
|
|
487
400
|
status: "cancelled",
|
|
488
|
-
exitCode:
|
|
401
|
+
exitCode: 0,
|
|
489
402
|
shutdownReason: "session_stop",
|
|
490
403
|
endReason: "stopped",
|
|
491
404
|
});
|
|
@@ -493,18 +406,16 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
493
406
|
|
|
494
407
|
async dispose(reason = "session_manager_dispose"): Promise<void> {
|
|
495
408
|
const sessions = [...this.sessions.values()];
|
|
496
|
-
if (sessions.length === 0)
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
409
|
+
if (sessions.length === 0) return;
|
|
499
410
|
await Promise.allSettled(
|
|
500
|
-
sessions.map(
|
|
501
|
-
|
|
411
|
+
sessions.map((session) =>
|
|
412
|
+
this.shutdownSession(session, {
|
|
502
413
|
status: "cancelled",
|
|
503
|
-
exitCode:
|
|
414
|
+
exitCode: 0,
|
|
504
415
|
shutdownReason: reason,
|
|
505
416
|
endReason: "disposed",
|
|
506
|
-
})
|
|
507
|
-
|
|
417
|
+
}),
|
|
418
|
+
),
|
|
508
419
|
);
|
|
509
420
|
this.usageBySession.clear();
|
|
510
421
|
}
|
|
@@ -516,7 +427,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
516
427
|
|
|
517
428
|
async list(limit = 200): Promise<SessionRecord[]> {
|
|
518
429
|
const rows = await this.listRows(limit);
|
|
519
|
-
return rows.map(
|
|
430
|
+
return rows.map(toSessionRecord);
|
|
520
431
|
}
|
|
521
432
|
|
|
522
433
|
async delete(sessionId: string): Promise<boolean> {
|
|
@@ -535,9 +446,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
535
446
|
|
|
536
447
|
async readTranscript(sessionId: string, maxChars?: number): Promise<string> {
|
|
537
448
|
const row = await this.getRow(sessionId);
|
|
538
|
-
if (!row?.transcript_path || !existsSync(row.transcript_path))
|
|
539
|
-
return "";
|
|
540
|
-
}
|
|
449
|
+
if (!row?.transcript_path || !existsSync(row.transcript_path)) return "";
|
|
541
450
|
const raw = readFileSync(row.transcript_path, "utf8");
|
|
542
451
|
if (typeof maxChars === "number" && Number.isFinite(maxChars)) {
|
|
543
452
|
return raw.slice(-Math.max(0, Math.floor(maxChars)));
|
|
@@ -548,21 +457,17 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
548
457
|
async readMessages(sessionId: string): Promise<LlmsProviders.Message[]> {
|
|
549
458
|
const row = await this.getRow(sessionId);
|
|
550
459
|
const messagesPath = row?.messages_path?.trim();
|
|
551
|
-
if (!messagesPath || !existsSync(messagesPath))
|
|
552
|
-
return [];
|
|
553
|
-
}
|
|
460
|
+
if (!messagesPath || !existsSync(messagesPath)) return [];
|
|
554
461
|
try {
|
|
555
|
-
const raw = readFileSync(messagesPath, "utf8");
|
|
556
|
-
if (!raw
|
|
557
|
-
|
|
462
|
+
const raw = readFileSync(messagesPath, "utf8").trim();
|
|
463
|
+
if (!raw) return [];
|
|
464
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
465
|
+
if (Array.isArray(parsed)) return parsed as LlmsProviders.Message[];
|
|
466
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
467
|
+
const messages = (parsed as { messages?: unknown }).messages;
|
|
468
|
+
if (Array.isArray(messages)) return messages as LlmsProviders.Message[];
|
|
558
469
|
}
|
|
559
|
-
|
|
560
|
-
const messages = Array.isArray(parsed)
|
|
561
|
-
? parsed
|
|
562
|
-
: Array.isArray((parsed as { messages?: unknown }).messages)
|
|
563
|
-
? ((parsed as { messages: unknown[] }).messages ?? [])
|
|
564
|
-
: [];
|
|
565
|
-
return messages as LlmsProviders.Message[];
|
|
470
|
+
return [];
|
|
566
471
|
} catch {
|
|
567
472
|
return [];
|
|
568
473
|
}
|
|
@@ -570,15 +475,11 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
570
475
|
|
|
571
476
|
async readHooks(sessionId: string, limit = 200): Promise<unknown[]> {
|
|
572
477
|
const row = await this.getRow(sessionId);
|
|
573
|
-
if (!row?.hook_path || !existsSync(row.hook_path))
|
|
574
|
-
return [];
|
|
575
|
-
}
|
|
478
|
+
if (!row?.hook_path || !existsSync(row.hook_path)) return [];
|
|
576
479
|
const lines = readFileSync(row.hook_path, "utf8")
|
|
577
480
|
.split("\n")
|
|
578
|
-
.
|
|
579
|
-
|
|
580
|
-
const sliced = lines.slice(-Math.max(1, Math.floor(limit)));
|
|
581
|
-
return sliced.map((line) => {
|
|
481
|
+
.filter((line) => line.trim().length > 0);
|
|
482
|
+
return lines.slice(-Math.max(1, Math.floor(limit))).map((line) => {
|
|
582
483
|
try {
|
|
583
484
|
return JSON.parse(line) as unknown;
|
|
584
485
|
} catch {
|
|
@@ -594,6 +495,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
594
495
|
};
|
|
595
496
|
}
|
|
596
497
|
|
|
498
|
+
async updateSessionModel(sessionId: string, modelId: string): Promise<void> {
|
|
499
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
500
|
+
session.config.modelId = modelId;
|
|
501
|
+
this.updateAgentConnection(session, { modelId });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ── Turn execution ──────────────────────────────────────────────────
|
|
505
|
+
|
|
597
506
|
private async runTurn(
|
|
598
507
|
session: ActiveSession,
|
|
599
508
|
input: {
|
|
@@ -604,9 +513,8 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
604
513
|
): Promise<AgentResult> {
|
|
605
514
|
const preparedInput = await this.prepareTurnInput(session, input);
|
|
606
515
|
const prompt = preparedInput.prompt.trim();
|
|
607
|
-
if (!prompt)
|
|
608
|
-
|
|
609
|
-
}
|
|
516
|
+
if (!prompt) throw new Error("prompt cannot be empty");
|
|
517
|
+
|
|
610
518
|
if (!session.artifacts && !session.pendingPrompt) {
|
|
611
519
|
session.pendingPrompt = prompt;
|
|
612
520
|
}
|
|
@@ -620,12 +528,10 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
620
528
|
preparedInput.userFiles,
|
|
621
529
|
);
|
|
622
530
|
|
|
623
|
-
while (
|
|
624
|
-
const updates = await
|
|
625
|
-
if (updates.length === 0)
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
const continuationPrompt = this.buildTeamRunContinuationPrompt(
|
|
531
|
+
while (shouldAutoContinueTeamRuns(session, result.finishReason)) {
|
|
532
|
+
const updates = await waitForTeamRunUpdates(session);
|
|
533
|
+
if (updates.length === 0) break;
|
|
534
|
+
const continuationPrompt = buildTeamRunContinuationPrompt(
|
|
629
535
|
session,
|
|
630
536
|
updates,
|
|
631
537
|
);
|
|
@@ -643,28 +549,44 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
643
549
|
): Promise<AgentResult> {
|
|
644
550
|
const shouldContinue =
|
|
645
551
|
session.started || session.agent.getMessages().length > 0;
|
|
646
|
-
const baselineMessages =
|
|
552
|
+
const baselineMessages =
|
|
553
|
+
session.persistedMessages ?? session.agent.getMessages();
|
|
647
554
|
const usageBaseline =
|
|
648
555
|
this.usageBySession.get(session.sessionId) ??
|
|
649
556
|
createInitialAccumulatedUsage();
|
|
650
557
|
session.turnUsageBaseline = usageBaseline;
|
|
558
|
+
|
|
559
|
+
captureModeSwitch(
|
|
560
|
+
session.config.telemetry,
|
|
561
|
+
session.sessionId,
|
|
562
|
+
session.config.mode,
|
|
563
|
+
);
|
|
564
|
+
captureConversationTurnEvent(session.config.telemetry, {
|
|
565
|
+
ulid: session.sessionId,
|
|
566
|
+
provider: session.config.providerId,
|
|
567
|
+
model: session.config.modelId,
|
|
568
|
+
source: "user",
|
|
569
|
+
mode: session.config.mode,
|
|
570
|
+
isNativeToolCall: false,
|
|
571
|
+
});
|
|
572
|
+
|
|
651
573
|
try {
|
|
652
|
-
const
|
|
653
|
-
?
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
baselineMessages,
|
|
662
|
-
);
|
|
574
|
+
const runFn = shouldContinue
|
|
575
|
+
? () => session.agent.continue(prompt, userImages, userFiles)
|
|
576
|
+
: () => session.agent.run(prompt, userImages, userFiles);
|
|
577
|
+
const result = await this.runWithAuthRetry(
|
|
578
|
+
session,
|
|
579
|
+
runFn,
|
|
580
|
+
baselineMessages,
|
|
581
|
+
);
|
|
582
|
+
|
|
663
583
|
session.started = true;
|
|
664
584
|
const persistedMessages = withLatestAssistantTurnMetadata(
|
|
665
585
|
result.messages,
|
|
666
586
|
result,
|
|
587
|
+
baselineMessages,
|
|
667
588
|
);
|
|
589
|
+
session.persistedMessages = persistedMessages;
|
|
668
590
|
this.usageBySession.set(
|
|
669
591
|
session.sessionId,
|
|
670
592
|
accumulateUsageTotals(usageBaseline, result.usage),
|
|
@@ -677,7 +599,6 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
677
599
|
);
|
|
678
600
|
return result;
|
|
679
601
|
} catch (error) {
|
|
680
|
-
// Persist whatever was rendered so far even when a turn fails.
|
|
681
602
|
await this.invoke<void>(
|
|
682
603
|
"persistSessionMessages",
|
|
683
604
|
session.sessionId,
|
|
@@ -698,7 +619,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
698
619
|
userFiles?: string[];
|
|
699
620
|
},
|
|
700
621
|
): Promise<PreparedTurnInput> {
|
|
701
|
-
const mentionBaseDir = session.config
|
|
622
|
+
const mentionBaseDir = resolveWorkspacePath(session.config);
|
|
702
623
|
const normalizedPrompt = normalizeUserInput(input.prompt).trim();
|
|
703
624
|
if (!normalizedPrompt) {
|
|
704
625
|
return {
|
|
@@ -715,10 +636,9 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
715
636
|
normalizedPrompt,
|
|
716
637
|
mentionBaseDir,
|
|
717
638
|
);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
);
|
|
639
|
+
emitMentionTelemetry(session.config.telemetry, enriched);
|
|
640
|
+
|
|
641
|
+
const prompt = formatModePrompt(enriched.prompt, session.config.mode);
|
|
722
642
|
const explicitUserFiles = this.resolveAbsoluteFilePaths(
|
|
723
643
|
session.config.cwd,
|
|
724
644
|
input.userFiles,
|
|
@@ -738,23 +658,11 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
738
658
|
};
|
|
739
659
|
}
|
|
740
660
|
|
|
741
|
-
|
|
742
|
-
if (!paths || paths.length === 0) {
|
|
743
|
-
return [];
|
|
744
|
-
}
|
|
745
|
-
const resolved = paths
|
|
746
|
-
.map((filePath) => filePath.trim())
|
|
747
|
-
.filter((filePath) => filePath.length > 0)
|
|
748
|
-
.map((filePath) =>
|
|
749
|
-
isAbsolute(filePath) ? filePath : resolve(cwd, filePath),
|
|
750
|
-
);
|
|
751
|
-
return Array.from(new Set(resolved));
|
|
752
|
-
}
|
|
661
|
+
// ── Session lifecycle ───────────────────────────────────────────────
|
|
753
662
|
|
|
754
663
|
private async ensureSessionPersisted(session: ActiveSession): Promise<void> {
|
|
755
|
-
if (session.artifacts)
|
|
756
|
-
|
|
757
|
-
}
|
|
664
|
+
if (session.artifacts) return;
|
|
665
|
+
const workspacePath = resolveWorkspacePath(session.config);
|
|
758
666
|
session.artifacts = (await this.invoke("createRootSessionWithArtifacts", {
|
|
759
667
|
sessionId: session.sessionId,
|
|
760
668
|
source: session.source,
|
|
@@ -763,7 +671,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
763
671
|
provider: session.config.providerId,
|
|
764
672
|
model: session.config.modelId,
|
|
765
673
|
cwd: session.config.cwd,
|
|
766
|
-
workspaceRoot:
|
|
674
|
+
workspaceRoot: workspacePath,
|
|
767
675
|
teamName: session.config.teamName,
|
|
768
676
|
enableTools: session.config.enableTools,
|
|
769
677
|
enableSpawn: session.config.enableSpawnAgent,
|
|
@@ -777,24 +685,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
777
685
|
session: ActiveSession,
|
|
778
686
|
finishReason: AgentResult["finishReason"],
|
|
779
687
|
): Promise<void> {
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
endReason: finishReason,
|
|
789
|
-
});
|
|
790
|
-
} else {
|
|
791
|
-
await this.shutdownSession(session, {
|
|
792
|
-
status: "completed",
|
|
793
|
-
exitCode: 0,
|
|
794
|
-
shutdownReason: "session_complete",
|
|
795
|
-
endReason: finishReason,
|
|
796
|
-
});
|
|
797
|
-
}
|
|
688
|
+
if (hasPendingTeamRunWork(session)) return;
|
|
689
|
+
const isAborted = finishReason === "aborted" || session.aborting;
|
|
690
|
+
await this.shutdownSession(session, {
|
|
691
|
+
status: isAborted ? "cancelled" : "completed",
|
|
692
|
+
exitCode: 0,
|
|
693
|
+
shutdownReason: "session_complete",
|
|
694
|
+
endReason: finishReason,
|
|
695
|
+
});
|
|
798
696
|
}
|
|
799
697
|
|
|
800
698
|
private async failSession(session: ActiveSession): Promise<void> {
|
|
@@ -815,17 +713,23 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
815
713
|
endReason: string;
|
|
816
714
|
},
|
|
817
715
|
): Promise<void> {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
716
|
+
if (input.status === "completed") {
|
|
717
|
+
captureTaskCompleted(session.config.telemetry, {
|
|
718
|
+
ulid: session.sessionId,
|
|
719
|
+
provider: session.config.providerId,
|
|
720
|
+
modelId: session.config.modelId,
|
|
721
|
+
mode: session.config.mode,
|
|
722
|
+
durationMs: Date.now() - Date.parse(session.startedAt),
|
|
723
|
+
});
|
|
821
724
|
}
|
|
725
|
+
notifyTeamRunWaiters(session);
|
|
726
|
+
|
|
822
727
|
if (session.artifacts) {
|
|
728
|
+
await this.updateStatus(session, input.status, input.exitCode);
|
|
823
729
|
await session.agent.shutdown(input.shutdownReason);
|
|
824
730
|
}
|
|
825
731
|
await Promise.resolve(session.runtime.shutdown(input.shutdownReason));
|
|
826
|
-
|
|
827
|
-
await session.pluginSandboxShutdown();
|
|
828
|
-
}
|
|
732
|
+
await session.pluginSandboxShutdown?.();
|
|
829
733
|
this.sessions.delete(session.sessionId);
|
|
830
734
|
this.emit({
|
|
831
735
|
type: "ended",
|
|
@@ -842,18 +746,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
842
746
|
status: SessionStatus,
|
|
843
747
|
exitCode?: number | null,
|
|
844
748
|
): Promise<void> {
|
|
845
|
-
if (!session.artifacts)
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
749
|
+
if (!session.artifacts) return;
|
|
848
750
|
const result = await this.invoke<{ updated: boolean; endedAt?: string }>(
|
|
849
751
|
"updateSessionStatus",
|
|
850
752
|
session.sessionId,
|
|
851
753
|
status,
|
|
852
754
|
exitCode,
|
|
853
755
|
);
|
|
854
|
-
if (!result.updated)
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
756
|
+
if (!result.updated) return;
|
|
857
757
|
session.artifacts.manifest.status = status;
|
|
858
758
|
session.artifacts.manifest.ended_at = result.endedAt ?? nowIso();
|
|
859
759
|
session.artifacts.manifest.exit_code =
|
|
@@ -866,51 +766,47 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
866
766
|
this.emitStatus(session.sessionId, status);
|
|
867
767
|
}
|
|
868
768
|
|
|
869
|
-
|
|
870
|
-
this.emit({
|
|
871
|
-
type: "status",
|
|
872
|
-
payload: { sessionId, status },
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
private async listRows(limit: number): Promise<SessionRowShape[]> {
|
|
877
|
-
const normalizedLimit = Math.max(1, Math.floor(limit));
|
|
878
|
-
return this.invoke<SessionRowShape[]>(
|
|
879
|
-
"listSessions",
|
|
880
|
-
Math.min(normalizedLimit, MAX_SCAN_LIMIT),
|
|
881
|
-
);
|
|
882
|
-
}
|
|
769
|
+
// ── Agent event handling ────────────────────────────────────────────
|
|
883
770
|
|
|
884
|
-
private
|
|
771
|
+
private onAgentEvent(
|
|
885
772
|
sessionId: string,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
773
|
+
config: CoreSessionConfig,
|
|
774
|
+
event: AgentEvent,
|
|
775
|
+
): void {
|
|
776
|
+
const ctx: AgentEventContext = {
|
|
777
|
+
sessionId,
|
|
778
|
+
config,
|
|
779
|
+
liveSession: this.sessions.get(sessionId),
|
|
780
|
+
usageBySession: this.usageBySession,
|
|
781
|
+
persistMessages: (sid, messages, systemPrompt) => {
|
|
782
|
+
void this.invoke<void>(
|
|
783
|
+
"persistSessionMessages",
|
|
784
|
+
sid,
|
|
785
|
+
messages,
|
|
786
|
+
systemPrompt,
|
|
787
|
+
);
|
|
788
|
+
},
|
|
789
|
+
emit: (e) => this.emit(e),
|
|
790
|
+
};
|
|
791
|
+
handleAgentEvent(ctx, event);
|
|
893
792
|
}
|
|
894
793
|
|
|
794
|
+
// ── Spawn / sub-agents ──────────────────────────────────────────────
|
|
795
|
+
|
|
895
796
|
private createSpawnTool(
|
|
896
797
|
config: CoreSessionConfig,
|
|
897
798
|
rootSessionId: string,
|
|
898
799
|
): Tool {
|
|
899
|
-
const createBaseTools = () => {
|
|
900
|
-
if (!config.enableTools) {
|
|
901
|
-
return [] as Tool[];
|
|
902
|
-
}
|
|
903
|
-
const preset =
|
|
904
|
-
config.mode === "plan" ? ToolPresets.readonly : ToolPresets.development;
|
|
905
|
-
return createBuiltinTools({
|
|
906
|
-
cwd: config.cwd,
|
|
907
|
-
...preset,
|
|
908
|
-
executors: this.defaultToolExecutors,
|
|
909
|
-
});
|
|
910
|
-
};
|
|
911
|
-
|
|
912
800
|
const createSubAgentTools = () => {
|
|
913
|
-
const tools =
|
|
801
|
+
const tools: Tool[] = config.enableTools
|
|
802
|
+
? createBuiltinTools({
|
|
803
|
+
cwd: config.cwd,
|
|
804
|
+
...(config.mode === "plan"
|
|
805
|
+
? ToolPresets.readonly
|
|
806
|
+
: ToolPresets.development),
|
|
807
|
+
executors: this.defaultToolExecutors,
|
|
808
|
+
})
|
|
809
|
+
: [];
|
|
914
810
|
if (config.enableSpawnAgent) {
|
|
915
811
|
tools.push(this.createSpawnTool(config, rootSessionId));
|
|
916
812
|
}
|
|
@@ -936,242 +832,53 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
936
832
|
requestToolApproval: this.defaultRequestToolApproval,
|
|
937
833
|
logger: config.logger,
|
|
938
834
|
onSubAgentStart: (context) => {
|
|
835
|
+
this.subAgentStarts.set(context.subAgentId, {
|
|
836
|
+
startedAt: Date.now(),
|
|
837
|
+
rootSessionId,
|
|
838
|
+
});
|
|
939
839
|
void this.invokeOptional("handleSubAgentStart", rootSessionId, context);
|
|
940
840
|
},
|
|
941
841
|
onSubAgentEnd: (context) => {
|
|
842
|
+
const started = this.subAgentStarts.get(context.subAgentId);
|
|
843
|
+
const durationMs = started ? Date.now() - started.startedAt : 0;
|
|
844
|
+
const outputLines = context.result?.text
|
|
845
|
+
? context.result.text.split("\n").length
|
|
846
|
+
: 0;
|
|
847
|
+
captureSubagentExecution(config.telemetry, {
|
|
848
|
+
ulid: rootSessionId,
|
|
849
|
+
durationMs,
|
|
850
|
+
outputLines,
|
|
851
|
+
success: !context.error,
|
|
852
|
+
});
|
|
853
|
+
this.subAgentStarts.delete(context.subAgentId);
|
|
942
854
|
void this.invokeOptional("handleSubAgentEnd", rootSessionId, context);
|
|
943
855
|
},
|
|
944
856
|
}) as Tool;
|
|
945
857
|
}
|
|
946
858
|
|
|
859
|
+
// ── Team run coordination ───────────────────────────────────────────
|
|
860
|
+
|
|
947
861
|
private async handleTeamEvent(
|
|
948
862
|
rootSessionId: string,
|
|
949
863
|
event: TeamEvent,
|
|
950
864
|
): Promise<void> {
|
|
951
865
|
const session = this.sessions.get(rootSessionId);
|
|
952
866
|
if (session) {
|
|
953
|
-
|
|
954
|
-
case "run_queued":
|
|
955
|
-
case "run_started":
|
|
956
|
-
session.activeTeamRunIds.add(event.run.id);
|
|
957
|
-
break;
|
|
958
|
-
case "run_completed":
|
|
959
|
-
case "run_failed":
|
|
960
|
-
case "run_cancelled":
|
|
961
|
-
case "run_interrupted": {
|
|
962
|
-
let runError: string | undefined;
|
|
963
|
-
if (event.type === "run_failed") {
|
|
964
|
-
runError = event.run.error;
|
|
965
|
-
} else if (event.type === "run_cancelled") {
|
|
966
|
-
runError = event.run.error ?? event.reason;
|
|
967
|
-
} else if (event.type === "run_interrupted") {
|
|
968
|
-
runError = event.run.error ?? event.reason;
|
|
969
|
-
}
|
|
970
|
-
session.activeTeamRunIds.delete(event.run.id);
|
|
971
|
-
session.pendingTeamRunUpdates.push({
|
|
972
|
-
runId: event.run.id,
|
|
973
|
-
agentId: event.run.agentId,
|
|
974
|
-
taskId: event.run.taskId,
|
|
975
|
-
status: event.type.replace("run_", "") as TeamRunUpdate["status"],
|
|
976
|
-
error: runError,
|
|
977
|
-
iterations: event.run.result?.iterations,
|
|
978
|
-
});
|
|
979
|
-
this.notifyTeamRunWaiters(session);
|
|
980
|
-
break;
|
|
981
|
-
}
|
|
982
|
-
default:
|
|
983
|
-
break;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
switch (event.type) {
|
|
988
|
-
case "task_start":
|
|
989
|
-
await this.invokeOptional(
|
|
990
|
-
"onTeamTaskStart",
|
|
991
|
-
rootSessionId,
|
|
992
|
-
event.agentId,
|
|
993
|
-
event.message,
|
|
994
|
-
);
|
|
995
|
-
break;
|
|
996
|
-
case "task_end":
|
|
997
|
-
if (event.error) {
|
|
998
|
-
await this.invokeOptional(
|
|
999
|
-
"onTeamTaskEnd",
|
|
1000
|
-
rootSessionId,
|
|
1001
|
-
event.agentId,
|
|
1002
|
-
"failed",
|
|
1003
|
-
`[error] ${event.error.message}`,
|
|
1004
|
-
event.messages,
|
|
1005
|
-
);
|
|
1006
|
-
break;
|
|
1007
|
-
}
|
|
1008
|
-
if (event.result?.finishReason === "aborted") {
|
|
1009
|
-
await this.invokeOptional(
|
|
1010
|
-
"onTeamTaskEnd",
|
|
1011
|
-
rootSessionId,
|
|
1012
|
-
event.agentId,
|
|
1013
|
-
"cancelled",
|
|
1014
|
-
"[done] aborted",
|
|
1015
|
-
event.result.messages,
|
|
1016
|
-
);
|
|
1017
|
-
break;
|
|
1018
|
-
}
|
|
1019
|
-
await this.invokeOptional(
|
|
1020
|
-
"onTeamTaskEnd",
|
|
1021
|
-
rootSessionId,
|
|
1022
|
-
event.agentId,
|
|
1023
|
-
"completed",
|
|
1024
|
-
`[done] ${event.result?.finishReason ?? "completed"}`,
|
|
1025
|
-
event.result?.messages,
|
|
1026
|
-
);
|
|
1027
|
-
break;
|
|
1028
|
-
default:
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
if (!session?.runtime.teamRuntime) {
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
const teamName = session.config.teamName?.trim() || "team";
|
|
1036
|
-
this.emit({
|
|
1037
|
-
type: "team_progress",
|
|
1038
|
-
payload: {
|
|
1039
|
-
sessionId: rootSessionId,
|
|
1040
|
-
teamName,
|
|
1041
|
-
lifecycle: toTeamProgressLifecycleEvent({
|
|
1042
|
-
teamName,
|
|
1043
|
-
sessionId: rootSessionId,
|
|
1044
|
-
event,
|
|
1045
|
-
}),
|
|
1046
|
-
summary: buildTeamProgressSummary(
|
|
1047
|
-
teamName,
|
|
1048
|
-
session.runtime.teamRuntime.exportState(),
|
|
1049
|
-
),
|
|
1050
|
-
},
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
private hasPendingTeamRunWork(session: ActiveSession): boolean {
|
|
1055
|
-
return (
|
|
1056
|
-
session.activeTeamRunIds.size > 0 ||
|
|
1057
|
-
session.pendingTeamRunUpdates.length > 0
|
|
1058
|
-
);
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
private shouldAutoContinueTeamRuns(
|
|
1062
|
-
session: ActiveSession,
|
|
1063
|
-
finishReason: AgentResult["finishReason"],
|
|
1064
|
-
): boolean {
|
|
1065
|
-
if (
|
|
1066
|
-
session.aborting ||
|
|
1067
|
-
finishReason === "aborted" ||
|
|
1068
|
-
finishReason === "error"
|
|
1069
|
-
) {
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
if (!session.config.enableAgentTeams) {
|
|
1073
|
-
return false;
|
|
1074
|
-
}
|
|
1075
|
-
return this.hasPendingTeamRunWork(session);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
private notifyTeamRunWaiters(session: ActiveSession): void {
|
|
1079
|
-
const waiters = session.teamRunWaiters.splice(0);
|
|
1080
|
-
for (const resolve of waiters) {
|
|
1081
|
-
resolve();
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
private async waitForTeamRunUpdates(
|
|
1086
|
-
session: ActiveSession,
|
|
1087
|
-
): Promise<TeamRunUpdate[]> {
|
|
1088
|
-
while (true) {
|
|
1089
|
-
if (session.aborting) {
|
|
1090
|
-
return [];
|
|
1091
|
-
}
|
|
1092
|
-
if (session.pendingTeamRunUpdates.length > 0) {
|
|
1093
|
-
const updates = [...session.pendingTeamRunUpdates];
|
|
1094
|
-
session.pendingTeamRunUpdates.length = 0;
|
|
1095
|
-
return updates;
|
|
1096
|
-
}
|
|
1097
|
-
if (session.activeTeamRunIds.size === 0) {
|
|
1098
|
-
return [];
|
|
1099
|
-
}
|
|
1100
|
-
await new Promise<void>((resolve) => {
|
|
1101
|
-
session.teamRunWaiters.push(resolve);
|
|
1102
|
-
});
|
|
867
|
+
trackTeamRunState(session, event);
|
|
1103
868
|
}
|
|
1104
|
-
}
|
|
1105
869
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
const lines = updates.map((update) => {
|
|
1111
|
-
const base = `- ${update.runId} (${update.agentId}) -> ${update.status}`;
|
|
1112
|
-
const task = update.taskId ? ` task=${update.taskId}` : "";
|
|
1113
|
-
const iterations =
|
|
1114
|
-
typeof update.iterations === "number"
|
|
1115
|
-
? ` iterations=${update.iterations}`
|
|
1116
|
-
: "";
|
|
1117
|
-
const error = update.error ? ` error=${update.error}` : "";
|
|
1118
|
-
return `${base}${task}${iterations}${error}`;
|
|
1119
|
-
});
|
|
1120
|
-
const remaining = session.activeTeamRunIds.size;
|
|
1121
|
-
const instruction =
|
|
1122
|
-
remaining > 0
|
|
1123
|
-
? `There are still ${remaining} teammate run(s) in progress. Continue coordination and decide whether to wait for more updates.`
|
|
1124
|
-
: "No teammate runs are currently in progress. Continue coordination using these updates.";
|
|
1125
|
-
return formatUserInputBlock(
|
|
1126
|
-
`System-delivered teammate async run updates:\n${lines.join("\n")}\n\n${instruction}`,
|
|
1127
|
-
session.config.mode === "plan" ? "plan" : "act",
|
|
870
|
+
await dispatchTeamEventToBackend(
|
|
871
|
+
rootSessionId,
|
|
872
|
+
event,
|
|
873
|
+
this.invokeOptional.bind(this),
|
|
1128
874
|
);
|
|
1129
|
-
}
|
|
1130
875
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
listener(event);
|
|
876
|
+
if (session) {
|
|
877
|
+
emitTeamProgress(session, rootSessionId, event, (e) => this.emit(e));
|
|
1134
878
|
}
|
|
1135
879
|
}
|
|
1136
880
|
|
|
1137
|
-
|
|
1138
|
-
const callable = (
|
|
1139
|
-
this.sessionService as unknown as Record<string, unknown>
|
|
1140
|
-
)[method];
|
|
1141
|
-
if (typeof callable !== "function") {
|
|
1142
|
-
throw new Error(`session service method not available: ${method}`);
|
|
1143
|
-
}
|
|
1144
|
-
const fn = callable as (...params: unknown[]) => T | Promise<T>;
|
|
1145
|
-
return Promise.resolve(fn.apply(this.sessionService, args));
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
private async invokeOptional(
|
|
1149
|
-
method: string,
|
|
1150
|
-
...args: unknown[]
|
|
1151
|
-
): Promise<void> {
|
|
1152
|
-
const callable = (
|
|
1153
|
-
this.sessionService as unknown as Record<string, unknown>
|
|
1154
|
-
)[method];
|
|
1155
|
-
if (typeof callable !== "function") {
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
const fn = callable as (...params: unknown[]) => unknown;
|
|
1159
|
-
await Promise.resolve(fn.apply(this.sessionService, args));
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
private async invokeOptionalValue<T = unknown>(
|
|
1163
|
-
method: string,
|
|
1164
|
-
...args: unknown[]
|
|
1165
|
-
): Promise<T | undefined> {
|
|
1166
|
-
const callable = (
|
|
1167
|
-
this.sessionService as unknown as Record<string, unknown>
|
|
1168
|
-
)[method];
|
|
1169
|
-
if (typeof callable !== "function") {
|
|
1170
|
-
return undefined;
|
|
1171
|
-
}
|
|
1172
|
-
const fn = callable as (...params: unknown[]) => T | Promise<T>;
|
|
1173
|
-
return await Promise.resolve(fn.apply(this.sessionService, args));
|
|
1174
|
-
}
|
|
881
|
+
// ── OAuth & auth ────────────────────────────────────────────────────
|
|
1175
882
|
|
|
1176
883
|
private async runWithAuthRetry(
|
|
1177
884
|
session: ActiveSession,
|
|
@@ -1181,37 +888,15 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
1181
888
|
try {
|
|
1182
889
|
return await run();
|
|
1183
890
|
} catch (error) {
|
|
1184
|
-
if (!
|
|
891
|
+
if (!isLikelyAuthError(error, session.config.providerId)) {
|
|
1185
892
|
throw error;
|
|
1186
893
|
}
|
|
1187
|
-
|
|
1188
894
|
await this.syncOAuthCredentials(session, { forceRefresh: true });
|
|
1189
895
|
session.agent.restore(baselineMessages);
|
|
1190
896
|
return run();
|
|
1191
897
|
}
|
|
1192
898
|
}
|
|
1193
899
|
|
|
1194
|
-
private isLikelyAuthError(error: unknown, providerId: string): boolean {
|
|
1195
|
-
if (
|
|
1196
|
-
providerId !== "cline" &&
|
|
1197
|
-
providerId !== "oca" &&
|
|
1198
|
-
providerId !== "openai-codex"
|
|
1199
|
-
) {
|
|
1200
|
-
return false;
|
|
1201
|
-
}
|
|
1202
|
-
const message =
|
|
1203
|
-
error instanceof Error ? error.message.toLowerCase() : String(error);
|
|
1204
|
-
return (
|
|
1205
|
-
message.includes("401") ||
|
|
1206
|
-
message.includes("403") ||
|
|
1207
|
-
message.includes("unauthorized") ||
|
|
1208
|
-
message.includes("forbidden") ||
|
|
1209
|
-
message.includes("invalid token") ||
|
|
1210
|
-
message.includes("expired token") ||
|
|
1211
|
-
message.includes("authentication")
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
900
|
private async syncOAuthCredentials(
|
|
1216
901
|
session: ActiveSession,
|
|
1217
902
|
options?: { forceRefresh?: boolean },
|
|
@@ -1230,32 +915,117 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
1230
915
|
}
|
|
1231
916
|
throw error;
|
|
1232
917
|
}
|
|
1233
|
-
if (!resolved?.apiKey)
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (session.config.apiKey === resolved.apiKey) {
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
918
|
+
if (!resolved?.apiKey || session.config.apiKey === resolved.apiKey) return;
|
|
1239
919
|
session.config.apiKey = resolved.apiKey;
|
|
1240
|
-
|
|
1241
|
-
updateConnection?: (overrides: { apiKey?: string }) => void;
|
|
1242
|
-
};
|
|
1243
|
-
agentWithConnection.updateConnection?.({ apiKey: resolved.apiKey });
|
|
1244
|
-
// Propagate refreshed credentials to all active teammate agents
|
|
920
|
+
this.updateAgentConnection(session, { apiKey: resolved.apiKey });
|
|
1245
921
|
session.runtime.teamRuntime?.updateTeammateConnections({
|
|
1246
922
|
apiKey: resolved.apiKey,
|
|
1247
923
|
});
|
|
1248
924
|
}
|
|
1249
925
|
|
|
1250
|
-
|
|
926
|
+
// ── Utility methods ─────────────────────────────────────────────────
|
|
927
|
+
|
|
928
|
+
private getSessionOrThrow(sessionId: string): ActiveSession {
|
|
1251
929
|
const session = this.sessions.get(sessionId);
|
|
1252
|
-
if (!session) {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
930
|
+
if (!session) throw new Error(`session not found: ${sessionId}`);
|
|
931
|
+
return session;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private resolveAbsoluteFilePaths(cwd: string, paths?: string[]): string[] {
|
|
935
|
+
if (!paths || paths.length === 0) return [];
|
|
936
|
+
const resolved = paths
|
|
937
|
+
.map((p) => p.trim())
|
|
938
|
+
.filter((p) => p.length > 0)
|
|
939
|
+
.map((p) => (isAbsolute(p) ? p : resolve(cwd, p)));
|
|
940
|
+
return Array.from(new Set(resolved));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private updateAgentConnection(
|
|
944
|
+
session: ActiveSession,
|
|
945
|
+
overrides: { apiKey?: string; modelId?: string },
|
|
946
|
+
): void {
|
|
1256
947
|
const agentWithConnection = session.agent as Agent & {
|
|
1257
|
-
updateConnection?: (overrides: {
|
|
948
|
+
updateConnection?: (overrides: {
|
|
949
|
+
apiKey?: string;
|
|
950
|
+
modelId?: string;
|
|
951
|
+
}) => void;
|
|
1258
952
|
};
|
|
1259
|
-
agentWithConnection.updateConnection?.(
|
|
953
|
+
agentWithConnection.updateConnection?.(overrides);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
private emitStatus(sessionId: string, status: string): void {
|
|
957
|
+
this.emit({
|
|
958
|
+
type: "status",
|
|
959
|
+
payload: { sessionId, status },
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
private emit(event: CoreSessionEvent): void {
|
|
964
|
+
for (const listener of this.listeners) listener(event);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private async listRows(limit: number): Promise<SessionRowShape[]> {
|
|
968
|
+
return this.invoke<SessionRowShape[]>(
|
|
969
|
+
"listSessions",
|
|
970
|
+
Math.min(Math.max(1, Math.floor(limit)), MAX_SCAN_LIMIT),
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private async getRow(
|
|
975
|
+
sessionId: string,
|
|
976
|
+
): Promise<SessionRowShape | undefined> {
|
|
977
|
+
const target = sessionId.trim();
|
|
978
|
+
if (!target) return undefined;
|
|
979
|
+
const rows = await this.listRows(MAX_SCAN_LIMIT);
|
|
980
|
+
return rows.find((row) => row.session_id === target);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// ── Session service invocation ──────────────────────────────────────
|
|
984
|
+
|
|
985
|
+
private async invoke<T>(method: string, ...args: unknown[]): Promise<T> {
|
|
986
|
+
const callable = (
|
|
987
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
988
|
+
)[method];
|
|
989
|
+
if (typeof callable !== "function") {
|
|
990
|
+
throw new Error(`session service method not available: ${method}`);
|
|
991
|
+
}
|
|
992
|
+
return Promise.resolve(
|
|
993
|
+
(callable as (...params: unknown[]) => T | Promise<T>).apply(
|
|
994
|
+
this.sessionService,
|
|
995
|
+
args,
|
|
996
|
+
),
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
private async invokeOptional(
|
|
1001
|
+
method: string,
|
|
1002
|
+
...args: unknown[]
|
|
1003
|
+
): Promise<void> {
|
|
1004
|
+
const callable = (
|
|
1005
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
1006
|
+
)[method];
|
|
1007
|
+
if (typeof callable !== "function") return;
|
|
1008
|
+
await Promise.resolve(
|
|
1009
|
+
(callable as (...params: unknown[]) => unknown).apply(
|
|
1010
|
+
this.sessionService,
|
|
1011
|
+
args,
|
|
1012
|
+
),
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private async invokeOptionalValue<T = unknown>(
|
|
1017
|
+
method: string,
|
|
1018
|
+
...args: unknown[]
|
|
1019
|
+
): Promise<T | undefined> {
|
|
1020
|
+
const callable = (
|
|
1021
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
1022
|
+
)[method];
|
|
1023
|
+
if (typeof callable !== "function") return undefined;
|
|
1024
|
+
return await Promise.resolve(
|
|
1025
|
+
(callable as (...params: unknown[]) => T | Promise<T>).apply(
|
|
1026
|
+
this.sessionService,
|
|
1027
|
+
args,
|
|
1028
|
+
),
|
|
1029
|
+
);
|
|
1260
1030
|
}
|
|
1261
1031
|
}
|