@masons/agent-network 0.4.16 → 0.4.18

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.
@@ -2,40 +2,57 @@
2
2
  * Conversation Manager — maps contacts to conversations.
3
3
  *
4
4
  * Owns the identity-based API that the Tool Interface and Inbound Dispatcher
5
- * call. Internally delegates session management to SessionLifecycle.
5
+ * call. Sends messages directly to routable addresses via ConnectorClient.send().
6
6
  *
7
- * Contact resolution: handle -> `mstps://${connectorHost}/${handle}`
8
- * Internal key: MSTP address (same as SessionLifecycle).
9
- * One session per contact, last-write-wins (D2).
10
- * In-memory only, no persistence (D3).
7
+ * Contact resolution: handle `mstps://${connectorHost}/${handle}`
8
+ * Address schemes: MSTP (`mstps://`), handle (bare), passport (`passport:@`)
9
+ * Internal key: routable address.
10
+ * One conversation per contact, in-memory only (D3).
11
11
  *
12
- * @see docs/openclaw/session-abstraction-system-design.md §4.1
12
+ * @see docs/connector/gateway-v2-consumer-adaptation-plan.md PR 2
13
13
  */
14
14
  import createDebug from "debug";
15
- import { SessionLifecycle } from "./session-lifecycle.js";
15
+ import { ConnectorError } from "./connector-client.js";
16
+ import { extractHandleFromAddress } from "./handle-utils.js";
16
17
  const dbg = createDebug("agent-network:conversation-manager");
17
18
  // ---------------------------------------------------------------------------
19
+ // Error code → tool result mapping
20
+ // ---------------------------------------------------------------------------
21
+ const ERROR_MESSAGES = {
22
+ ADDRESS_NOT_FOUND: "Cannot find agent '{to}'. Check the handle.",
23
+ ACCESS_DENIED: "No connection to '{to}'. Send a connection request first.",
24
+ RECIPIENT_UNREACHABLE: "Agent '{to}' is currently unreachable. Try again later.",
25
+ MAILBOX_FULL: "Agent '{to}' mailbox is full. Try again later.",
26
+ INTERNAL_ERROR: "Network error. Try again.",
27
+ };
28
+ function formatErrorMessage(code, to) {
29
+ const template = ERROR_MESSAGES[code] ?? `Send failed: ${code}`;
30
+ return to ? template.replace(/\{to\}/g, to) : template;
31
+ }
32
+ // ---------------------------------------------------------------------------
18
33
  // ConversationManager
19
34
  // ---------------------------------------------------------------------------
20
35
  export class ConversationManager {
21
- sessionLifecycle;
22
36
  client;
23
37
  connectorHost;
24
- /** Conversations keyed by MSTP address */
38
+ /** Conversations keyed by routable address */
25
39
  conversations = new Map();
26
40
  constructor(client, connectorHost) {
27
41
  this.client = client;
28
42
  this.connectorHost = connectorHost;
29
- this.sessionLifecycle = new SessionLifecycle(client);
30
43
  }
31
44
  // -------------------------------------------------------------------------
32
45
  // Public API — identity-based
33
46
  // -------------------------------------------------------------------------
34
47
  /**
35
- * Send a message to a contact. Session management is transparent.
36
- * Creates a session automatically if none exists (D1 — sync-but-transparent).
48
+ * Send a message to a contact. Sends directly to the resolved address —
49
+ * no session management needed.
37
50
  *
38
- * @param contact - Handle or MSTP address of the target agent.
51
+ * Returns a structured result with status and optional error.
52
+ * ConnectorError codes (ADDRESS_NOT_FOUND, ACCESS_DENIED, etc.) are
53
+ * mapped to human-readable messages for LLM tool results.
54
+ *
55
+ * @param contact - Handle, MSTP address, or passport address of the target.
39
56
  * @param content - Message content to send.
40
57
  */
41
58
  async send(contact, content) {
@@ -50,81 +67,62 @@ export class ConversationManager {
50
67
  });
51
68
  }
