@masons/agent-network 0.3.9 → 0.4.0
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/channel.d.ts.map +1 -1
- package/dist/channel.js +65 -73
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +32 -0
- package/dist/conversation-manager.d.ts +91 -0
- package/dist/conversation-manager.d.ts.map +1 -0
- package/dist/conversation-manager.js +189 -0
- package/dist/plugin.js +2 -2
- package/dist/session-lifecycle.d.ts +89 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +348 -0
- package/dist/tools.d.ts +8 -5
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +63 -115
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/skills/agent-network/SKILL.md +12 -22
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Lifecycle — manages MSTP session state per contact.
|
|
3
|
+
*
|
|
4
|
+
* State machine: idle -> creating -> active -> recreating -> failed.
|
|
5
|
+
* Internal key: MSTP address (not handle) — supports same handle on different Connectors.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Maps MSTP address to session state (sessionId, status, pending promise)
|
|
9
|
+
* - Auto-creates sessions on demand via ensureSession()
|
|
10
|
+
* - Deduplicates concurrent creates (second caller awaits first's promise)
|
|
11
|
+
* - Registers inbound sessions from remote agents
|
|
12
|
+
* - Invalidates all sessions on transport disconnect
|
|
13
|
+
* - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
|
|
14
|
+
*
|
|
15
|
+
* @see docs/openclaw/session-abstraction-system-design.md §6.1
|
|
16
|
+
*/
|
|
17
|
+
import type { ConnectorClient } from "./connector-client.js";
|
|
18
|
+
export type SessionStatus = "idle" | "creating" | "active" | "recreating" | "failed";
|
|
19
|
+
export interface SessionState {
|
|
20
|
+
status: SessionStatus;
|
|
21
|
+
sessionId: string | null;
|
|
22
|
+
pendingRequestId: string | null;
|
|
23
|
+
retryCount: number;
|
|
24
|
+
/** Promise that resolves when session creation completes. Used for dedup. */
|
|
25
|
+
createPromise: Promise<string> | null;
|
|
26
|
+
/** Resolve function for the create promise. */
|
|
27
|
+
createResolve: ((sessionId: string) => void) | null;
|
|
28
|
+
/** Reject function for the create promise. */
|
|
29
|
+
createReject: ((err: Error) => void) | null;
|
|
30
|
+
}
|
|
31
|
+
export declare class SessionLifecycle {
|
|
32
|
+
private readonly client;
|
|
33
|
+
/** Primary map: MSTP address -> session state */
|
|
34
|
+
private readonly sessions;
|
|
35
|
+
/** Reverse index: sessionId -> MSTP address */
|
|
36
|
+
private readonly sessionToAddress;
|
|
37
|
+
/** Configurable timeout for tests */
|
|
38
|
+
private timeoutMs;
|
|
39
|
+
constructor(client: ConnectorClient);
|
|
40
|
+
/**
|
|
41
|
+
* Ensure an active session exists for the given address.
|
|
42
|
+
* Returns the sessionId. Creates a new session if none exists.
|
|
43
|
+
* Concurrent callers get the same promise (dedup).
|
|
44
|
+
*/
|
|
45
|
+
ensureSession(address: string): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Get the active sessionId for an address, or null.
|
|
48
|
+
*/
|
|
49
|
+
getSession(address: string): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Close the session for an address.
|
|
52
|
+
*/
|
|
53
|
+
closeSession(address: string, reason?: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Register an inbound session (remote initiated).
|
|
56
|
+
*/
|
|
57
|
+
registerInbound(sessionId: string, address: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Invalidate all sessions. Called on transport disconnect.
|
|
60
|
+
* Conversations are preserved but sessions are cleared.
|
|
61
|
+
*/
|
|
62
|
+
invalidateAll(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Reverse lookup: sessionId -> MSTP address.
|
|
65
|
+
*/
|
|
66
|
+
getAddressBySessionId(sessionId: string): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Get all session entries (for listing conversations).
|
|
69
|
+
*/
|
|
70
|
+
listSessions(): Array<{
|
|
71
|
+
address: string;
|
|
72
|
+
status: SessionStatus;
|
|
73
|
+
sessionId: string | null;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Remove a session entry entirely (for endConversation).
|
|
77
|
+
*/
|
|
78
|
+
removeSession(address: string): void;
|
|
79
|
+
/** @internal Override timeout for tests. */
|
|
80
|
+
_setTimeoutForTesting(ms: number): void;
|
|
81
|
+
/** @internal Reset for test isolation. */
|
|
82
|
+
_resetForTesting(): void;
|
|
83
|
+
private subscribe;
|
|
84
|
+
private createNewSession;
|
|
85
|
+
private handleSessionCreated;
|
|
86
|
+
private handleSessionEnded;
|
|
87
|
+
private handleSessionError;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=session-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-lifecycle.d.ts","sourceRoot":"","sources":["../src/session-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACtC,+CAA+C;IAC/C,aAAa,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACpD,8CAA8C;IAC9C,YAAY,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AAaD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,+CAA+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAE9D,qCAAqC;IACrC,OAAO,CAAC,SAAS,CAA6B;gBAElC,MAAM,EAAE,eAAe;IASnC;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB/C;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQ1C;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBpD;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBzD;;;OAGG;IACH,aAAa,IAAI,IAAI;IAqBrB;;OAEG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI5D;;OAEG;IACH,YAAY,IAAI,KAAK,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,aAAa,CAAC;QACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;IAgBF;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAcpC,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIvC,0CAA0C;IAC1C,gBAAgB,IAAI,IAAI;IAmBxB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,gBAAgB;IA+GxB,OAAO,CAAC,oBAAoB;IAuC5B,OAAO,CAAC,kBAAkB;IAiB1B,OAAO,CAAC,kBAAkB;CA0B3B"}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Lifecycle — manages MSTP session state per contact.
|
|
3
|
+
*
|
|
4
|
+
* State machine: idle -> creating -> active -> recreating -> failed.
|
|
5
|
+
* Internal key: MSTP address (not handle) — supports same handle on different Connectors.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Maps MSTP address to session state (sessionId, status, pending promise)
|
|
9
|
+
* - Auto-creates sessions on demand via ensureSession()
|
|
10
|
+
* - Deduplicates concurrent creates (second caller awaits first's promise)
|
|
11
|
+
* - Registers inbound sessions from remote agents
|
|
12
|
+
* - Invalidates all sessions on transport disconnect
|
|
13
|
+
* - Subscribes to ConnectorClient events: session_created, session_error, session_ended, disconnected
|
|
14
|
+
*
|
|
15
|
+
* @see docs/openclaw/session-abstraction-system-design.md §6.1
|
|
16
|
+
*/
|
|
17
|
+
import createDebug from "debug";
|
|
18
|
+
const dbg = createDebug("agent-network:session-lifecycle");
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Constants
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
const SESSION_CREATE_TIMEOUT_MS = 15_000;
|
|
23
|
+
const MAX_RETRY_COUNT = 1;
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// SessionLifecycle
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
export class SessionLifecycle {
|
|
28
|
+
client;
|
|
29
|
+
/** Primary map: MSTP address -> session state */
|
|
30
|
+
sessions = new Map();
|
|
31
|
+
/** Reverse index: sessionId -> MSTP address */
|
|
32
|
+
sessionToAddress = new Map();
|
|
33
|
+
/** Configurable timeout for tests */
|
|
34
|
+
timeoutMs = SESSION_CREATE_TIMEOUT_MS;
|
|
35
|
+
constructor(client) {
|
|
36
|
+
this.client = client;
|
|
37
|
+
this.subscribe();
|
|
38
|
+
}
|
|
39
|
+
// -------------------------------------------------------------------------
|
|
40
|
+
// Public API
|
|
41
|
+
// -------------------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* Ensure an active session exists for the given address.
|
|
44
|
+
* Returns the sessionId. Creates a new session if none exists.
|
|
45
|
+
* Concurrent callers get the same promise (dedup).
|
|
46
|
+
*/
|
|
47
|
+
ensureSession(address) {
|
|
48
|
+
const state = this.sessions.get(address);
|
|
49
|
+
// Active session — return immediately
|
|
50
|
+
if (state?.status === "active" && state.sessionId) {
|
|
51
|
+
return Promise.resolve(state.sessionId);
|
|
52
|
+
}
|
|
53
|
+
// Creating/recreating — dedup: return existing promise
|
|
54
|
+
if ((state?.status === "creating" || state?.status === "recreating") &&
|
|
55
|
+
state.createPromise) {
|
|
56
|
+
return state.createPromise;
|
|
57
|
+
}
|
|
58
|
+
// Idle, failed, or no state — create new session
|
|
59
|
+
return this.createNewSession(address, state?.retryCount ?? 0);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the active sessionId for an address, or null.
|
|
63
|
+
*/
|
|
64
|
+
getSession(address) {
|
|
65
|
+
const state = this.sessions.get(address);
|
|
66
|
+
if (state?.status === "active" && state.sessionId) {
|
|
67
|
+
return state.sessionId;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Close the session for an address.
|
|
73
|
+
*/
|
|
74
|
+
closeSession(address, reason) {
|
|
75
|
+
const state = this.sessions.get(address);
|
|
76
|
+
if (!state)
|
|
77
|
+
return;
|
|
78
|
+
if (state.sessionId) {
|
|
79
|
+
this.client.endSession(state.sessionId, reason);
|
|
80
|
+
this.sessionToAddress.delete(state.sessionId);
|
|
81
|
+
}
|
|
82
|
+
// Reject any pending create promise
|
|
83
|
+
if (state.createReject) {
|
|
84
|
+
state.createReject(new Error("Session closed by caller"));
|
|
85
|
+
state.createResolve = null;
|
|
86
|
+
state.createReject = null;
|
|
87
|
+
state.createPromise = null;
|
|
88
|
+
}
|
|
89
|
+
this.sessions.delete(address);
|
|
90
|
+
dbg("closeSession address=%s", address);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Register an inbound session (remote initiated).
|
|
94
|
+
*/
|
|
95
|
+
registerInbound(sessionId, address) {
|
|
96
|
+
// If there's an existing session for this address, clean up
|
|
97
|
+
const existing = this.sessions.get(address);
|
|
98
|
+
if (existing?.sessionId && existing.sessionId !== sessionId) {
|
|
99
|
+
this.sessionToAddress.delete(existing.sessionId);
|
|
100
|
+
}
|
|
101
|
+
const state = {
|
|
102
|
+
status: "active",
|
|
103
|
+
sessionId,
|
|
104
|
+
pendingRequestId: null,
|
|
105
|
+
retryCount: 0,
|
|
106
|
+
createPromise: null,
|
|
107
|
+
createResolve: null,
|
|
108
|
+
createReject: null,
|
|
109
|
+
};
|
|
110
|
+
this.sessions.set(address, state);
|
|
111
|
+
this.sessionToAddress.set(sessionId, address);
|
|
112
|
+
dbg("registerInbound sessionId=%s address=%s", sessionId, address);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Invalidate all sessions. Called on transport disconnect.
|
|
116
|
+
* Conversations are preserved but sessions are cleared.
|
|
117
|
+
*/
|
|
118
|
+
invalidateAll() {
|
|
119
|
+
const count = this.sessions.size;
|
|
120
|
+
for (const [address, state] of this.sessions) {
|
|
121
|
+
// Reject pending creates
|
|
122
|
+
if (state.createReject) {
|
|
123
|
+
state.createReject(new Error("Transport disconnected"));
|
|
124
|
+
state.createResolve = null;
|
|
125
|
+
state.createReject = null;
|
|
126
|
+
state.createPromise = null;
|
|
127
|
+
}
|
|
128
|
+
// Reset to idle — next send() will auto-create
|
|
129
|
+
state.status = "idle";
|
|
130
|
+
state.sessionId = null;
|
|
131
|
+
state.pendingRequestId = null;
|
|
132
|
+
state.retryCount = 0;
|
|
133
|
+
this.sessions.set(address, state);
|
|
134
|
+
}
|
|
135
|
+
this.sessionToAddress.clear();
|
|
136
|
+
dbg("invalidateAll cleared %d sessions", count);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Reverse lookup: sessionId -> MSTP address.
|
|
140
|
+
*/
|
|
141
|
+
getAddressBySessionId(sessionId) {
|
|
142
|
+
return this.sessionToAddress.get(sessionId);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get all session entries (for listing conversations).
|
|
146
|
+
*/
|
|
147
|
+
listSessions() {
|
|
148
|
+
const entries = [];
|
|
149
|
+
for (const [address, state] of this.sessions) {
|
|
150
|
+
entries.push({
|
|
151
|
+
address,
|
|
152
|
+
status: state.status,
|
|
153
|
+
sessionId: state.sessionId,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return entries;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Remove a session entry entirely (for endConversation).
|
|
160
|
+
*/
|
|
161
|
+
removeSession(address) {
|
|
162
|
+
const state = this.sessions.get(address);
|
|
163
|
+
if (state?.sessionId) {
|
|
164
|
+
this.sessionToAddress.delete(state.sessionId);
|
|
165
|
+
}
|
|
166
|
+
if (state?.createReject) {
|
|
167
|
+
state.createReject(new Error("Session removed"));
|
|
168
|
+
state.createResolve = null;
|
|
169
|
+
state.createReject = null;
|
|
170
|
+
state.createPromise = null;
|
|
171
|
+
}
|
|
172
|
+
this.sessions.delete(address);
|
|
173
|
+
}
|
|
174
|
+
/** @internal Override timeout for tests. */
|
|
175
|
+
_setTimeoutForTesting(ms) {
|
|
176
|
+
this.timeoutMs = ms;
|
|
177
|
+
}
|
|
178
|
+
/** @internal Reset for test isolation. */
|
|
179
|
+
_resetForTesting() {
|
|
180
|
+
// Reject pending creates silently
|
|
181
|
+
for (const state of this.sessions.values()) {
|
|
182
|
+
if (state.createReject) {
|
|
183
|
+
state.createReject(new Error("Reset for testing"));
|
|
184
|
+
state.createResolve = null;
|
|
185
|
+
state.createReject = null;
|
|
186
|
+
state.createPromise = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
this.sessions.clear();
|
|
190
|
+
this.sessionToAddress.clear();
|
|
191
|
+
this.timeoutMs = SESSION_CREATE_TIMEOUT_MS;
|
|
192
|
+
}
|
|
193
|
+
// -------------------------------------------------------------------------
|
|
194
|
+
// Private
|
|
195
|
+
// -------------------------------------------------------------------------
|
|
196
|
+
subscribe() {
|
|
197
|
+
this.client.on("session_created", (event) => {
|
|
198
|
+
this.handleSessionCreated(event.sessionId, event.requestId, event.direction);
|
|
199
|
+
});
|
|
200
|
+
this.client.on("session_error", (sessionId, message) => {
|
|
201
|
+
this.handleSessionError(sessionId, message);
|
|
202
|
+
});
|
|
203
|
+
this.client.on("session_ended", (event) => {
|
|
204
|
+
this.handleSessionEnded(event.sessionId);
|
|
205
|
+
});
|
|
206
|
+
this.client.on("disconnected", () => {
|
|
207
|
+
this.invalidateAll();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
createNewSession(address, previousRetryCount) {
|
|
211
|
+
const status = previousRetryCount > 0 ? "recreating" : "creating";
|
|
212
|
+
let createResolve = null;
|
|
213
|
+
let createReject = null;
|
|
214
|
+
const createPromise = new Promise((resolve, reject) => {
|
|
215
|
+
createResolve = resolve;
|
|
216
|
+
createReject = reject;
|
|
217
|
+
});
|
|
218
|
+
const { requestId, sent } = this.client.createSession(address);
|
|
219
|
+
if (!sent) {
|
|
220
|
+
const state = {
|
|
221
|
+
status: "failed",
|
|
222
|
+
sessionId: null,
|
|
223
|
+
pendingRequestId: null,
|
|
224
|
+
retryCount: previousRetryCount,
|
|
225
|
+
createPromise: null,
|
|
226
|
+
createResolve: null,
|
|
227
|
+
createReject: null,
|
|
228
|
+
};
|
|
229
|
+
this.sessions.set(address, state);
|
|
230
|
+
return Promise.reject(new Error("Failed to create session: transport unavailable"));
|
|
231
|
+
}
|
|
232
|
+
const state = {
|
|
233
|
+
status,
|
|
234
|
+
sessionId: null,
|
|
235
|
+
pendingRequestId: requestId,
|
|
236
|
+
retryCount: previousRetryCount,
|
|
237
|
+
createPromise,
|
|
238
|
+
createResolve,
|
|
239
|
+
createReject,
|
|
240
|
+
};
|
|
241
|
+
this.sessions.set(address, state);
|
|
242
|
+
// Timeout
|
|
243
|
+
const timer = setTimeout(() => {
|
|
244
|
+
const current = this.sessions.get(address);
|
|
245
|
+
if (current &&
|
|
246
|
+
current.pendingRequestId === requestId &&
|
|
247
|
+
(current.status === "creating" || current.status === "recreating")) {
|
|
248
|
+
dbg("session create timeout address=%s requestId=%s", address, requestId);
|
|
249
|
+
// Can we retry?
|
|
250
|
+
if (current.retryCount < MAX_RETRY_COUNT) {
|
|
251
|
+
// Move to failed, then retry
|
|
252
|
+
current.status = "failed";
|
|
253
|
+
current.retryCount++;
|
|
254
|
+
current.pendingRequestId = null;
|
|
255
|
+
const retryResolve = current.createResolve;
|
|
256
|
+
const retryReject = current.createReject;
|
|
257
|
+
current.createPromise = null;
|
|
258
|
+
current.createResolve = null;
|
|
259
|
+
current.createReject = null;
|
|
260
|
+
this.sessions.set(address, current);
|
|
261
|
+
// Retry: create a new session, pipe result to original promise
|
|
262
|
+
this.createNewSession(address, current.retryCount)
|
|
263
|
+
.then((sessionId) => retryResolve?.(sessionId))
|
|
264
|
+
.catch((err) => retryReject?.(err));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// Max retries exhausted
|
|
268
|
+
current.status = "idle";
|
|
269
|
+
current.pendingRequestId = null;
|
|
270
|
+
current.retryCount = 0;
|
|
271
|
+
if (current.createReject) {
|
|
272
|
+
current.createReject(new Error("Session creation timed out. The remote agent may be unavailable."));
|
|
273
|
+
}
|
|
274
|
+
current.createPromise = null;
|
|
275
|
+
current.createResolve = null;
|
|
276
|
+
current.createReject = null;
|
|
277
|
+
this.sessions.set(address, current);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}, this.timeoutMs);
|
|
281
|
+
// Attach cleanup to the promise to clear the timer if resolved early
|
|
282
|
+
createPromise
|
|
283
|
+
.then(() => clearTimeout(timer))
|
|
284
|
+
.catch(() => clearTimeout(timer));
|
|
285
|
+
dbg("createNewSession address=%s requestId=%s status=%s retry=%d", address, requestId, status, previousRetryCount);
|
|
286
|
+
return createPromise;
|
|
287
|
+
}
|
|
288
|
+
handleSessionCreated(sessionId, requestId, direction) {
|
|
289
|
+
// Outbound: match by requestId
|
|
290
|
+
if (direction === "outbound" && requestId) {
|
|
291
|
+
for (const [address, state] of this.sessions) {
|
|
292
|
+
if (state.pendingRequestId === requestId &&
|
|
293
|
+
(state.status === "creating" || state.status === "recreating")) {
|
|
294
|
+
state.status = "active";
|
|
295
|
+
state.sessionId = sessionId;
|
|
296
|
+
state.pendingRequestId = null;
|
|
297
|
+
this.sessionToAddress.set(sessionId, address);
|
|
298
|
+
if (state.createResolve) {
|
|
299
|
+
state.createResolve(sessionId);
|
|
300
|
+
}
|
|
301
|
+
state.createPromise = null;
|
|
302
|
+
state.createResolve = null;
|
|
303
|
+
state.createReject = null;
|
|
304
|
+
dbg("session created (outbound) address=%s sessionId=%s", address, sessionId);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Inbound sessions are registered externally via registerInbound()
|
|
310
|
+
// from the channel adapter (which has the metadata to derive the address).
|
|
311
|
+
// We don't handle inbound SESSION_CREATED here.
|
|
312
|
+
}
|
|
313
|
+
handleSessionEnded(sessionId) {
|
|
314
|
+
const address = this.sessionToAddress.get(sessionId);
|
|
315
|
+
if (!address)
|
|
316
|
+
return;
|
|
317
|
+
const state = this.sessions.get(address);
|
|
318
|
+
if (!state)
|
|
319
|
+
return;
|
|
320
|
+
// Only clear if this session is the current one
|
|
321
|
+
if (state.sessionId === sessionId) {
|
|
322
|
+
state.status = "idle";
|
|
323
|
+
state.sessionId = null;
|
|
324
|
+
dbg("session ended address=%s sessionId=%s", address, sessionId);
|
|
325
|
+
}
|
|
326
|
+
this.sessionToAddress.delete(sessionId);
|
|
327
|
+
}
|
|
328
|
+
handleSessionError(sessionId, message) {
|
|
329
|
+
const address = this.sessionToAddress.get(sessionId);
|
|
330
|
+
if (!address)
|
|
331
|
+
return;
|
|
332
|
+
const state = this.sessions.get(address);
|
|
333
|
+
if (!state)
|
|
334
|
+
return;
|
|
335
|
+
dbg("session error address=%s sessionId=%s message=%s", address, sessionId, message);
|
|
336
|
+
// Session error on active session — mark for auto-recreate on next send().
|
|
337
|
+
// Reset retryCount to 0: this is a new failure context (transport error),
|
|
338
|
+
// not a continuation of a creation timeout. The next ensureSession() gets
|
|
339
|
+
// a fresh retry budget.
|
|
340
|
+
if (state.status === "active") {
|
|
341
|
+
this.sessionToAddress.delete(sessionId);
|
|
342
|
+
state.status = "idle";
|
|
343
|
+
state.sessionId = null;
|
|
344
|
+
state.retryCount = 0;
|
|
345
|
+
dbg("auto-recreate queued address=%s", address);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLM tools — setup, connection, profile, and conversation tools.
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
4
|
+
* Registers tools with OpenClaw's Plugin API so the LLM can
|
|
5
5
|
* drive setup, profile completion, connection listing, connection
|
|
6
6
|
* requests, request management, and real-time conversations,
|
|
7
7
|
* guided by SKILL.md.
|
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
* Two access patterns:
|
|
10
10
|
* - **HTTP tools** (setup, connection): read config via `requirePlatformConfig()`,
|
|
11
11
|
* call Platform API via `platform-client.ts`.
|
|
12
|
-
* - **WebSocket tools** (conversation):
|
|
13
|
-
*
|
|
12
|
+
* - **WebSocket tools** (conversation): use `requireConversationManager()` for
|
|
13
|
+
* identity-based messaging. Session management is fully transparent.
|
|
14
14
|
*
|
|
15
|
+
* Session Abstraction (#741):
|
|
16
|
+
* - `masons_send_message(to, content)` — sends via ConversationManager
|
|
17
|
+
* - `masons_end_conversation(contact)` — ends via ConversationManager
|
|
18
|
+
* - `masons_create_session` — DEPRECATED SHIM (one release cycle)
|
|
19
|
+
* - `masons_end_session` — DEPRECATED SHIM (one release cycle)
|
|
15
20
|
*/
|
|
16
21
|
interface ToolContent {
|
|
17
22
|
content: Array<{
|
|
@@ -32,8 +37,6 @@ interface ToolApi {
|
|
|
32
37
|
}
|
|
33
38
|
/** @internal Reset module state for test isolation. */
|
|
34
39
|
export declare function _resetToolsForTesting(): void;
|
|
35
|
-
/** @internal Override session creation timeout for tests. */
|
|
36
|
-
export declare function _setSessionTimeoutForTesting(ms: number): void;
|
|
37
40
|
/**
|
|
38
41
|
* Register agent network tools with the OpenClaw Plugin API.
|
|
39
42
|
*
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAmCH,UAAU,WAAW;IACnB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3B;AAED,UAAU,OAAO;IACf,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE;AAeD,uDAAuD;AACvD,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAwFD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAqsBhD"}
|