@ouro.bot/cli 0.1.0-alpha.669 → 0.1.0-alpha.670

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.
@@ -51,12 +51,9 @@ const config_1 = require("../heart/config");
51
51
  const session_events_1 = require("../heart/session-events");
52
52
  const context_1 = require("../mind/context");
53
53
  const prompt_1 = require("../mind/prompt");
54
- const channel_1 = require("../mind/friends/channel");
55
- const resolver_1 = require("../mind/friends/resolver");
56
- const store_file_1 = require("../mind/friends/store-file");
54
+ const friends_1 = require("@ouro.bot/friends");
57
55
  const pending_1 = require("../mind/pending");
58
56
  const context_2 = require("../mind/context");
59
- const tokens_1 = require("../mind/friends/tokens");
60
57
  const trust_gate_1 = require("./trust-gate");
61
58
  const pipeline_1 = require("./pipeline");
62
59
  const mcp_manager_1 = require("../repertoire/mcp-manager");
@@ -193,8 +190,8 @@ async function runSenseTurn(options) {
193
190
  // Resolve context
194
191
  const agentRoot = (0, identity_1.getAgentRoot)(agentName);
195
192
  const friendsPath = path.join(agentRoot, "friends");
196
- const friendStore = new store_file_1.FileFriendStore(friendsPath);
197
- const capabilities = (0, channel_1.getChannelCapabilities)(channel);
193
+ const friendStore = new friends_1.FileFriendStore(friendsPath);
194
+ const capabilities = (0, friends_1.getChannelCapabilities)(channel);
198
195
  // If friendId looks like a UUID, look up the friend record directly and use its identity.
199
196
  // Otherwise, resolve as a local user (same pattern as CLI sense).
200
197
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-/.test(friendId);
@@ -229,7 +226,7 @@ async function runSenseTurn(options) {
229
226
  const username = os.userInfo().username;
230
227
  resolverParams = { provider: "local", externalId: username, displayName: username, channel };
231
228
  }
232
- const resolver = new resolver_1.FriendResolver(friendStore, resolverParams);
229
+ const resolver = new friends_1.FriendResolver(friendStore, resolverParams);
233
230
  // Initialize MCP manager so MCP tools appear as first-class tools in the agent's tool list.
234
231
  // Runtime MCP servers (e.g. Workbench's ouro_workbench) are passed per-turn for THIS agent only.
235
232
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)(options.runtimeMcpServers ? { runtimeServers: options.runtimeMcpServers } : undefined) ?? undefined;
@@ -351,7 +348,7 @@ async function runSenseTurn(options) {
351
348
  persistPromise = (0, context_2.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
352
349
  },
353
350
  /* v8 ignore stop */
354
- accumulateFriendTokens: tokens_1.accumulateFriendTokens,
351
+ accumulateFriendTokens: friends_1.accumulateFriendTokens,
355
352
  });