52
69
  try {
53
- const sessionId = await this.sessionLifecycle.ensureSession(address);
54
- const sent = this.client.sendMessage(sessionId, content, {
55
- contentType: "text",
56
- });
57
- if (!sent) {
58
- return {
59
- status: "failed",
60
- error: "Failed to send message. The network connection may be temporarily unavailable.",
61
- };
62
- }
70
+ const ack = await this.client.send(address, content, "text");
63
71
  // Update lastMessageAt
64
72
  const entry = this.conversations.get(address);
65
73
  if (entry) {
66
74
  entry.lastMessageAt = Date.now();
67
75
  }
68
- dbg("send contact=%s address=%s sessionId=%s", contact, address, sessionId);
76
+ dbg("send contact=%s address=%s status=%s", contact, address, ack.status);
69
77
  return { status: "sent" };
70
78
  }
71
79
  catch (err) {
80
+ if (err instanceof ConnectorError) {
81
+ const message = formatErrorMessage(err.code, err.to ?? contact);
82
+ dbg("send failed contact=%s code=%s message=%s", contact, err.code, message);
83
+ return { status: "failed", error: message, errorCode: err.code };
84
+ }
72
85
  const message = err instanceof Error ? err.message : "Unknown error";
73
86
  dbg("send failed contact=%s error=%s", contact, message);
74
87
  return { status: "failed", error: message };
75
88
  }
76
89
  }
77
90
  /**
78
- * Register an inbound session (remote agent initiated).
79
- * Called by the channel adapter on SESSION_CREATED with direction=inbound.
91
+ * Register an inbound conversation (remote agent or visitor initiated).
92
+ * Called by the channel adapter when a message arrives from a new address.
80
93
  */
