@clinebot/core 0.0.6 → 0.0.10
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/agents/hooks-config-loader.d.ts +1 -0
- 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.d.ts +2 -0
- package/dist/index.node.js +164 -162
- 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/rpc-spawn-lease.d.ts +7 -0
- 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 +1 -1
- package/dist/session/utils/types.d.ts +1 -1
- package/dist/storage/provider-settings-legacy-migration.d.ts +25 -0
- 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 +48 -11
- package/dist/tools/types.d.ts +3 -3
- package/dist/types/config.d.ts +1 -1
- package/dist/types/events.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/agents/hooks-config-loader.ts +2 -0
- 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.node.ts +4 -0
- package/src/index.ts +27 -0
- package/src/input/file-indexer.test.ts +40 -0
- package/src/input/file-indexer.ts +21 -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.test.ts +51 -1
- package/src/runtime/hook-file-hooks.ts +91 -11
- 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.ts +367 -601
- package/src/session/rpc-spawn-lease.test.ts +49 -0
- package/src/session/rpc-spawn-lease.ts +122 -0
- 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-graph.ts +2 -0
- package/src/session/session-host.ts +21 -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 +1 -1
- package/src/session/utils/types.ts +1 -1
- package/src/storage/provider-settings-legacy-migration.test.ts +133 -1
- package/src/storage/provider-settings-legacy-migration.ts +63 -11
- package/src/telemetry/core-events.ts +344 -0
- package/src/tools/definitions.test.ts +203 -36
- package/src/tools/definitions.ts +66 -28
- package/src/tools/executors/editor.test.ts +35 -0
- package/src/tools/executors/editor.ts +33 -46
- 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 +88 -38
- package/src/tools/types.ts +7 -3
- package/src/types/config.ts +1 -1
- package/src/types/events.ts +6 -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,
|
|
@@ -388,10 +306,10 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
388
306
|
activeTeamRunIds: new Set<string>(),
|
|
389
307
|
pendingTeamRunUpdates: [],
|
|
390
308
|
teamRunWaiters: [],
|
|
391
|
-
pluginSandboxShutdown
|
|
309
|
+
pluginSandboxShutdown,
|
|
392
310
|
};
|
|
393
|
-
this.sessions.set(
|
|
394
|
-
this.emitStatus(
|
|
311
|
+
this.sessions.set(sessionId, active);
|
|
312
|
+
this.emitStatus(sessionId, "running");
|
|
395
313
|
|
|
396
314
|
let result: AgentResult | undefined;
|
|
397
315
|
try {
|
|
@@ -422,10 +340,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
422
340
|
}
|
|
423
341
|
|
|
424
342
|
async send(input: SendSessionInput): Promise<AgentResult | undefined> {
|
|
425
|
-
const session = this.
|
|
426
|
-
if (!session) {
|
|
427
|
-
throw new Error(`session not found: ${input.sessionId}`);
|
|
428
|
-
}
|
|
343
|
+
const session = this.getSessionOrThrow(input.sessionId);
|
|
429
344
|
session.config.telemetry?.capture({
|
|
430
345
|
event: "session.input_sent",
|
|
431
346
|
properties: {
|
|
@@ -455,37 +370,34 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
455
370
|
sessionId: string,
|
|
456
371
|
): Promise<SessionAccumulatedUsage | undefined> {
|
|
457
372
|
const usage = this.usageBySession.get(sessionId);
|
|
458
|
-
|
|
459
|
-
return undefined;
|
|
460
|
-
}
|
|
461
|
-
return { ...usage };
|
|
373
|
+
return usage ? { ...usage } : undefined;
|
|
462
374
|
}
|
|
463
375
|
|
|
464
|
-
async abort(sessionId: string): Promise<void> {
|
|
376
|
+
async abort(sessionId: string, reason?: unknown): Promise<void> {
|
|
465
377
|
const session = this.sessions.get(sessionId);
|
|
466
|
-
if (!session)
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
378
|
+
if (!session) return;
|
|
469
379
|
session.config.telemetry?.capture({
|
|
470
380
|
event: "session.aborted",
|
|
471
381
|
properties: { sessionId },
|
|
472
382
|
});
|
|
473
383
|
session.aborting = true;
|
|
474
|
-
|
|
384
|
+
(
|
|
385
|
+
session.agent as Agent & {
|
|
386
|
+
abort: (abortReason?: unknown) => void;
|
|
387
|
+
}
|
|
388
|
+
).abort(reason);
|
|
475
389
|
}
|
|
476
390
|
|
|
477
391
|
async stop(sessionId: string): Promise<void> {
|
|
478
392
|
const session = this.sessions.get(sessionId);
|
|
479
|
-
if (!session)
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
393
|
+
if (!session) return;
|
|
482
394
|
session.config.telemetry?.capture({
|
|
483
395
|
event: "session.stopped",
|
|
484
396
|
properties: { sessionId },
|
|
485
397
|
});
|
|
486
398
|
await this.shutdownSession(session, {
|
|
487
399
|
status: "cancelled",
|
|
488
|
-
exitCode:
|
|
400
|
+
exitCode: 0,
|
|
489
401
|
shutdownReason: "session_stop",
|
|
490
402
|
endReason: "stopped",
|
|
491
403
|
});
|
|
@@ -493,18 +405,16 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
493
405
|
|
|
494
406
|
async dispose(reason = "session_manager_dispose"): Promise<void> {
|
|
495
407
|
const sessions = [...this.sessions.values()];
|
|
496
|
-
if (sessions.length === 0)
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
408
|
+
if (sessions.length === 0) return;
|
|
499
409
|
await Promise.allSettled(
|
|
500
|
-
sessions.map(
|
|
501
|
-
|
|
410
|
+
sessions.map((session) =>
|
|
411
|
+
this.shutdownSession(session, {
|
|
502
412
|
status: "cancelled",
|
|
503
|
-
exitCode:
|
|
413
|
+
exitCode: 0,
|
|
504
414
|
shutdownReason: reason,
|
|
505
415
|
endReason: "disposed",
|
|
506
|
-
})
|
|
507
|
-
|
|
416
|
+
}),
|
|
417
|
+
),
|
|
508
418
|
);
|
|
509
419
|
this.usageBySession.clear();
|
|
510
420
|
}
|
|
@@ -516,7 +426,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
516
426
|
|
|
517
427
|
async list(limit = 200): Promise<SessionRecord[]> {
|
|
518
428
|
const rows = await this.listRows(limit);
|
|
519
|
-
return rows.map(
|
|
429
|
+
return rows.map(toSessionRecord);
|
|
520
430
|
}
|
|
521
431
|
|
|
522
432
|
async delete(sessionId: string): Promise<boolean> {
|
|
@@ -535,9 +445,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
535
445
|
|
|
536
446
|
async readTranscript(sessionId: string, maxChars?: number): Promise<string> {
|
|
537
447
|
const row = await this.getRow(sessionId);
|
|
538
|
-
if (!row?.transcript_path || !existsSync(row.transcript_path))
|
|
539
|
-
return "";
|
|
540
|
-
}
|
|
448
|
+
if (!row?.transcript_path || !existsSync(row.transcript_path)) return "";
|
|
541
449
|
const raw = readFileSync(row.transcript_path, "utf8");
|
|
542
450
|
if (typeof maxChars === "number" && Number.isFinite(maxChars)) {
|
|
543
451
|
return raw.slice(-Math.max(0, Math.floor(maxChars)));
|
|
@@ -548,21 +456,17 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
548
456
|
async readMessages(sessionId: string): Promise<LlmsProviders.Message[]> {
|
|
549
457
|
const row = await this.getRow(sessionId);
|
|
550
458
|
const messagesPath = row?.messages_path?.trim();
|
|
551
|
-
if (!messagesPath || !existsSync(messagesPath))
|
|
552
|
-
return [];
|
|
553
|
-
}
|
|
459
|
+
if (!messagesPath || !existsSync(messagesPath)) return [];
|
|
554
460
|
try {
|
|
555
|
-
const raw = readFileSync(messagesPath, "utf8");
|
|
556
|
-
if (!raw
|
|
557
|
-
|
|
461
|
+
const raw = readFileSync(messagesPath, "utf8").trim();
|
|
462
|
+
if (!raw) return [];
|
|
463
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
464
|
+
if (Array.isArray(parsed)) return parsed as LlmsProviders.Message[];
|
|
465
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
466
|
+
const messages = (parsed as { messages?: unknown }).messages;
|
|
467
|
+
if (Array.isArray(messages)) return messages as LlmsProviders.Message[];
|
|
558
468
|
}
|
|
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[];
|
|
469
|
+
return [];
|
|
566
470
|
} catch {
|
|
567
471
|
return [];
|
|
568
472
|
}
|
|
@@ -570,15 +474,11 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
570
474
|
|
|
571
475
|
async readHooks(sessionId: string, limit = 200): Promise<unknown[]> {
|
|
572
476
|
const row = await this.getRow(sessionId);
|
|
573
|
-
if (!row?.hook_path || !existsSync(row.hook_path))
|
|
574
|
-
return [];
|
|
575
|
-
}
|
|
477
|
+
if (!row?.hook_path || !existsSync(row.hook_path)) return [];
|
|
576
478
|
const lines = readFileSync(row.hook_path, "utf8")
|
|
577
479
|
.split("\n")
|
|
578
|
-
.
|
|
579
|
-
|
|
580
|
-
const sliced = lines.slice(-Math.max(1, Math.floor(limit)));
|
|
581
|
-
return sliced.map((line) => {
|
|
480
|
+
.filter((line) => line.trim().length > 0);
|
|
481
|
+
return lines.slice(-Math.max(1, Math.floor(limit))).map((line) => {
|
|
582
482
|
try {
|
|
583
483
|
return JSON.parse(line) as unknown;
|
|
584
484
|
} catch {
|
|
@@ -594,6 +494,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
594
494
|
};
|
|
595
495
|
}
|
|
596
496
|
|
|
497
|
+
async updateSessionModel(sessionId: string, modelId: string): Promise<void> {
|
|
498
|
+
const session = this.getSessionOrThrow(sessionId);
|
|
499
|
+
session.config.modelId = modelId;
|
|
500
|
+
this.updateAgentConnection(session, { modelId });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ── Turn execution ──────────────────────────────────────────────────
|
|
504
|
+
|
|
597
505
|
private async runTurn(
|
|
598
506
|
session: ActiveSession,
|
|
599
507
|
input: {
|
|
@@ -604,9 +512,8 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
604
512
|
): Promise<AgentResult> {
|
|
605
513
|
const preparedInput = await this.prepareTurnInput(session, input);
|
|
606
514
|
const prompt = preparedInput.prompt.trim();
|
|
607
|
-
if (!prompt)
|
|
608
|
-
|
|
609
|
-
}
|
|
515
|
+
if (!prompt) throw new Error("prompt cannot be empty");
|
|
516
|
+
|
|
610
517
|
if (!session.artifacts && !session.pendingPrompt) {
|
|
611
518
|
session.pendingPrompt = prompt;
|
|
612
519
|
}
|
|
@@ -620,12 +527,10 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
620
527
|
preparedInput.userFiles,
|
|
621
528
|
);
|
|
622
529
|
|
|
623
|
-
while (
|
|
624
|
-
const updates = await
|
|
625
|
-
if (updates.length === 0)
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
const continuationPrompt = this.buildTeamRunContinuationPrompt(
|
|
530
|
+
while (shouldAutoContinueTeamRuns(session, result.finishReason)) {
|
|
531
|
+
const updates = await waitForTeamRunUpdates(session);
|
|
532
|
+
if (updates.length === 0) break;
|
|
533
|
+
const continuationPrompt = buildTeamRunContinuationPrompt(
|
|
629
534
|
session,
|
|
630
535
|
updates,
|
|
631
536
|
);
|
|
@@ -648,18 +553,31 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
648
553
|
this.usageBySession.get(session.sessionId) ??
|
|
649
554
|
createInitialAccumulatedUsage();
|
|
650
555
|
session.turnUsageBaseline = usageBaseline;
|
|
556
|
+
|
|
557
|
+
captureModeSwitch(
|
|
558
|
+
session.config.telemetry,
|
|
559
|
+
session.sessionId,
|
|
560
|
+
session.config.mode,
|
|
561
|
+
);
|
|
562
|
+
captureConversationTurnEvent(session.config.telemetry, {
|
|
563
|
+
ulid: session.sessionId,
|
|
564
|
+
provider: session.config.providerId,
|
|
565
|
+
model: session.config.modelId,
|
|
566
|
+
source: "user",
|
|
567
|
+
mode: session.config.mode,
|
|
568
|
+
isNativeToolCall: false,
|
|
569
|
+
});
|
|
570
|
+
|
|
651
571
|
try {
|
|
652
|
-
const
|
|
653
|
-
?
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
baselineMessages,
|
|
662
|
-
);
|
|
572
|
+
const runFn = shouldContinue
|
|
573
|
+
? () => session.agent.continue(prompt, userImages, userFiles)
|
|
574
|
+
: () => session.agent.run(prompt, userImages, userFiles);
|
|
575
|
+
const result = await this.runWithAuthRetry(
|
|
576
|
+
session,
|
|
577
|
+
runFn,
|
|
578
|
+
baselineMessages,
|
|
579
|
+
);
|
|
580
|
+
|
|
663
581
|
session.started = true;
|
|
664
582
|
const persistedMessages = withLatestAssistantTurnMetadata(
|
|
665
583
|
result.messages,
|
|
@@ -677,7 +595,6 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
677
595
|
);
|
|
678
596
|
return result;
|
|
679
597
|
} catch (error) {
|
|
680
|
-
// Persist whatever was rendered so far even when a turn fails.
|
|
681
598
|
await this.invoke<void>(
|
|
682
599
|
"persistSessionMessages",
|
|
683
600
|
session.sessionId,
|
|
@@ -698,7 +615,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
698
615
|
userFiles?: string[];
|
|
699
616
|
},
|
|
700
617
|
): Promise<PreparedTurnInput> {
|
|
701
|
-
const mentionBaseDir = session.config
|
|
618
|
+
const mentionBaseDir = resolveWorkspacePath(session.config);
|
|
702
619
|
const normalizedPrompt = normalizeUserInput(input.prompt).trim();
|
|
703
620
|
if (!normalizedPrompt) {
|
|
704
621
|
return {
|
|
@@ -715,10 +632,9 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
715
632
|
normalizedPrompt,
|
|
716
633
|
mentionBaseDir,
|
|
717
634
|
);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
);
|
|
635
|
+
emitMentionTelemetry(session.config.telemetry, enriched);
|
|
636
|
+
|
|
637
|
+
const prompt = formatModePrompt(enriched.prompt, session.config.mode);
|
|
722
638
|
const explicitUserFiles = this.resolveAbsoluteFilePaths(
|
|
723
639
|
session.config.cwd,
|
|
724
640
|
input.userFiles,
|
|
@@ -738,23 +654,11 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
738
654
|
};
|
|
739
655
|
}
|
|
740
656
|
|
|
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
|
-
}
|
|
657
|
+
// ── Session lifecycle ───────────────────────────────────────────────
|
|
753
658
|
|
|
754
659
|
private async ensureSessionPersisted(session: ActiveSession): Promise<void> {
|
|
755
|
-
if (session.artifacts)
|
|
756
|
-
|
|
757
|
-
}
|
|
660
|
+
if (session.artifacts) return;
|
|
661
|
+
const workspacePath = resolveWorkspacePath(session.config);
|
|
758
662
|
session.artifacts = (await this.invoke("createRootSessionWithArtifacts", {
|
|
759
663
|
sessionId: session.sessionId,
|
|
760
664
|
source: session.source,
|
|
@@ -763,7 +667,7 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
763
667
|
provider: session.config.providerId,
|
|
764
668
|
model: session.config.modelId,
|
|
765
669
|
cwd: session.config.cwd,
|
|
766
|
-
workspaceRoot:
|
|
670
|
+
workspaceRoot: workspacePath,
|
|
767
671
|
teamName: session.config.teamName,
|
|
768
672
|
enableTools: session.config.enableTools,
|
|
769
673
|
enableSpawn: session.config.enableSpawnAgent,
|
|
@@ -777,24 +681,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
777
681
|
session: ActiveSession,
|
|
778
682
|
finishReason: AgentResult["finishReason"],
|
|
779
683
|
): 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
|
-
}
|
|
684
|
+
if (hasPendingTeamRunWork(session)) return;
|
|
685
|
+
const isAborted = finishReason === "aborted" || session.aborting;
|
|
686
|
+
await this.shutdownSession(session, {
|
|
687
|
+
status: isAborted ? "cancelled" : "completed",
|
|
688
|
+
exitCode: 0,
|
|
689
|
+
shutdownReason: "session_complete",
|
|
690
|
+
endReason: finishReason,
|
|
691
|
+
});
|
|
798
692
|
}
|
|
799
693
|
|
|
800
694
|
private async failSession(session: ActiveSession): Promise<void> {
|
|
@@ -815,17 +709,23 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
815
709
|
endReason: string;
|
|
816
710
|
},
|
|
817
711
|
): Promise<void> {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
712
|
+
if (input.status === "completed") {
|
|
713
|
+
captureTaskCompleted(session.config.telemetry, {
|
|
714
|
+
ulid: session.sessionId,
|
|
715
|
+
provider: session.config.providerId,
|
|
716
|
+
modelId: session.config.modelId,
|
|
717
|
+
mode: session.config.mode,
|
|
718
|
+
durationMs: Date.now() - Date.parse(session.startedAt),
|
|
719
|
+
});
|
|
821
720
|
}
|
|
721
|
+
notifyTeamRunWaiters(session);
|
|
722
|
+
|
|
822
723
|
if (session.artifacts) {
|
|
724
|
+
await this.updateStatus(session, input.status, input.exitCode);
|
|
823
725
|
await session.agent.shutdown(input.shutdownReason);
|
|
824
726
|
}
|
|
825
727
|
await Promise.resolve(session.runtime.shutdown(input.shutdownReason));
|
|
826
|
-
|
|
827
|
-
await session.pluginSandboxShutdown();
|
|
828
|
-
}
|
|
728
|
+
await session.pluginSandboxShutdown?.();
|
|
829
729
|
this.sessions.delete(session.sessionId);
|
|
830
730
|
this.emit({
|
|
831
731
|
type: "ended",
|
|
@@ -842,18 +742,14 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
842
742
|
status: SessionStatus,
|
|
843
743
|
exitCode?: number | null,
|
|
844
744
|
): Promise<void> {
|
|
845
|
-
if (!session.artifacts)
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
745
|
+
if (!session.artifacts) return;
|
|
848
746
|
const result = await this.invoke<{ updated: boolean; endedAt?: string }>(
|
|
849
747
|
"updateSessionStatus",
|
|
850
748
|
session.sessionId,
|
|
851
749
|
status,
|
|
852
750
|
exitCode,
|
|
853
751
|
);
|
|
854
|
-
if (!result.updated)
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
752
|
+
if (!result.updated) return;
|
|
857
753
|
session.artifacts.manifest.status = status;
|
|
858
754
|
session.artifacts.manifest.ended_at = result.endedAt ?? nowIso();
|
|
859
755
|
session.artifacts.manifest.exit_code =
|
|
@@ -866,51 +762,47 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
866
762
|
this.emitStatus(session.sessionId, status);
|
|
867
763
|
}
|
|
868
764
|
|
|
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
|
-
}
|
|
765
|
+
// ── Agent event handling ────────────────────────────────────────────
|
|
883
766
|
|
|
884
|
-
private
|
|
767
|
+
private onAgentEvent(
|
|
885
768
|
sessionId: string,
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
769
|
+
config: CoreSessionConfig,
|
|
770
|
+
event: AgentEvent,
|
|
771
|
+
): void {
|
|
772
|
+
const ctx: AgentEventContext = {
|
|
773
|
+
sessionId,
|
|
774
|
+
config,
|
|
775
|
+
liveSession: this.sessions.get(sessionId),
|
|
776
|
+
usageBySession: this.usageBySession,
|
|
777
|
+
persistMessages: (sid, messages, systemPrompt) => {
|
|
778
|
+
void this.invoke<void>(
|
|
779
|
+
"persistSessionMessages",
|
|
780
|
+
sid,
|
|
781
|
+
messages,
|
|
782
|
+
systemPrompt,
|
|
783
|
+
);
|
|
784
|
+
},
|
|
785
|
+
emit: (e) => this.emit(e),
|
|
786
|
+
};
|
|
787
|
+
handleAgentEvent(ctx, event);
|
|
893
788
|
}
|
|
894
789
|
|
|
790
|
+
// ── Spawn / sub-agents ──────────────────────────────────────────────
|
|
791
|
+
|
|
895
792
|
private createSpawnTool(
|
|
896
793
|
config: CoreSessionConfig,
|
|
897
794
|
rootSessionId: string,
|
|
898
795
|
): 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
796
|
const createSubAgentTools = () => {
|
|
913
|
-
const tools =
|
|
797
|
+
const tools: Tool[] = config.enableTools
|
|
798
|
+
? createBuiltinTools({
|
|
799
|
+
cwd: config.cwd,
|
|
800
|
+
...(config.mode === "plan"
|
|
801
|
+
? ToolPresets.readonly
|
|
802
|
+
: ToolPresets.development),
|
|
803
|
+
executors: this.defaultToolExecutors,
|
|
804
|
+
})
|
|
805
|
+
: [];
|
|
914
806
|
if (config.enableSpawnAgent) {
|
|
915
807
|
tools.push(this.createSpawnTool(config, rootSessionId));
|
|
916
808
|
}
|
|
@@ -936,242 +828,53 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
936
828
|
requestToolApproval: this.defaultRequestToolApproval,
|
|
937
829
|
logger: config.logger,
|
|
938
830
|
onSubAgentStart: (context) => {
|
|
831
|
+
this.subAgentStarts.set(context.subAgentId, {
|
|
832
|
+
startedAt: Date.now(),
|
|
833
|
+
rootSessionId,
|
|
834
|
+
});
|
|
939
835
|
void this.invokeOptional("handleSubAgentStart", rootSessionId, context);
|
|
940
836
|
},
|
|
941
837
|
onSubAgentEnd: (context) => {
|
|
838
|
+
const started = this.subAgentStarts.get(context.subAgentId);
|
|
839
|
+
const durationMs = started ? Date.now() - started.startedAt : 0;
|
|
840
|
+
const outputLines = context.result?.text
|
|
841
|
+
? context.result.text.split("\n").length
|
|
842
|
+
: 0;
|
|
843
|
+
captureSubagentExecution(config.telemetry, {
|
|
844
|
+
ulid: rootSessionId,
|
|
845
|
+
durationMs,
|
|
846
|
+
outputLines,
|
|
847
|
+
success: !context.error,
|
|
848
|
+
});
|
|
849
|
+
this.subAgentStarts.delete(context.subAgentId);
|
|
942
850
|
void this.invokeOptional("handleSubAgentEnd", rootSessionId, context);
|
|
943
851
|
},
|
|
944
852
|
}) as Tool;
|
|
945
853
|
}
|
|
946
854
|
|
|
855
|
+
// ── Team run coordination ───────────────────────────────────────────
|
|
856
|
+
|
|
947
857
|
private async handleTeamEvent(
|
|
948
858
|
rootSessionId: string,
|
|
949
859
|
event: TeamEvent,
|
|
950
860
|
): Promise<void> {
|
|
951
861
|
const session = this.sessions.get(rootSessionId);
|
|
952
862
|
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
|
-
});
|
|
863
|
+
trackTeamRunState(session, event);
|
|
1103
864
|
}
|
|
1104
|
-
}
|
|
1105
865
|
|
|
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",
|
|
866
|
+
await dispatchTeamEventToBackend(
|
|
867
|
+
rootSessionId,
|
|
868
|
+
event,
|
|
869
|
+
this.invokeOptional.bind(this),
|
|
1128
870
|
);
|
|
1129
|
-
}
|
|
1130
871
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
listener(event);
|
|
872
|
+
if (session) {
|
|
873
|
+
emitTeamProgress(session, rootSessionId, event, (e) => this.emit(e));
|
|
1134
874
|
}
|
|
1135
875
|
}
|
|
1136
876
|
|
|
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
|
-
}
|
|
877
|
+
// ── OAuth & auth ────────────────────────────────────────────────────
|
|
1175
878
|
|
|
1176
879
|
private async runWithAuthRetry(
|
|
1177
880
|
session: ActiveSession,
|
|
@@ -1181,37 +884,15 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
1181
884
|
try {
|
|
1182
885
|
return await run();
|
|
1183
886
|
} catch (error) {
|
|
1184
|
-
if (!
|
|
887
|
+
if (!isLikelyAuthError(error, session.config.providerId)) {
|
|
1185
888
|
throw error;
|
|
1186
889
|
}
|
|
1187
|
-
|
|
1188
890
|
await this.syncOAuthCredentials(session, { forceRefresh: true });
|
|
1189
891
|
session.agent.restore(baselineMessages);
|
|
1190
892
|
return run();
|
|
1191
893
|
}
|
|
1192
894
|
}
|
|
1193
895
|
|
|
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
896
|
private async syncOAuthCredentials(
|
|
1216
897
|
session: ActiveSession,
|
|
1217
898
|
options?: { forceRefresh?: boolean },
|
|
@@ -1230,32 +911,117 @@ export class DefaultSessionManager implements SessionManager {
|
|
|
1230
911
|
}
|
|
1231
912
|
throw error;
|
|
1232
913
|
}
|
|
1233
|
-
if (!resolved?.apiKey)
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (session.config.apiKey === resolved.apiKey) {
|
|
1237
|
-
return;
|
|
1238
|
-
}
|
|
914
|
+
if (!resolved?.apiKey || session.config.apiKey === resolved.apiKey) return;
|
|
1239
915
|
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
|
|
916
|
+
this.updateAgentConnection(session, { apiKey: resolved.apiKey });
|
|
1245
917
|
session.runtime.teamRuntime?.updateTeammateConnections({
|
|
1246
918
|
apiKey: resolved.apiKey,
|
|
1247
919
|
});
|
|
1248
920
|
}
|
|
1249
921
|
|
|
1250
|
-
|
|
922
|
+
// ── Utility methods ─────────────────────────────────────────────────
|
|
923
|
+
|
|
924
|
+
private getSessionOrThrow(sessionId: string): ActiveSession {
|
|
1251
925
|
const session = this.sessions.get(sessionId);
|
|
1252
|
-
if (!session) {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
926
|
+
if (!session) throw new Error(`session not found: ${sessionId}`);
|
|
927
|
+
return session;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private resolveAbsoluteFilePaths(cwd: string, paths?: string[]): string[] {
|
|
931
|
+
if (!paths || paths.length === 0) return [];
|
|
932
|
+
const resolved = paths
|
|
933
|
+
.map((p) => p.trim())
|
|
934
|
+
.filter((p) => p.length > 0)
|
|
935
|
+
.map((p) => (isAbsolute(p) ? p : resolve(cwd, p)));
|
|
936
|
+
return Array.from(new Set(resolved));
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
private updateAgentConnection(
|
|
940
|
+
session: ActiveSession,
|
|
941
|
+
overrides: { apiKey?: string; modelId?: string },
|
|
942
|
+
): void {
|
|
1256
943
|
const agentWithConnection = session.agent as Agent & {
|
|
1257
|
-
updateConnection?: (overrides: {
|
|
944
|
+
updateConnection?: (overrides: {
|
|
945
|
+
apiKey?: string;
|
|
946
|
+
modelId?: string;
|
|
947
|
+
}) => void;
|
|
1258
948
|
};
|
|
1259
|
-
agentWithConnection.updateConnection?.(
|
|
949
|
+
agentWithConnection.updateConnection?.(overrides);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
private emitStatus(sessionId: string, status: string): void {
|
|
953
|
+
this.emit({
|
|
954
|
+
type: "status",
|
|
955
|
+
payload: { sessionId, status },
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
private emit(event: CoreSessionEvent): void {
|
|
960
|
+
for (const listener of this.listeners) listener(event);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
private async listRows(limit: number): Promise<SessionRowShape[]> {
|
|
964
|
+
return this.invoke<SessionRowShape[]>(
|
|
965
|
+
"listSessions",
|
|
966
|
+
Math.min(Math.max(1, Math.floor(limit)), MAX_SCAN_LIMIT),
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
private async getRow(
|
|
971
|
+
sessionId: string,
|
|
972
|
+
): Promise<SessionRowShape | undefined> {
|
|
973
|
+
const target = sessionId.trim();
|
|
974
|
+
if (!target) return undefined;
|
|
975
|
+
const rows = await this.listRows(MAX_SCAN_LIMIT);
|
|
976
|
+
return rows.find((row) => row.session_id === target);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
// ── Session service invocation ──────────────────────────────────────
|
|
980
|
+
|
|
981
|
+
private async invoke<T>(method: string, ...args: unknown[]): Promise<T> {
|
|
982
|
+
const callable = (
|
|
983
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
984
|
+
)[method];
|
|
985
|
+
if (typeof callable !== "function") {
|
|
986
|
+
throw new Error(`session service method not available: ${method}`);
|
|
987
|
+
}
|
|
988
|
+
return Promise.resolve(
|
|
989
|
+
(callable as (...params: unknown[]) => T | Promise<T>).apply(
|
|
990
|
+
this.sessionService,
|
|
991
|
+
args,
|
|
992
|
+
),
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private async invokeOptional(
|
|
997
|
+
method: string,
|
|
998
|
+
...args: unknown[]
|
|
999
|
+
): Promise<void> {
|
|
1000
|
+
const callable = (
|
|
1001
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
1002
|
+
)[method];
|
|
1003
|
+
if (typeof callable !== "function") return;
|
|
1004
|
+
await Promise.resolve(
|
|
1005
|
+
(callable as (...params: unknown[]) => unknown).apply(
|
|
1006
|
+
this.sessionService,
|
|
1007
|
+
args,
|
|
1008
|
+
),
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
private async invokeOptionalValue<T = unknown>(
|
|
1013
|
+
method: string,
|
|
1014
|
+
...args: unknown[]
|
|
1015
|
+
): Promise<T | undefined> {
|
|
1016
|
+
const callable = (
|
|
1017
|
+
this.sessionService as unknown as Record<string, unknown>
|
|
1018
|
+
)[method];
|
|
1019
|
+
if (typeof callable !== "function") return undefined;
|
|
1020
|
+
return await Promise.resolve(
|
|
1021
|
+
(callable as (...params: unknown[]) => T | Promise<T>).apply(
|
|
1022
|
+
this.sessionService,
|
|
1023
|
+
args,
|
|
1024
|
+
),
|
|
1025
|
+
);
|
|
1260
1026
|
}
|
|
1261
1027
|
}
|