@phronesis-io/openclaw-eigenflux 0.0.1 → 0.0.2
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/README.md +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/notifier.d.ts +1 -17
- package/dist/notifier.d.ts.map +1 -1
- package/dist/notifier.js +1 -94
- package/dist/notifier.js.map +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -2
- package/src/agent-prompt-templates.ts +0 -91
- package/src/config.test.ts +0 -188
- package/src/config.ts +0 -410
- package/src/credentials-loader.test.ts +0 -78
- package/src/credentials-loader.ts +0 -121
- package/src/gateway-rpc-client.test.ts +0 -190
- package/src/gateway-rpc-client.ts +0 -373
- package/src/index.integration.test.ts +0 -437
- package/src/index.test.ts +0 -454
- package/src/index.ts +0 -758
- package/src/logger.ts +0 -27
- package/src/notification-route-resolver.test.ts +0 -136
- package/src/notification-route-resolver.ts +0 -430
- package/src/notifier.test.ts +0 -374
- package/src/notifier.ts +0 -558
- package/src/openclaw-plugin-sdk.d.ts +0 -121
- package/src/pm-polling-client.test.ts +0 -390
- package/src/pm-polling-client.ts +0 -257
- package/src/polling-client.test.ts +0 -279
- package/src/polling-client.ts +0 -283
- package/src/session-route-memory.ts +0 -106
package/src/logger.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger wrapper for consistent logging
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class Logger {
|
|
6
|
-
private baseLogger: any;
|
|
7
|
-
|
|
8
|
-
constructor(baseLogger: any) {
|
|
9
|
-
this.baseLogger = baseLogger;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
info(message: string, ...args: any[]): void {
|
|
13
|
-
this.baseLogger?.info(`[EigenFlux] ${message}`, ...args);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
warn(message: string, ...args: any[]): void {
|
|
17
|
-
this.baseLogger?.warn(`[EigenFlux] ${message}`, ...args);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
error(message: string, ...args: any[]): void {
|
|
21
|
-
this.baseLogger?.error(`[EigenFlux] ${message}`, ...args);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
debug(message: string, ...args: any[]): void {
|
|
25
|
-
this.baseLogger?.debug?.(`[EigenFlux] ${message}`, ...args);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { Logger } from './logger';
|
|
5
|
-
import { resolveNotificationRoute } from './notification-route-resolver';
|
|
6
|
-
import { writeStoredNotificationRoute } from './session-route-memory';
|
|
7
|
-
|
|
8
|
-
function createLogger(): Logger {
|
|
9
|
-
return new Logger({
|
|
10
|
-
info: jest.fn(),
|
|
11
|
-
warn: jest.fn(),
|
|
12
|
-
error: jest.fn(),
|
|
13
|
-
debug: jest.fn(),
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('resolveNotificationRoute', () => {
|
|
18
|
-
test('prefers remembered session route over dynamically fresher session when config is automatic', () => {
|
|
19
|
-
const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-route-memory-'));
|
|
20
|
-
const sessionStorePath = path.join(workdir, 'sessions.json');
|
|
21
|
-
|
|
22
|
-
fs.writeFileSync(
|
|
23
|
-
sessionStorePath,
|
|
24
|
-
JSON.stringify({
|
|
25
|
-
'agent:main:main': {
|
|
26
|
-
updatedAt: 100,
|
|
27
|
-
deliveryContext: { channel: 'webchat' },
|
|
28
|
-
},
|
|
29
|
-
'agent:main:feishu:group:oc_newer': {
|
|
30
|
-
updatedAt: 400,
|
|
31
|
-
deliveryContext: {
|
|
32
|
-
channel: 'feishu',
|
|
33
|
-
to: 'chat:oc_newer',
|
|
34
|
-
accountId: 'default',
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
'agent:mengtian:feishu:direct:ou_saved': {
|
|
38
|
-
updatedAt: 200,
|
|
39
|
-
deliveryContext: {
|
|
40
|
-
channel: 'feishu',
|
|
41
|
-
to: 'user:ou_saved',
|
|
42
|
-
accountId: 'default',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
}),
|
|
46
|
-
'utf-8'
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
writeStoredNotificationRoute(
|
|
50
|
-
workdir,
|
|
51
|
-
{
|
|
52
|
-
sessionKey: 'agent:mengtian:feishu:direct:ou_saved',
|
|
53
|
-
agentId: 'mengtian',
|
|
54
|
-
replyChannel: 'feishu',
|
|
55
|
-
replyTo: 'user:ou_saved',
|
|
56
|
-
replyAccountId: 'default',
|
|
57
|
-
},
|
|
58
|
-
createLogger()
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const route = resolveNotificationRoute(
|
|
62
|
-
{
|
|
63
|
-
sessionKey: 'main',
|
|
64
|
-
agentId: 'main',
|
|
65
|
-
sessionStorePath,
|
|
66
|
-
workdir,
|
|
67
|
-
routeOverrides: {
|
|
68
|
-
sessionKey: false,
|
|
69
|
-
agentId: false,
|
|
70
|
-
replyChannel: false,
|
|
71
|
-
replyTo: false,
|
|
72
|
-
replyAccountId: false,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
createLogger()
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
expect(route).toEqual({
|
|
79
|
-
sessionKey: 'agent:mengtian:feishu:direct:ou_saved',
|
|
80
|
-
agentId: 'mengtian',
|
|
81
|
-
replyChannel: 'feishu',
|
|
82
|
-
replyTo: 'user:ou_saved',
|
|
83
|
-
replyAccountId: 'default',
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
fs.rmSync(workdir, { recursive: true, force: true });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('respects explicit route overrides while still enriching exact session metadata', () => {
|
|
90
|
-
const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-route-explicit-'));
|
|
91
|
-
const sessionStorePath = path.join(workdir, 'sessions.json');
|
|
92
|
-
|
|
93
|
-
fs.writeFileSync(
|
|
94
|
-
sessionStorePath,
|
|
95
|
-
JSON.stringify({
|
|
96
|
-
'agent:main:feishu:direct:ou_explicit': {
|
|
97
|
-
updatedAt: 300,
|
|
98
|
-
deliveryContext: {
|
|
99
|
-
channel: 'feishu',
|
|
100
|
-
to: 'user:ou_explicit',
|
|
101
|
-
accountId: 'default',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
}),
|
|
105
|
-
'utf-8'
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const route = resolveNotificationRoute(
|
|
109
|
-
{
|
|
110
|
-
sessionKey: 'agent:main:feishu:direct:ou_explicit',
|
|
111
|
-
agentId: 'main',
|
|
112
|
-
replyChannel: 'feishu',
|
|
113
|
-
sessionStorePath,
|
|
114
|
-
workdir,
|
|
115
|
-
routeOverrides: {
|
|
116
|
-
sessionKey: true,
|
|
117
|
-
agentId: true,
|
|
118
|
-
replyChannel: true,
|
|
119
|
-
replyTo: false,
|
|
120
|
-
replyAccountId: false,
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
createLogger()
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
expect(route).toEqual({
|
|
127
|
-
sessionKey: 'agent:main:feishu:direct:ou_explicit',
|
|
128
|
-
agentId: 'main',
|
|
129
|
-
replyChannel: 'feishu',
|
|
130
|
-
replyTo: 'user:ou_explicit',
|
|
131
|
-
replyAccountId: 'default',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
fs.rmSync(workdir, { recursive: true, force: true });
|
|
135
|
-
});
|
|
136
|
-
});
|
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { type NotificationRouteOverrides } from './config';
|
|
5
|
-
import { Logger } from './logger';
|
|
6
|
-
import { readStoredNotificationRoute } from './session-route-memory';
|
|
7
|
-
|
|
8
|
-
const INTERNAL_CHANNELS = new Set(['webchat']);
|
|
9
|
-
|
|
10
|
-
function getDefaultOpenClawStateDir(): string {
|
|
11
|
-
return path.join(os.homedir(), '.openclaw');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type DeliveryContextLike = {
|
|
15
|
-
channel?: unknown;
|
|
16
|
-
to?: unknown;
|
|
17
|
-
accountId?: unknown;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type SessionOriginLike = {
|
|
21
|
-
provider?: unknown;
|
|
22
|
-
to?: unknown;
|
|
23
|
-
accountId?: unknown;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type SessionStoreEntry = {
|
|
27
|
-
updatedAt?: unknown;
|
|
28
|
-
deliveryContext?: DeliveryContextLike;
|
|
29
|
-
lastTo?: unknown;
|
|
30
|
-
lastAccountId?: unknown;
|
|
31
|
-
origin?: SessionOriginLike;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type SessionStoreSnapshot = {
|
|
35
|
-
path: string;
|
|
36
|
-
store: Record<string, SessionStoreEntry>;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type NotificationRouteConfig = {
|
|
40
|
-
sessionKey: string;
|
|
41
|
-
agentId: string;
|
|
42
|
-
replyChannel?: string;
|
|
43
|
-
replyTo?: string;
|
|
44
|
-
replyAccountId?: string;
|
|
45
|
-
sessionStorePath?: string;
|
|
46
|
-
workdir?: string;
|
|
47
|
-
routeOverrides?: NotificationRouteOverrides;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type ResolvedNotificationRoute = {
|
|
51
|
-
sessionKey: string;
|
|
52
|
-
agentId: string;
|
|
53
|
-
replyChannel?: string;
|
|
54
|
-
replyTo?: string;
|
|
55
|
-
replyAccountId?: string;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type PreferredRoute = {
|
|
59
|
-
channel?: string;
|
|
60
|
-
to?: string;
|
|
61
|
-
accountId?: string;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
type RouteSelection = {
|
|
65
|
-
route: ResolvedNotificationRoute;
|
|
66
|
-
updatedAt: number;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
function readNonEmptyString(value: unknown): string | undefined {
|
|
70
|
-
if (typeof value !== 'string') {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
const trimmed = value.trim();
|
|
74
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function normalizeChannel(value: unknown): string | undefined {
|
|
78
|
-
return readNonEmptyString(value)?.toLowerCase();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function normalizeUpdatedAt(value: unknown): number {
|
|
82
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
83
|
-
return value;
|
|
84
|
-
}
|
|
85
|
-
return 0;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function createRouteOverrides(
|
|
89
|
-
overrides: NotificationRouteConfig['routeOverrides']
|
|
90
|
-
): NotificationRouteOverrides {
|
|
91
|
-
return {
|
|
92
|
-
sessionKey: overrides?.sessionKey === true,
|
|
93
|
-
agentId: overrides?.agentId === true,
|
|
94
|
-
replyChannel: overrides?.replyChannel === true,
|
|
95
|
-
replyTo: overrides?.replyTo === true,
|
|
96
|
-
replyAccountId: overrides?.replyAccountId === true,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function isAnyRouteOverrideEnabled(overrides: NotificationRouteOverrides): boolean {
|
|
101
|
-
return Object.values(overrides).some(Boolean);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function isInternalSessionKey(sessionKey: string): boolean {
|
|
105
|
-
const trimmed = readNonEmptyString(sessionKey);
|
|
106
|
-
if (!trimmed) {
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
if (trimmed === 'main') {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const parts = trimmed.split(':').filter((part) => part.length > 0);
|
|
114
|
-
return parts[0]?.toLowerCase() === 'agent' && parts[2]?.toLowerCase() === 'main';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isExternalChannel(channel: string | undefined): boolean {
|
|
118
|
-
return Boolean(channel && !INTERNAL_CHANNELS.has(channel));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function routeTargetMatches(actual: string | undefined, expected: string | undefined): boolean {
|
|
122
|
-
if (!expected) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
if (!actual) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
return actual === expected || actual.endsWith(`:${expected}`) || expected.endsWith(`:${actual}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function routeMatchesPreferred(
|
|
132
|
-
route: ResolvedNotificationRoute,
|
|
133
|
-
preferred: PreferredRoute | undefined
|
|
134
|
-
): boolean {
|
|
135
|
-
if (!preferred) {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
if (preferred.channel && route.replyChannel !== preferred.channel) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
if (!routeTargetMatches(route.replyTo, preferred.to)) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
if (preferred.accountId && route.replyAccountId !== preferred.accountId) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function deriveAgentIdFromSessionKey(sessionKey: string | undefined): string | undefined {
|
|
151
|
-
const trimmed = readNonEmptyString(sessionKey);
|
|
152
|
-
if (!trimmed) {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
const parts = trimmed.split(':').filter((part) => part.length > 0);
|
|
156
|
-
if (parts[0]?.toLowerCase() !== 'agent') {
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
return readNonEmptyString(parts[1]);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function extractExternalRoute(
|
|
163
|
-
sessionKey: string,
|
|
164
|
-
entry: SessionStoreEntry | undefined
|
|
165
|
-
): ResolvedNotificationRoute | undefined {
|
|
166
|
-
if (!entry) {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const replyChannel =
|
|
171
|
-
normalizeChannel(entry.deliveryContext?.channel) ?? normalizeChannel(entry.origin?.provider);
|
|
172
|
-
const replyTo =
|
|
173
|
-
readNonEmptyString(entry.deliveryContext?.to) ??
|
|
174
|
-
readNonEmptyString(entry.lastTo) ??
|
|
175
|
-
readNonEmptyString(entry.origin?.to);
|
|
176
|
-
const replyAccountId =
|
|
177
|
-
readNonEmptyString(entry.deliveryContext?.accountId) ??
|
|
178
|
-
readNonEmptyString(entry.lastAccountId) ??
|
|
179
|
-
readNonEmptyString(entry.origin?.accountId);
|
|
180
|
-
|
|
181
|
-
if (!isExternalChannel(replyChannel) || !replyTo) {
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
sessionKey,
|
|
187
|
-
agentId: deriveAgentIdFromSessionKey(sessionKey) ?? 'main',
|
|
188
|
-
replyChannel,
|
|
189
|
-
replyTo,
|
|
190
|
-
replyAccountId,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function tryDeriveAgentIdFromStorePath(sessionStorePath: string): string | undefined {
|
|
195
|
-
const normalized = path.normalize(sessionStorePath);
|
|
196
|
-
const parts = normalized.split(path.sep).filter(Boolean);
|
|
197
|
-
const agentsIndex = parts.lastIndexOf('agents');
|
|
198
|
-
if (agentsIndex === -1) {
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
return readNonEmptyString(parts[agentsIndex + 1]);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function listSessionStorePaths(explicitPath: string | undefined, baseAgentId: string): string[] {
|
|
205
|
-
const candidates: string[] = [];
|
|
206
|
-
const seen = new Set<string>();
|
|
207
|
-
|
|
208
|
-
const addPath = (candidate: string | undefined) => {
|
|
209
|
-
const trimmed = readNonEmptyString(candidate);
|
|
210
|
-
if (!trimmed) {
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const normalized = path.normalize(trimmed);
|
|
214
|
-
if (seen.has(normalized)) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
seen.add(normalized);
|
|
218
|
-
candidates.push(normalized);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
addPath(explicitPath);
|
|
222
|
-
if (candidates.length > 0) {
|
|
223
|
-
return candidates;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const defaultOpenClawStateDir = getDefaultOpenClawStateDir();
|
|
227
|
-
addPath(path.join(defaultOpenClawStateDir, 'agents', baseAgentId, 'sessions', 'sessions.json'));
|
|
228
|
-
|
|
229
|
-
const agentsRoot = path.join(defaultOpenClawStateDir, 'agents');
|
|
230
|
-
try {
|
|
231
|
-
if (fs.existsSync(agentsRoot)) {
|
|
232
|
-
for (const entry of fs.readdirSync(agentsRoot, { withFileTypes: true })) {
|
|
233
|
-
if (!entry.isDirectory()) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
addPath(path.join(agentsRoot, entry.name, 'sessions', 'sessions.json'));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
} catch {
|
|
240
|
-
// Ignore directory scan failures; we will still try explicit/default paths.
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return candidates;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function readSessionStore(
|
|
247
|
-
sessionStorePath: string,
|
|
248
|
-
logger: Logger
|
|
249
|
-
): Record<string, SessionStoreEntry> | undefined {
|
|
250
|
-
try {
|
|
251
|
-
if (!fs.existsSync(sessionStorePath)) {
|
|
252
|
-
return undefined;
|
|
253
|
-
}
|
|
254
|
-
const raw = fs.readFileSync(sessionStorePath, 'utf-8');
|
|
255
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
256
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
return parsed as Record<string, SessionStoreEntry>;
|
|
260
|
-
} catch (error) {
|
|
261
|
-
logger.debug(
|
|
262
|
-
`Failed to read session store ${sessionStorePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
263
|
-
);
|
|
264
|
-
return undefined;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function readSessionStores(
|
|
269
|
-
sessionStorePath: string | undefined,
|
|
270
|
-
baseAgentId: string,
|
|
271
|
-
logger: Logger
|
|
272
|
-
): SessionStoreSnapshot[] {
|
|
273
|
-
const snapshots: SessionStoreSnapshot[] = [];
|
|
274
|
-
for (const candidate of listSessionStorePaths(sessionStorePath, baseAgentId)) {
|
|
275
|
-
const store = readSessionStore(candidate, logger);
|
|
276
|
-
if (store) {
|
|
277
|
-
snapshots.push({ path: candidate, store });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return snapshots;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function buildPreferredRoute(route: ResolvedNotificationRoute): PreferredRoute | undefined {
|
|
284
|
-
if (!route.replyChannel && !route.replyTo && !route.replyAccountId) {
|
|
285
|
-
return undefined;
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
channel: route.replyChannel,
|
|
289
|
-
to: route.replyTo,
|
|
290
|
-
accountId: route.replyAccountId,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function mergeRoute(
|
|
295
|
-
base: ResolvedNotificationRoute,
|
|
296
|
-
resolved: ResolvedNotificationRoute,
|
|
297
|
-
overrides: NotificationRouteOverrides,
|
|
298
|
-
allowSessionOverride: boolean
|
|
299
|
-
): ResolvedNotificationRoute {
|
|
300
|
-
const nextSessionKey =
|
|
301
|
-
allowSessionOverride && !overrides.sessionKey ? resolved.sessionKey : base.sessionKey;
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
sessionKey: nextSessionKey,
|
|
305
|
-
agentId:
|
|
306
|
-
overrides.agentId === true
|
|
307
|
-
? base.agentId
|
|
308
|
-
: resolved.agentId ?? deriveAgentIdFromSessionKey(nextSessionKey) ?? base.agentId,
|
|
309
|
-
replyChannel:
|
|
310
|
-
overrides.replyChannel === true ? base.replyChannel : resolved.replyChannel ?? base.replyChannel,
|
|
311
|
-
replyTo: overrides.replyTo === true ? base.replyTo : resolved.replyTo ?? base.replyTo,
|
|
312
|
-
replyAccountId:
|
|
313
|
-
overrides.replyAccountId === true
|
|
314
|
-
? base.replyAccountId
|
|
315
|
-
: resolved.replyAccountId ?? base.replyAccountId,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function selectExactRoute(
|
|
320
|
-
snapshots: SessionStoreSnapshot[],
|
|
321
|
-
sessionKey: string
|
|
322
|
-
): RouteSelection | undefined {
|
|
323
|
-
let best: RouteSelection | undefined;
|
|
324
|
-
|
|
325
|
-
for (const snapshot of snapshots) {
|
|
326
|
-
const entry = snapshot.store[sessionKey];
|
|
327
|
-
const route = extractExternalRoute(sessionKey, entry);
|
|
328
|
-
if (!route) {
|
|
329
|
-
continue;
|
|
330
|
-
}
|
|
331
|
-
const updatedAt = normalizeUpdatedAt(entry?.updatedAt);
|
|
332
|
-
if (!best || updatedAt > best.updatedAt) {
|
|
333
|
-
best = { route, updatedAt };
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return best;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function selectLatestExternalRoute(
|
|
341
|
-
snapshots: SessionStoreSnapshot[],
|
|
342
|
-
preferred: PreferredRoute | undefined,
|
|
343
|
-
preferredAgentId?: string
|
|
344
|
-
): RouteSelection | undefined {
|
|
345
|
-
let best: RouteSelection | undefined;
|
|
346
|
-
|
|
347
|
-
for (const snapshot of snapshots) {
|
|
348
|
-
const pathAgentId = tryDeriveAgentIdFromStorePath(snapshot.path);
|
|
349
|
-
for (const [sessionKey, entry] of Object.entries(snapshot.store)) {
|
|
350
|
-
if (sessionKey.includes(':subagent:')) {
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const route = extractExternalRoute(sessionKey, entry);
|
|
355
|
-
if (!route || !routeMatchesPreferred(route, preferred)) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (preferredAgentId && route.agentId !== preferredAgentId && pathAgentId !== preferredAgentId) {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const updatedAt = normalizeUpdatedAt(entry.updatedAt);
|
|
364
|
-
if (!best || updatedAt > best.updatedAt) {
|
|
365
|
-
best = { route, updatedAt };
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return best;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function resolveNotificationRoute(
|
|
374
|
-
config: NotificationRouteConfig,
|
|
375
|
-
logger: Logger
|
|
376
|
-
): ResolvedNotificationRoute {
|
|
377
|
-
const overrides = createRouteOverrides(config.routeOverrides);
|
|
378
|
-
|
|
379
|
-
let resolved: ResolvedNotificationRoute = {
|
|
380
|
-
sessionKey: readNonEmptyString(config.sessionKey) ?? 'main',
|
|
381
|
-
agentId:
|
|
382
|
-
readNonEmptyString(config.agentId) ??
|
|
383
|
-
deriveAgentIdFromSessionKey(config.sessionKey) ??
|
|
384
|
-
'main',
|
|
385
|
-
replyChannel: normalizeChannel(config.replyChannel),
|
|
386
|
-
replyTo: readNonEmptyString(config.replyTo),
|
|
387
|
-
replyAccountId: readNonEmptyString(config.replyAccountId),
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
const snapshots = readSessionStores(config.sessionStorePath, resolved.agentId, logger);
|
|
391
|
-
|
|
392
|
-
const exactRoute = selectExactRoute(snapshots, resolved.sessionKey);
|
|
393
|
-
if (exactRoute) {
|
|
394
|
-
resolved = mergeRoute(resolved, exactRoute.route, overrides, false);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!isAnyRouteOverrideEnabled(overrides)) {
|
|
398
|
-
const rememberedRoute = readStoredNotificationRoute(config.workdir, logger);
|
|
399
|
-
if (rememberedRoute) {
|
|
400
|
-
resolved = mergeRoute(resolved, rememberedRoute, overrides, true);
|
|
401
|
-
const rememberedExact = selectExactRoute(snapshots, resolved.sessionKey);
|
|
402
|
-
if (rememberedExact) {
|
|
403
|
-
resolved = mergeRoute(resolved, rememberedExact.route, overrides, false);
|
|
404
|
-
}
|
|
405
|
-
logger.debug(
|
|
406
|
-
`Resolved notification route via remembered session: session_key=${resolved.sessionKey}, channel=${resolved.replyChannel ?? 'unknown'}, to=${resolved.replyTo ?? 'unknown'}`
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (!isInternalSessionKey(resolved.sessionKey)) {
|
|
412
|
-
return resolved;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const preferred = buildPreferredRoute(resolved);
|
|
416
|
-
const latestRoute = selectLatestExternalRoute(
|
|
417
|
-
snapshots,
|
|
418
|
-
preferred,
|
|
419
|
-
overrides.agentId || overrides.sessionKey ? resolved.agentId : undefined
|
|
420
|
-
);
|
|
421
|
-
if (!latestRoute) {
|
|
422
|
-
return resolved;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
resolved = mergeRoute(resolved, latestRoute.route, overrides, true);
|
|
426
|
-
logger.debug(
|
|
427
|
-
`Resolved notification route via session store: session_key=${resolved.sessionKey}, channel=${resolved.replyChannel ?? 'unknown'}, to=${resolved.replyTo ?? 'unknown'}`
|
|
428
|
-
);
|
|
429
|
-
return resolved;
|
|
430
|
-
}
|