81
- registerInbound(sessionId, contact, address) {
82
- this.sessionLifecycle.registerInbound(sessionId, address);
83
- if (!this.conversations.has(address)) {
84
- this.conversations.set(address, {
85
- contact,
86
- address,
87
- lastMessageAt: Date.now(),
88
- initiatedBy: "remote",
89
- });
90
- }
91
- else {
92
- const entry = this.conversations.get(address);
93
- if (entry) {
94
- entry.lastMessageAt = Date.now();
95
- }
96
- }
97
- dbg("registerInbound sessionId=%s contact=%s address=%s", sessionId, contact, address);
94
+ registerInbound(contact, address) {
95
+ this.ensureConversationEntry(contact, address, "remote");
96
+ dbg("registerInbound contact=%s address=%s", contact, address);
98
97
  }
99
98
  /**
100
- * Resolve a sessionId to a contact handle.
101
- * Used by the inbound dispatcher to derive sender identity from a message event.
99
+ * Resolve a routable address to a contact handle.
100
+ * Used by the inbound dispatcher to derive sender identity from MESSAGE_RECEIVED.from.
102
101
  */
103
- getContactBySessionId(sessionId) {
104
- const address = this.sessionLifecycle.getAddressBySessionId(sessionId);
105
- if (!address)
106
- return undefined;
102
+ getContactByAddress(address) {
107
103
  const entry = this.conversations.get(address);
108
- return entry?.contact;
109
- }
110
- /**
111
- * Resolve a sessionId to an MSTP address.
112
- * Used by the channel adapter for reverse lookup.
113
- */
114
- getAddressBySessionId(sessionId) {
115
- return this.sessionLifecycle.getAddressBySessionId(sessionId);
104
+ if (entry)
105
+ return entry.contact;
106
+ // Try resolving — the address might be a full MSTP address while conversations
107
+ // are keyed by a different form. Extract handle as fallback.
108
+ for (const [, e] of this.conversations) {
109
+ if (e.address === address)
110
+ return e.contact;
111
+ }
112
+ return undefined;
116
113
  }
117
114
  /**
118
115
  * List all conversations with metadata.
116
+ * `active` is always true for address-based protocol — there is no session
117
+ * lifecycle. Conversations persist until explicitly ended or Plugin restarts.
119
118
  */
120
119
  listConversations() {
121
120
  const result = [];
122
- for (const [address, entry] of this.conversations) {
123
- const sessionId = this.sessionLifecycle.getSession(address);
121
+ for (const [, entry] of this.conversations) {
124
122
  result.push({
125
123
  contact: entry.contact,
126
124
  address: entry.address,
127
- active: sessionId !== null,
125
+ active: true,
128
126
  lastMessageAt: entry.lastMessageAt,
129
127
  initiatedBy: entry.initiatedBy,
130
128
  });
@@ -144,50 +142,76 @@ export class ConversationManager {
144
142
  }
145
143
  /**
146
144
  * End the conversation with a contact.
147
- * Closes the active session if one exists.
145
+ * Removes the conversation entry. No END_SESSION needed — the Connector
146
+ * manages connection lifecycle (idle timeout).
148
147
  */
149
148
  endConversation(contact) {
150
149
  const address = this.resolveAddress(contact);
151
- this.sessionLifecycle.closeSession(address);
152
150
  this.conversations.delete(address);
153
151
  dbg("endConversation contact=%s address=%s", contact, address);
154
152
  }
155
- // NOTE: handleDisconnected() and handleSessionEnded() are NOT needed here.
156
- // SessionLifecycle owns session state transitions via its own event subscriptions
157
- // (session_ended, disconnected). Conversations persist across both events —
158
- // sessions are recreated transparently on the next send().
159
153
  // -------------------------------------------------------------------------
160
154
  // Contact resolution
161
155
  // -------------------------------------------------------------------------
162
156
  /**
163
- * Resolve a contact (handle or MSTP address) to an MSTP address.
157
+ * Resolve a contact (handle, MSTP address, or passport address) to a
158
+ * routable address.
164
159
  *
165
160
  * Resolution order:
166
- * 1. If `contact` is already an MSTP address (starts with mstps:// or mstp://),
167
- * return it as-is.
168
- * 2. If `contact` is already a key in the conversations map (e.g., a sessionId
169
- * used as fallback address for direct MSTP clients), return it as-is.
170
- * 3. Otherwise construct: `mstps://${connectorHost}/${handle}`.
161
+ * 1. MSTP address (starts with mstps:// or mstp://) → return as-is.
162
+ * 2. Passport address (starts with passport:) → return as-is (already routable).
163
+ * 3. Already a key in the conversations map return as-is.
164
+ * 4. Reverse lookup: if any existing conversation has this handle as its
165
+ * contact, use that conversation's address. This ensures replies to a
166
+ * cross-Connector sender route to the sender's original address, not to
167
+ * a locally-constructed address.
168
+ * 5. Otherwise treat as handle → construct `mstps://${connectorHost}/${handle}`.
171
169
  */
172
170
  resolveAddress(contact) {
173
171
  if (contact.startsWith("mstps://") || contact.startsWith("mstp://")) {
174
172
  return contact;
175
173
  }
176
- // Check if the contact string is already a registered conversation key
177
- // (handles the fallback case where sessionId is used as address).
174
+ if (contact.startsWith("passport:")) {
175
+ return contact;
176
+ }
178
177
  if (this.conversations.has(contact)) {
179
178
  return contact;
180
179
  }
180
+ // Reverse lookup: handle → existing conversation address.
181
+ // Handles the case where inbound arrived from a remote Connector
182
+ // (e.g., "mstps://remote-host/mason") but the LLM replies using
183
+ // the bare handle "mason".
184
+ for (const [addr, entry] of this.conversations) {
185
+ if (entry.contact === contact)
186
+ return addr;
187
+ }
181
188
  return `mstps://${this.connectorHost}/${contact}`;
182
189
  }
183
190
  /**
184
- * Extract handle from an MSTP address.
185
- * `mstps://preview.masons.ai/alice` -> `alice`
191
+ * Extract a display-friendly handle from any address scheme.
192
+ * Delegates to shared utility — single source of truth.
186
193
  */
187
194
  extractHandle(address) {
188
- const trimmed = address.replace(/\/+$/, "");
189
- const parts = trimmed.split("/");
190
- return parts[parts.length - 1] || address;
195
+ return extractHandleFromAddress(address);
196
+ }
197
+ // -------------------------------------------------------------------------
198
+ // Internal
199
+ // -------------------------------------------------------------------------
200
+ ensureConversationEntry(contact, address, initiatedBy) {
201
+ if (!this.conversations.has(address)) {
202
+ this.conversations.set(address, {
203
+ contact,
204
+ address,
205
+ lastMessageAt: Date.now(),
206
+ initiatedBy,
207
+ });
208
+ }
209
+ else {
210
+ const entry = this.conversations.get(address);
211
+ if (entry) {
212
+ entry.lastMessageAt = Date.now();
213
+ }
214
+ }
191
215
  }
192
216
  // -------------------------------------------------------------------------
193
217
  // Test helpers
@@ -195,6 +219,5 @@ export class ConversationManager {
195
219
  /** @internal Reset for test isolation. */
196
220
  _resetForTesting() {
197
221
  this.conversations.clear();
198
- this.sessionLifecycle._resetForTesting();
199
222
  }
200
223
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Agent Environment Context — self-identity and owner identity.
3
+ *
4
+ * Populated from REGISTER_ACK when the Connector includes agent/owner fields
5
+ * (#969). Connection-scoped: cleared on disconnect, refreshed on reconnect.
6
+ *
7
+ * Module-level singleton — shared between connector-client.ts (write) and
8
+ * plugin.ts (read). Follows the same pattern as owner-session-state.ts.
9
+ *
10
+ * **Trust model**: Identity is Connector-asserted, not cryptographically
11
+ * verified. The Plugin trusts the Connector because they communicate over an
12
+ * authenticated WebSocket channel. A self-hosted Connector could assert
13
+ * arbitrary identity — this is the same trust level as the `from` field on
14
+ * MESSAGE_RECEIVED. Do not build security-critical logic on these values.
15
+ */
16
+ export interface AgentIdentity {
17
+ handle: string;
18
+ name?: string;
19
+ }
20
+ export interface OwnerIdentity {
21
+ handle: string;
22
+ displayName?: string;
23
+ }
24
+ /**
25
+ * Store agent and owner identity from an enriched REGISTER_ACK.
26
+ *
27
+ * Either parameter may be undefined (old Connector, or agent without
28
+ * a bound owner). Undefined values leave the corresponding state as null.
29
+ */
30
+ export declare function setEnvironmentContext(agent?: {
31
+ handle: string;
32
+ name?: string;
33
+ }, owner?: {
34
+ handle: string;
35
+ displayName?: string;
36
+ }): void;
37
+ export declare function getAgentIdentity(): AgentIdentity | null;
38
+ export declare function getOwnerIdentity(): OwnerIdentity | null;
39
+ /** Convenience: get the owner's handle for interaction tagging. */
40
+ export declare function getOwnerHandle(): string | null;
41
+ /** Clear all environment context (connection lost — identity invalid). */
42
+ export declare function clearEnvironmentContext(): void;
43
+ /** @internal Reset module state for test isolation. */
44
+ export declare function _resetForTesting(): void;
45
+ //# sourceMappingURL=environment-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-context.d.ts","sourceRoot":"","sources":["../src/environment-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACzC,KAAK,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,IAAI,CAkBN;AAMD,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED,mEAAmE;AACnE,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAMD,0EAA0E;AAC1E,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAMD,uDAAuD;AACvD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Agent Environment Context — self-identity and owner identity.
3
+ *
4
+ * Populated from REGISTER_ACK when the Connector includes agent/owner fields
5
+ * (#969). Connection-scoped: cleared on disconnect, refreshed on reconnect.
6
+ *
7
+ * Module-level singleton — shared between connector-client.ts (write) and
8
+ * plugin.ts (read). Follows the same pattern as owner-session-state.ts.
9
+ *
10
+ * **Trust model**: Identity is Connector-asserted, not cryptographically
11
+ * verified. The Plugin trusts the Connector because they communicate over an
12
+ * authenticated WebSocket channel. A self-hosted Connector could assert
13
+ * arbitrary identity — this is the same trust level as the `from` field on
14
+ * MESSAGE_RECEIVED. Do not build security-critical logic on these values.
15
+ */
16
+ // ---------------------------------------------------------------------------
17
+ // State
18
+ // ---------------------------------------------------------------------------
19
+ let agentIdentity = null;
20
+ let ownerIdentity = null;
21
+ // ---------------------------------------------------------------------------
22
+ // Write API (called from connector-client.ts on REGISTER_ACK)
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * Store agent and owner identity from an enriched REGISTER_ACK.
26
+ *
27
+ * Either parameter may be undefined (old Connector, or agent without
28
+ * a bound owner). Undefined values leave the corresponding state as null.
29
+ */
30
+ export function setEnvironmentContext(agent, owner) {
31
+ // Defensive: validate handle is a string even though types say so.
32
+ // A malformed REGISTER_ACK from an untrusted Connector could send
33
+ // non-string values; guard prevents storing garbage in module state.
34
+ if (agent && typeof agent.handle === "string") {
35
+ const identity = { handle: agent.handle };
36
+ if (agent.name)
37
+ identity.name = agent.name;
38
+ agentIdentity = identity;
39
+ }
40
+ else {
41
+ agentIdentity = null;
42
+ }
43
+ if (owner && typeof owner.handle === "string") {
44
+ const identity = { handle: owner.handle };
45
+ if (owner.displayName)
46
+ identity.displayName = owner.displayName;
47
+ ownerIdentity = identity;
48
+ }
49
+ else {
50
+ ownerIdentity = null;
51
+ }
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Read API (called from plugin.ts in before_prompt_build)
55
+ // ---------------------------------------------------------------------------
56
+ export function getAgentIdentity() {
57
+ return agentIdentity;
58
+ }
59
+ export function getOwnerIdentity() {
60
+ return ownerIdentity;
61
+ }
62
+ /** Convenience: get the owner's handle for interaction tagging. */
63
+ export function getOwnerHandle() {
64
+ return ownerIdentity?.handle ?? null;
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Lifecycle
68
+ // ---------------------------------------------------------------------------
69
+ /** Clear all environment context (connection lost — identity invalid). */
70
+ export function clearEnvironmentContext() {
71
+ agentIdentity = null;
72
+ ownerIdentity = null;
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Test-only reset
76
+ // ---------------------------------------------------------------------------
77
+ /** @internal Reset module state for test isolation. */
78
+ export function _resetForTesting() {
79
+ agentIdentity = null;
80
+ ownerIdentity = null;
81
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared handle extraction utility.
3
+ *
4
+ * Used by ConversationManager (contact resolution) and Channel (sender name derivation).
5
+ * Single source of truth for address → display handle mapping.
6
+ */
7
+ /**
8
+ * Extract a display-friendly handle from any address scheme.
9
+ *
10
+ * - `mstps://preview.masons.ai/alice` → `alice`
11
+ * - `passport:@luomingke` → `luomingke`
12
+ * - `passport:user_abc` → `user_abc`
13
+ * - `mason` → `mason` (bare handle passthrough)
14
+ */
15
+ export declare function extractHandleFromAddress(address: string): string;
16
+ //# sourceMappingURL=handle-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-utils.d.ts","sourceRoot":"","sources":["../src/handle-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAUhE"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared handle extraction utility.
3
+ *
4
+ * Used by ConversationManager (contact resolution) and Channel (sender name derivation).
5
+ * Single source of truth for address → display handle mapping.
6
+ */
7
+ /**
8
+ * Extract a display-friendly handle from any address scheme.
9
+ *
10
+ * - `mstps://preview.masons.ai/alice` → `alice`
11
+ * - `passport:@luomingke` → `luomingke`
12
+ * - `passport:user_abc` → `user_abc`
13
+ * - `mason` → `mason` (bare handle passthrough)
14
+ */
15
+ export function extractHandleFromAddress(address) {
16
+ if (address.startsWith("passport:@")) {
17
+ return address.slice("passport:@".length);
18
+ }
19
+ if (address.startsWith("passport:")) {
20
+ return address.slice("passport:".length);
21
+ }
22
+ const trimmed = address.replace(/\/+$/, "");
23
+ const parts = trimmed.split("/");
24
+ return parts[parts.length - 1] || address;
25
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { AUTHORIZED_TOKEN_TTL_MS, SETUP_CODE_CHARSET, SETUP_CODE_LENGTH, SETUP_CODE_TTL_MS, } from "./constants.js";
2
2
  export type { AgentNetworkAccount } from "./config-schema.js";
3
- export type { ErrorEvent, MessageReceivedEvent, RegisterAckEvent, SessionCreatedEvent, SessionEndedEvent, } from "./types.js";
3
+ export type { AddressedMessageEvent, DeliveryPendingEvent, RegisterAckEvent, SendAckEvent, StructuredErrorEvent, } from "./types.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAIxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EACV,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAIxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
@@ -1,31 +1,24 @@
1
1
  /**
2
- * Owner Session State — tracks which MSTP sessions belong to the agent's owner.
2
+ * Owner Identity State — tracks which addresses belong to the agent's owner.
3
3
  *
4
4
  * When the Connector detects that a Passport visitor is the agent's owner
5
- * (deterministic userId comparison, #836), it sets `is_owner: true` in the
6
- * SESSION_CREATED metadata. This module maintains the Plugin-side state:
5
+ * (deterministic userId comparison, #836), it includes `is_owner: true` in
6
+ * MESSAGE_RECEIVED metadata. This module maintains the Plugin-side state:
7
7
  *
8
- * - `ownerSessionIds`: Set of session IDs where the visitor is the owner.
9
- * Populated on session_created, cleaned up on session_ended / disconnected.
8
+ * - `ownerAddresses`: Set of routable addresses where the visitor is the owner.
9
+ * Populated on first MESSAGE_RECEIVED with is_owner=true, cleared on disconnect.
10
10
  *
11
11
  * - `currentTurnIsOwner`: Per-turn flag set in the message_received handler
12
12
  * and read in before_prompt_build. Reset after each read to avoid stale state.
13
- * Race condition note (from design review): if two messages arrive on
14
- * different sessions concurrently, the flag may reflect the wrong session.
15
- * Fail-safe: owner gets the routing warning (same as today's behavior).
16
- * Acceptable for Phase 1 — OpenClaw processes turns sequentially per agent.
17
13
  *
18
14
  * Module-level singleton — shared between channel.ts (write) and plugin.ts (read).
19
- * Same pattern as owner-notes.ts.
20
15
  */
21
- /** Mark a session as belonging to the owner. */
22
- export declare function markOwnerSession(sessionId: string): void;
23
- /** Remove a session from owner tracking (session ended or disconnected). */
24
- export declare function removeOwnerSession(sessionId: string): void;
25
- /** Clear all owner sessions (connection lost — all sessions are invalid). */
26
- export declare function clearOwnerSessions(): void;
27
- /** Check if a session belongs to the owner. */
28
- export declare function isOwnerSession(sessionId: string): boolean;
16
+ /** Mark an address as belonging to the owner. */
17
+ export declare function markOwnerAddress(address: string): void;
18
+ /** Check if an address belongs to the owner. */
19
+ export declare function isOwnerAddress(address: string): boolean;
20
+ /** Clear all owner state (connection lost — all addresses invalid). */
21
+ export declare function clearOwnerState(): void;
29
22
  /**
30
23
  * Set the per-turn owner flag. Called in message_received before dispatch.
31
24
  * The flag is consumed (read + reset) by before_prompt_build.
@@ -1 +1 @@
1
- {"version":3,"file":"owner-session-state.d.ts","sourceRoot":"","sources":["../src/owner-session-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgBH,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE1D;AAMD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAInD"}
1
+ {"version":3,"file":"owner-session-state.d.ts","sourceRoot":"","sources":["../src/owner-session-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,iDAAiD;AACjD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE1D;AAMD;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAInD"}
@@ -1,50 +1,41 @@
1
1
  /**
2
- * Owner Session State — tracks which MSTP sessions belong to the agent's owner.
2
+ * Owner Identity State — tracks which addresses belong to the agent's owner.
3
3
  *
4
4
  * When the Connector detects that a Passport visitor is the agent's owner
5
- * (deterministic userId comparison, #836), it sets `is_owner: true` in the
6
- * SESSION_CREATED metadata. This module maintains the Plugin-side state:
5
+ * (deterministic userId comparison, #836), it includes `is_owner: true` in
6
+ * MESSAGE_RECEIVED metadata. This module maintains the Plugin-side state:
7
7
  *
8
- * - `ownerSessionIds`: Set of session IDs where the visitor is the owner.
9
- * Populated on session_created, cleaned up on session_ended / disconnected.
8
+ * - `ownerAddresses`: Set of routable addresses where the visitor is the owner.
9
+ * Populated on first MESSAGE_RECEIVED with is_owner=true, cleared on disconnect.
10
10
  *
11
11
  * - `currentTurnIsOwner`: Per-turn flag set in the message_received handler
12
12
  * and read in before_prompt_build. Reset after each read to avoid stale state.
13
- * Race condition note (from design review): if two messages arrive on
14
- * different sessions concurrently, the flag may reflect the wrong session.
15
- * Fail-safe: owner gets the routing warning (same as today's behavior).
16
- * Acceptable for Phase 1 — OpenClaw processes turns sequentially per agent.
17
13
  *
18
14
  * Module-level singleton — shared between channel.ts (write) and plugin.ts (read).
19
- * Same pattern as owner-notes.ts.
20
15
  */
21
16
  // ---------------------------------------------------------------------------
22
17
  // State
23
18
  // ---------------------------------------------------------------------------
24
- /** Session IDs where the visitor has been identified as the agent's owner. */
25
- const ownerSessionIds = new Set();
26
- /** Whether the current LLM turn was triggered by an owner session. */
19
+ /** Routable addresses where the visitor has been identified as the owner. */
20
+ const ownerAddresses = new Set();
21
+ /** Whether the current LLM turn was triggered by an owner. */
27
22
  let currentTurnIsOwner = false;
28
23
  // ---------------------------------------------------------------------------
29
24
  // Write API (called from channel.ts)
30
25
  // ---------------------------------------------------------------------------
31
- /** Mark a session as belonging to the owner. */
32
- export function markOwnerSession(sessionId) {
33
- ownerSessionIds.add(sessionId);
26
+ /** Mark an address as belonging to the owner. */
27
+ export function markOwnerAddress(address) {
28
+ ownerAddresses.add(address);
34
29
  }
35
- /** Remove a session from owner tracking (session ended or disconnected). */
36
- export function removeOwnerSession(sessionId) {
37
- ownerSessionIds.delete(sessionId);
30
+ /** Check if an address belongs to the owner. */
31
+ export function isOwnerAddress(address) {
32
+ return ownerAddresses.has(address);
38
33
  }
39
- /** Clear all owner sessions (connection lost — all sessions are invalid). */
40
- export function clearOwnerSessions() {
41
- ownerSessionIds.clear();
34
+ /** Clear all owner state (connection lost — all addresses invalid). */
35
+ export function clearOwnerState() {
36
+ ownerAddresses.clear();
42
37
  currentTurnIsOwner = false;
43
38
  }
44
- /** Check if a session belongs to the owner. */
45
- export function isOwnerSession(sessionId) {
46
- return ownerSessionIds.has(sessionId);
47
- }
48
39
  /**
49
40
  * Set the per-turn owner flag. Called in message_received before dispatch.
50
41
  * The flag is consumed (read + reset) by before_prompt_build.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAoHA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CAgThC,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AA8HA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;kBA4BI,iBAAiB;CA6UhC,CAAC;AAEF,eAAe,MAAM,CAAC"}