356
353
  if (turnResult.gateResult && !turnResult.gateResult.allowed) {
357
354
  const blockedResponse = "autoReply" in turnResult.gateResult
@@ -51,7 +51,7 @@ const teams_apps_1 = require("@microsoft/teams.apps");
51
51
  const teams_dev_1 = require("@microsoft/teams.dev");
52
52
  const core_1 = require("../heart/core");
53
53
  const tools_1 = require("../repertoire/tools");
54
- const channel_1 = require("../mind/friends/channel");
54
+ const friends_1 = require("@ouro.bot/friends");
55
55
  const config_1 = require("../heart/config");
56
56
  const prompt_1 = require("../mind/prompt");
57
57
  const phrases_1 = require("../mind/phrases");
@@ -62,10 +62,6 @@ const commands_1 = require("./commands");
62
62
  const nerves_1 = require("../nerves");
63
63
  const runtime_1 = require("../nerves/runtime");
64
64
  const proactive_content_guard_1 = require("./proactive-content-guard");
65
- const store_file_1 = require("../mind/friends/store-file");
66
- const types_1 = require("../mind/friends/types");
67
- const resolver_1 = require("../mind/friends/resolver");
68
- const tokens_1 = require("../mind/friends/tokens");
69
65
  const turn_coordinator_1 = require("../heart/turn-coordinator");
70
66
  const identity_1 = require("../heart/identity");
71
67
  const mcp_manager_1 = require("../repertoire/mcp-manager");
@@ -610,7 +606,7 @@ async function withConversationLock(convId, fn) {
610
606
  // are deleted while the process is alive.
611
607
  function getFriendStore() {
612
608
  const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
613
- return new store_file_1.FileFriendStore(friendsPath);
609
+ return new friends_1.FileFriendStore(friendsPath);
614
610
  }
615
611
  function createTeamsCommandRegistry() {
616
612
  const registry = (0, commands_1.createCommandRegistry)();
@@ -660,7 +656,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
660
656
  const provider = teamsContext?.aadObjectId ? "aad" : "teams-conversation";
661
657
  const externalId = teamsContext?.aadObjectId || conversationId;
662
658
  // Build FriendResolver for the pipeline
663
- const resolver = new resolver_1.FriendResolver(store, {
659
+ const resolver = new friends_1.FriendResolver(store, {
664
660
  provider,
665
661
  externalId,
666
662
  tenantId: teamsContext?.tenantId,
@@ -676,7 +672,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
676
672
  const callbacks = createTeamsCallbacks(stream, controller, sendMessage, { conversationId, flushIntervalMs: channelConfig.flushIntervalMs, ...(reactionOverrides?.suppressEmptyStreamMessage ? { suppressEmptyStreamMessage: true } : {}) });
677
673
  const traceId = (0, nerves_1.createTraceId)();
678
674
  const sessPath = (0, config_2.sessionPath)(friendId, "teams", conversationId);
679
- const teamsCapabilities = (0, channel_1.getChannelCapabilities)("teams");
675
+ const teamsCapabilities = (0, friends_1.getChannelCapabilities)("teams");
680
676
  const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "teams", conversationId);
681
677
  // Build Teams-specific toolContext fields for injection into the pipeline
682
678
  const teamsToolContext = teamsContext ? {
@@ -773,7 +769,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
773
769
  const prepared = (0, context_1.postTurnTrim)(turnMessages, usage, hooks);
774
770
  (0, context_1.deferPostTurnPersist)(sessionPathArg, prepared, usage, state);
775
771
  },
776
- accumulateFriendTokens: tokens_1.accumulateFriendTokens,
772
+ accumulateFriendTokens: friends_1.accumulateFriendTokens,
777
773
  signal: controller.signal,
778
774
  runAgentOptions: agentOptions,
779
775
  failoverState: teamsFailoverState,
@@ -1024,7 +1020,7 @@ function registerBotHandlers(app, label) {
1024
1020
  const commandStore = getFriendStore();
1025
1021
  const commandProvider = activity.from?.aadObjectId ? "aad" : "teams-conversation";
1026
1022
  const commandExternalId = activity.from?.aadObjectId || convId;
1027
- const commandResolver = new resolver_1.FriendResolver(commandStore, {
1023
+ const commandResolver = new friends_1.FriendResolver(commandStore, {
1028
1024
  provider: commandProvider,
1029
1025
  externalId: commandExternalId,
1030
1026
  tenantId: activity.conversation?.tenantId,
@@ -1139,14 +1135,14 @@ function findAadObjectId(friend) {
1139
1135
  function resolveTeamsFriendStore(deps) {
1140
1136
  return deps.store
1141
1137
  ?? deps.createFriendStore?.()
1142
- ?? new store_file_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
1138
+ ?? new friends_1.FileFriendStore(path.join((0, identity_1.getAgentRoot)(), "friends"));
1143
1139
  }
1144
1140
  function getTeamsConversations(botApi) {
1145
1141
  return botApi.conversations;
1146
1142
  }
1147
1143
  function hasExplicitCrossChatAuthorization(params) {
1148
1144
  return params.intent === "explicit_cross_chat"
1149
- && types_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
1145
+ && friends_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
1150
1146
  }
1151
1147
  async function sendProactiveTeamsMessageToSession(params, deps) {
1152
1148
  const store = resolveTeamsFriendStore(deps);
@@ -1168,7 +1164,7 @@ async function sendProactiveTeamsMessageToSession(params, deps) {
1168
1164
  });
1169
1165
  return { delivered: false, reason: "friend_not_found" };
1170
1166
  }
1171
- if (!hasExplicitCrossChatAuthorization(params) && !types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
1167
+ if (!hasExplicitCrossChatAuthorization(params) && !friends_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
1172
1168
  (0, runtime_1.emitNervesEvent)({
1173
1169
  component: "senses",
1174
1170
  event: "senses.teams_proactive_trust_skip",
@@ -1380,7 +1376,7 @@ function startTeamsApp() {
1380
1376
  const port = process.env.PORT ? Number(process.env.PORT) : (0, config_2.getTeamsChannelConfig)().port;
1381
1377
  app.start(port);
1382
1378
  // Diagnostic: log tool count at startup to verify deploy
1383
- const startupTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("teams"));
1379
+ const startupTools = (0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("teams"));
1384
1380
  const toolNames = startupTools.map((t) => t.function.name);
1385
1381
  (0, runtime_1.emitNervesEvent)({ level: "info", event: "channel.app_started", component: "channels", message: `Teams bot started on port ${port} with ${mode} (chunked streaming)`, meta: { port, mode, toolCount: toolNames.length, hasProactive: toolNames.includes("teams_send_message") } });
1386
1382
  // --- Secondary bot (dual-bot support) ---
@@ -39,7 +39,7 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const identity_1 = require("../heart/identity");
41
41
  const runtime_1 = require("../nerves/runtime");
42
- const types_1 = require("../mind/friends/types");
42
+ const friends_1 = require("@ouro.bot/friends");
43
43
  const pending_1 = require("../mind/pending");
44
44
  // Canned reply; eventually agents should compose their own first-contact message
45
45
  exports.STRANGER_AUTO_REPLY = "I'm sorry, I'm not allowed to talk to strangers";
@@ -205,7 +205,7 @@ function enforceTrustGate(input) {
205
205
  }
206
206
  const trustLevel = input.friend.trustLevel ?? "friend";
207
207
  // Family and friend — always allow on open
208
- if ((0, types_1.isTrustedLevel)(trustLevel)) {
208
+ if ((0, friends_1.isTrustedLevel)(trustLevel)) {
209
209
  return { allowed: true };
210
210
  }
211
211
  const bundleRoot = input.bundleRoot ?? (0, identity_1.getAgentRoot)();
@@ -39,7 +39,7 @@ exports.voiceOutboundPhoneNumberForFriend = voiceOutboundPhoneNumberForFriend;
39
39
  exports.placeTrustedFriendVoiceOutboundCall = placeTrustedFriendVoiceOutboundCall;
40
40
  const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
- const types_1 = require("../../mind/friends/types");
42
+ const friends_1 = require("@ouro.bot/friends");
43
43
  const runtime_1 = require("../../nerves/runtime");
44
44
  const phone_1 = require("./phone");
45
45
  function readVoiceOutboundFriendRecordById(friendsDir, friendId) {
@@ -122,7 +122,7 @@ async function placeTrustedFriendVoiceOutboundCall(request) {
122
122
  });
123
123
  return { status: "blocked", detail: "voice call requires a known friend record" };
124
124
  }
125
- if (!(0, types_1.isTrustedLevel)(friend.trustLevel)) {
125
+ if (!(0, friends_1.isTrustedLevel)(friend.trustLevel)) {
126
126
  (0, runtime_1.emitNervesEvent)({
127
127
  component: "senses",
128
128
  event: "senses.voice_outbound_call_blocked",
@@ -65,9 +65,7 @@ const http = __importStar(require("http"));
65
65
  const path = __importStar(require("path"));
66
66
  const ws_1 = require("ws");
67
67
  const context_1 = require("../../mind/context");
68
- const channel_1 = require("../../mind/friends/channel");
69
- const resolver_1 = require("../../mind/friends/resolver");
70
- const store_file_1 = require("../../mind/friends/store-file");
68
+ const friends_1 = require("@ouro.bot/friends");
71
69
  const identity_1 = require("../../heart/identity");
72
70
  const mcp_manager_1 = require("../../repertoire/mcp-manager");
73
71
  const tools_1 = require("../../repertoire/tools");
@@ -359,7 +357,7 @@ function voiceFriendId(options, from, callSid) {
359
357
  }
360
358
  async function resolveVoiceFriendContext(options, input) {
361
359
  const agentRoot = options.agentRoot ?? (0, identity_1.getAgentRoot)(options.agentName);
362
- const friendStore = new store_file_1.FileFriendStore(path.join(agentRoot, "friends"));
360
+ const friendStore = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
363
361
  const explicitFriendId = input.friendId?.trim();
364
362
  if (explicitFriendId) {
365
363
  const existing = await friendStore.get(explicitFriendId);
@@ -367,14 +365,14 @@ async function resolveVoiceFriendContext(options, input) {
367
365
  return {
368
366
  friendId: existing.id,
369
367
  friendStore,
370
- resolved: { friend: existing, channel: (0, channel_1.getChannelCapabilities)("voice") },
368
+ resolved: { friend: existing, channel: (0, friends_1.getChannelCapabilities)("voice") },
371
369
  };
372
370
  }
373
371
  }
374
372
  const remotePhone = (0, phone_1.normalizeTwilioE164PhoneNumber)(input.remotePhone);
375
373
  const provider = remotePhone ? "imessage-handle" : "local";
376
374
  const externalId = remotePhone || explicitFriendId || voiceFriendId(options, input.remotePhone ?? "", input.callSid);
377
- const resolver = new resolver_1.FriendResolver(friendStore, {
375
+ const resolver = new friends_1.FriendResolver(friendStore, {
378
376
  provider,
379
377
  externalId,
380
378
  displayName: remotePhone || externalId,
@@ -1492,7 +1490,7 @@ function realtimeBootstrapInstructions(agentName, voiceStyle) {
1492
1490
  ].filter(Boolean).join(" ");
1493
1491
  }
1494
1492
  function realtimeBootstrapTools() {
1495
- return realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("voice")));
1493
+ return realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("voice")));
1496
1494
  }
1497
1495
  function timeoutAfter(ms) {
1498
1496
  return new Promise((resolve) => {
@@ -1861,14 +1859,14 @@ class TwilioOpenAIRealtimeMediaStreamSession {
1861
1859
  },
1862
1860
  };
1863
1861
  void this.refreshRealtimeToolsWithMcp(resolved);
1864
- return (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, undefined);
1862
+ return (0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, undefined);
1865
1863
  }
1866
1864
  async refreshRealtimeToolsWithMcp(resolved) {
1867
1865
  try {
1868
1866
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
1869
1867
  if (!mcpManager || this.closed)
1870
1868
  return;
1871
- const tools = realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, mcpManager));
1869
+ const tools = realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, mcpManager));
1872
1870
  this.sendOpenAI({
1873
1871
  type: "session.update",
1874
1872
  session: {
@@ -2922,7 +2920,7 @@ class OpenAISipPhoneSession {
2922
2920
  },
2923
2921
  };
2924
2922
  void this.refreshRealtimeToolsWithMcp(resolved);
2925
- return (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, undefined);
2923
+ return (0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, undefined);
2926
2924
  }
2927
2925
  playRealtimeAudioCue(request) {
2928
2926
  const source = request.source ?? "tone";
@@ -2971,7 +2969,7 @@ class OpenAISipPhoneSession {
2971
2969
  const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
2972
2970
  if (!mcpManager || this.closed)
2973
2971
  return;
2974
- const tools = realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, mcpManager), OPENAI_SIP_UNSUPPORTED_TOOL_NAMES);
2972
+ const tools = realtimeToolsFromChatTools((0, tools_1.getToolsForChannel)((0, friends_1.getChannelCapabilities)("voice"), resolved.friend.toolPreferences, resolved, undefined, mcpManager), OPENAI_SIP_UNSUPPORTED_TOOL_NAMES);
2975
2973
  this.sendOpenAI({
2976
2974
  type: "session.update",
2977
2975
  session: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.669",
3
+ "version": "0.1.0-alpha.670",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -50,6 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@anthropic-ai/sdk": "^0.78.0",
53
+ "@ouro.bot/friends": "git+https://github.com/ourostack/friends.git#0095316eed6876a27f36a26ddc47ad9da800a652",
53
54
  "@azure/identity": "^4.13.0",
54
55
  "@azure/storage-blob": "^12.31.0",
55
56
  "@microsoft/teams.api": "2.0.11",
@@ -1,132 +0,0 @@
1
- "use strict";
2
- // Channel capabilities -- hardcoded const map keyed by channel identifier.
3
- // Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.channelToFacing = channelToFacing;
6
- exports.getChannelCapabilities = getChannelCapabilities;
7
- exports.isRemoteChannel = isRemoteChannel;
8
- exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
9
- const runtime_1 = require("../../nerves/runtime");
10
- const AGENT_FACING_CHANNELS = new Set(["inner", "mcp", "a2a"]);
11
- function channelToFacing(channel) {
12
- const facing = channel && AGENT_FACING_CHANNELS.has(channel) ? "agent" : "human";
13
- (0, runtime_1.emitNervesEvent)({
14
- component: "channels",
15
- event: "channel.facing_lookup",
16
- message: "channel facing lookup",
17
- meta: { channel: channel ?? "undefined", facing },
18
- });
19
- return facing;
20
- }
21
- const CHANNEL_CAPABILITIES = {
22
- cli: {
23
- channel: "cli",
24
- senseType: "local",
25
- availableIntegrations: [],
26
- supportsMarkdown: false,
27
- supportsStreaming: true,
28
- supportsRichCards: false,
29
- maxMessageLength: Infinity,
30
- },
31
- teams: {
32
- channel: "teams",
33
- senseType: "closed",
34
- availableIntegrations: ["ado", "graph", "github"],
35
- supportsMarkdown: true,
36
- supportsStreaming: true,
37
- supportsRichCards: true,
38
- maxMessageLength: Infinity,
39
- },
40
- bluebubbles: {
41
- channel: "bluebubbles",
42
- senseType: "open",
43
- availableIntegrations: [],
44
- supportsMarkdown: false,
45
- supportsStreaming: false,
46
- supportsRichCards: false,
47
- maxMessageLength: Infinity,
48
- },
49
- mail: {
50
- channel: "mail",
51
- senseType: "open",
52
- availableIntegrations: [],
53
- supportsMarkdown: false,
54
- supportsStreaming: false,
55
- supportsRichCards: false,
56
- maxMessageLength: Infinity,
57
- },
58
- voice: {
59
- channel: "voice",
60
- senseType: "local",
61
- availableIntegrations: [],
62
- supportsMarkdown: false,
63
- supportsStreaming: true,
64
- supportsRichCards: false,
65
- maxMessageLength: Infinity,
66
- },
67
- a2a: {
68
- channel: "a2a",
69
- senseType: "open",
70
- availableIntegrations: [],
71
- supportsMarkdown: true,
72
- supportsStreaming: false,
73
- supportsRichCards: false,
74
- maxMessageLength: Infinity,
75
- },
76
- inner: {
77
- channel: "inner",
78
- senseType: "internal",
79
- availableIntegrations: [],
80
- supportsMarkdown: false,
81
- supportsStreaming: true,
82
- supportsRichCards: false,
83
- maxMessageLength: Infinity,
84
- },
85
- mcp: {
86
- channel: "mcp",
87
- senseType: "local",
88
- availableIntegrations: [],
89
- supportsMarkdown: true,
90
- supportsStreaming: false,
91
- supportsRichCards: false,
92
- maxMessageLength: Infinity,
93
- },
94
- };
95
- const DEFAULT_CAPABILITIES = {
96
- channel: "cli",
97
- senseType: "local",
98
- availableIntegrations: [],
99
- supportsMarkdown: false,
100
- supportsStreaming: false,
101
- supportsRichCards: false,
102
- maxMessageLength: Infinity,
103
- };
104
- function getChannelCapabilities(channel) {
105
- (0, runtime_1.emitNervesEvent)({
106
- component: "channels",
107
- event: "channel.capabilities_lookup",
108
- message: "channel capabilities lookup",
109
- meta: { channel },
110
- });
111
- return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
112
- }
113
- /** Whether the channel is remote (open or closed) vs local/internal. */
114
- function isRemoteChannel(capabilities) {
115
- const senseType = capabilities?.senseType;
116
- return senseType !== undefined && senseType !== "local" && senseType !== "internal";
117
- }
118
- /**
119
- * Returns channel names whose senseType is "open" or "closed" -- i.e. channels
120
- * that are always-on (daemon-managed) rather than interactive or internal.
121
- */
122
- function getAlwaysOnSenseNames() {
123
- (0, runtime_1.emitNervesEvent)({
124
- component: "channels",
125
- event: "channel.always_on_lookup",
126
- message: "always-on sense names lookup",
127
- meta: {},
128
- });
129
- return Object.entries(CHANNEL_CAPABILITIES)
130
- .filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
131
- .map(([channel]) => channel);
132
- }
@@ -1,144 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.upsertGroupContextParticipants = upsertGroupContextParticipants;
4
- const node_crypto_1 = require("node:crypto");
5
- const runtime_1 = require("../../nerves/runtime");
6
- const CURRENT_SCHEMA_VERSION = 1;
7
- function normalizeDisplayName(externalId, displayName) {
8
- const trimmed = displayName?.trim();
9
- return trimmed && trimmed.length > 0 ? trimmed : externalId;
10
- }
11
- function buildNameNotes(name, now) {
12
- return name !== "Unknown"
13
- ? { name: { value: name, savedAt: now } }
14
- : {};
15
- }
16
- function dedupeParticipants(participants) {
17
- const deduped = new Map();
18
- for (const participant of participants) {
19
- const externalId = participant.externalId.trim();
20
- if (!externalId)
21
- continue;
22
- const key = `${participant.provider}:${externalId}`;
23
- if (!deduped.has(key)) {
24
- deduped.set(key, {
25
- ...participant,
26
- externalId,
27
- displayName: participant.displayName?.trim() || undefined,
28
- });
29
- }
30
- }
31
- return Array.from(deduped.values());
32
- }
33
- function createGroupExternalId(provider, groupExternalId, linkedAt) {
34
- return {
35
- provider,
36
- externalId: groupExternalId,
37
- linkedAt,
38
- };
39
- }
40
- function shouldPromoteToAcquaintance(friend) {
41
- return (friend.trustLevel ?? "stranger") === "stranger";
42
- }
43
- function createAcquaintanceRecord(participant, groupExternalId, linkedAt) {
44
- const name = normalizeDisplayName(participant.externalId, participant.displayName);
45
- return {
46
- id: (0, node_crypto_1.randomUUID)(),
47
- name,
48
- role: "acquaintance",
49
- trustLevel: "acquaintance",
50
- connections: [],
51
- externalIds: [
52
- {
53
- provider: participant.provider,
54
- externalId: participant.externalId,
55
- linkedAt,
56
- },
57
- createGroupExternalId(participant.provider, groupExternalId, linkedAt),
58
- ],
59
- tenantMemberships: [],
60
- toolPreferences: {},
61
- notes: buildNameNotes(name, linkedAt),
62
- totalTokens: 0,
63
- createdAt: linkedAt,
64
- updatedAt: linkedAt,
65
- schemaVersion: CURRENT_SCHEMA_VERSION,
66
- };
67
- }
68
- async function upsertGroupContextParticipants(input) {
69
- (0, runtime_1.emitNervesEvent)({
70
- component: "friends",
71
- event: "friends.group_context_upsert_start",
72
- message: "upserting shared-group participant context",
73
- meta: {
74
- participantCount: input.participants.length,
75
- hasGroupExternalId: input.groupExternalId.trim().length > 0,
76
- },
77
- });
78
- const groupExternalId = input.groupExternalId.trim();
79
- if (!groupExternalId) {
80
- return [];
81
- }
82
- const now = input.now ?? (() => new Date().toISOString());
83
- const participants = dedupeParticipants(input.participants);
84
- const results = [];
85
- for (const participant of participants) {
86
- const linkedAt = now();
87
- const existing = await input.store.findByExternalId(participant.provider, participant.externalId);
88
- if (!existing) {
89
- const created = createAcquaintanceRecord(participant, groupExternalId, linkedAt);
90
- await input.store.put(created.id, created);
91
- results.push({
92
- friendId: created.id,
93
- name: created.name,
94
- trustLevel: "acquaintance",
95
- created: true,
96
- updated: false,
97
- addedGroupExternalId: true,
98
- });
99
- continue;
100
- }
101
- const hasGroupExternalId = existing.externalIds.some((externalId) => externalId.externalId === groupExternalId);
102
- const promoteToAcquaintance = shouldPromoteToAcquaintance(existing);
103
- const trustLevel = promoteToAcquaintance
104
- ? "acquaintance"
105
- : existing.trustLevel;
106
- const role = promoteToAcquaintance
107
- ? "acquaintance"
108
- : existing.role;
109
- const updatedExternalIds = hasGroupExternalId
110
- ? existing.externalIds
111
- : [...existing.externalIds, createGroupExternalId(participant.provider, groupExternalId, linkedAt)];
112
- const updated = promoteToAcquaintance || !hasGroupExternalId;
113
- const record = updated
114
- ? {
115
- ...existing,
116
- role,
117
- trustLevel,
118
- externalIds: updatedExternalIds,
119
- updatedAt: linkedAt,
120
- }
121
- : existing;
122
- if (updated) {
123
- await input.store.put(record.id, record);
124
- }
125
- results.push({
126
- friendId: record.id,
127
- name: record.name,
128
- trustLevel,
129
- created: false,
130
- updated,
131
- addedGroupExternalId: !hasGroupExternalId,
132
- });
133
- }
134
- (0, runtime_1.emitNervesEvent)({
135
- component: "friends",
136
- event: "friends.group_context_upsert_end",
137
- message: "upserted shared-group participant context",
138
- meta: {
139
- participantCount: participants.length,
140
- updatedCount: results.filter((result) => result.created || result.updated).length,
141
- },
142
- });
143
- return results;
144
- }