@ouro.bot/cli 0.1.0-alpha.51 → 0.1.0-alpha.53
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/changelog.json +17 -0
- package/dist/heart/active-work.js +32 -11
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/daemon-cli.js +19 -0
- package/dist/heart/daemon/daemon-entry.js +41 -1
- package/dist/heart/daemon/daemon.js +4 -0
- package/dist/heart/daemon/launchd.js +3 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/target-resolution.js +123 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/prompt.js +27 -0
- package/dist/repertoire/tools-base.js +128 -8
- package/dist/senses/bluebubbles.js +43 -8
- package/dist/senses/pipeline.js +22 -0
- package/dist/senses/teams.js +107 -78
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeTrustContext = describeTrustContext;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
function findRelatedGroupId(friend) {
|
|
6
|
+
return friend.externalIds.find((externalId) => externalId.externalId.startsWith("group:"))?.externalId;
|
|
7
|
+
}
|
|
8
|
+
function resolveLevel(friend) {
|
|
9
|
+
return friend.trustLevel ?? "stranger";
|
|
10
|
+
}
|
|
11
|
+
function describeTrustContext(input) {
|
|
12
|
+
const level = resolveLevel(input.friend);
|
|
13
|
+
const relatedGroupId = findRelatedGroupId(input.friend);
|
|
14
|
+
const explanation = level === "family" || level === "friend"
|
|
15
|
+
? {
|
|
16
|
+
level,
|
|
17
|
+
basis: "direct",
|
|
18
|
+
summary: level === "family"
|
|
19
|
+
? "direct family trust"
|
|
20
|
+
: "direct trusted relationship",
|
|
21
|
+
why: "this relationship is directly trusted rather than inferred through a shared group or cold first contact.",
|
|
22
|
+
permits: [
|
|
23
|
+
"local operations when appropriate",
|
|
24
|
+
"proactive follow-through",
|
|
25
|
+
"full collaborative problem solving",
|
|
26
|
+
],
|
|
27
|
+
constraints: [],
|
|
28
|
+
}
|
|
29
|
+
: level === "acquaintance"
|
|
30
|
+
? {
|
|
31
|
+
level,
|
|
32
|
+
basis: "shared_group",
|
|
33
|
+
summary: relatedGroupId
|
|
34
|
+
? "known through the shared project group"
|
|
35
|
+
: "known through a shared group context",
|
|
36
|
+
why: relatedGroupId
|
|
37
|
+
? `this trust comes from the shared group context ${relatedGroupId}, not from direct endorsement.`
|
|
38
|
+
: "this trust comes from shared group context rather than direct endorsement.",
|
|
39
|
+
permits: [
|
|
40
|
+
"group-safe coordination",
|
|
41
|
+
"normal conversation inside the shared context",
|
|
42
|
+
],
|
|
43
|
+
constraints: [
|
|
44
|
+
"guarded local actions",
|
|
45
|
+
"do not assume broad private authority",
|
|
46
|
+
],
|
|
47
|
+
relatedGroupId,
|
|
48
|
+
}
|
|
49
|
+
: {
|
|
50
|
+
level,
|
|
51
|
+
basis: "unknown",
|
|
52
|
+
summary: "truly unknown first-contact context",
|
|
53
|
+
why: "this person is not known through direct trust or a shared group context.",
|
|
54
|
+
permits: [
|
|
55
|
+
"safe first-contact orientation only",
|
|
56
|
+
],
|
|
57
|
+
constraints: [
|
|
58
|
+
"first contact does not reach the full model on open channels",
|
|
59
|
+
"no local or privileged actions",
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
(0, runtime_1.emitNervesEvent)({
|
|
63
|
+
component: "friends",
|
|
64
|
+
event: "friends.trust_explained",
|
|
65
|
+
message: "built explicit trust explanation",
|
|
66
|
+
meta: {
|
|
67
|
+
channel: input.channel,
|
|
68
|
+
level: explanation.level,
|
|
69
|
+
basis: explanation.basis,
|
|
70
|
+
hasRelatedGroup: Boolean(explanation.relatedGroupId),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return explanation;
|
|
74
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -51,6 +51,7 @@ const tools_1 = require("../repertoire/tools");
|
|
|
51
51
|
const skills_1 = require("../repertoire/skills");
|
|
52
52
|
const identity_1 = require("../heart/identity");
|
|
53
53
|
const types_1 = require("./friends/types");
|
|
54
|
+
const trust_explanation_1 = require("./friends/trust-explanation");
|
|
54
55
|
const channel_1 = require("./friends/channel");
|
|
55
56
|
const runtime_1 = require("../nerves/runtime");
|
|
56
57
|
const bundle_manifest_1 = require("./bundle-manifest");
|
|
@@ -329,6 +330,31 @@ some of my tools are unavailable right now: ${toolList}
|
|
|
329
330
|
|
|
330
331
|
i don't know this person well enough yet to run local operations on their behalf. i can suggest remote-safe alternatives or ask them to run it from CLI.`;
|
|
331
332
|
}
|
|
333
|
+
function trustContextSection(context) {
|
|
334
|
+
if (!context?.friend)
|
|
335
|
+
return "";
|
|
336
|
+
const channelName = context.channel.channel;
|
|
337
|
+
if (channelName === "cli" || channelName === "inner")
|
|
338
|
+
return "";
|
|
339
|
+
const explanation = (0, trust_explanation_1.describeTrustContext)({
|
|
340
|
+
friend: context.friend,
|
|
341
|
+
channel: channelName,
|
|
342
|
+
isGroupChat: context.isGroupChat,
|
|
343
|
+
});
|
|
344
|
+
const lines = [
|
|
345
|
+
"## trust context",
|
|
346
|
+
`level: ${explanation.level}`,
|
|
347
|
+
`basis: ${explanation.basis}`,
|
|
348
|
+
`summary: ${explanation.summary}`,
|
|
349
|
+
`why: ${explanation.why}`,
|
|
350
|
+
`permits: ${explanation.permits.join(", ")}`,
|
|
351
|
+
`constraints: ${explanation.constraints.join(", ") || "none"}`,
|
|
352
|
+
];
|
|
353
|
+
if (explanation.relatedGroupId) {
|
|
354
|
+
lines.push(`related group: ${explanation.relatedGroupId}`);
|
|
355
|
+
}
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
332
358
|
function skillsSection() {
|
|
333
359
|
const names = (0, skills_1.listSkills)() || [];
|
|
334
360
|
if (!names.length)
|
|
@@ -520,6 +546,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
520
546
|
toolsSection(channel, options, context),
|
|
521
547
|
reasoningEffortSection(options),
|
|
522
548
|
toolRestrictionSection(context),
|
|
549
|
+
trustContextSection(context),
|
|
523
550
|
mixedTrustGroupSection(context),
|
|
524
551
|
skillsSection(),
|
|
525
552
|
taskBoardSection(),
|
|
@@ -50,6 +50,7 @@ const tools_1 = require("./coding/tools");
|
|
|
50
50
|
const memory_1 = require("../mind/memory");
|
|
51
51
|
const pending_1 = require("../mind/pending");
|
|
52
52
|
const progress_story_1 = require("../heart/progress-story");
|
|
53
|
+
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
53
54
|
// Tracks which file paths have been read via read_file in this session.
|
|
54
55
|
// edit_file requires a file to be read first (must-read-first guard).
|
|
55
56
|
exports.editFileReadTracker = new Set();
|
|
@@ -115,6 +116,34 @@ function normalizeProgressOutcome(text) {
|
|
|
115
116
|
}
|
|
116
117
|
return trimmed;
|
|
117
118
|
}
|
|
119
|
+
function writePendingEnvelope(queueDir, message) {
|
|
120
|
+
fs.mkdirSync(queueDir, { recursive: true });
|
|
121
|
+
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
122
|
+
const filePath = path.join(queueDir, fileName);
|
|
123
|
+
fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
|
|
124
|
+
}
|
|
125
|
+
function renderCrossChatDeliveryStatus(target, result) {
|
|
126
|
+
const phase = result.status === "delivered_now"
|
|
127
|
+
? "completed"
|
|
128
|
+
: result.status === "queued_for_later"
|
|
129
|
+
? "queued"
|
|
130
|
+
: result.status === "blocked"
|
|
131
|
+
? "blocked"
|
|
132
|
+
: "errored";
|
|
133
|
+
const lead = result.status === "delivered_now"
|
|
134
|
+
? "delivered now"
|
|
135
|
+
: result.status === "queued_for_later"
|
|
136
|
+
? "queued for later"
|
|
137
|
+
: result.status === "blocked"
|
|
138
|
+
? "blocked"
|
|
139
|
+
: "failed";
|
|
140
|
+
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
141
|
+
scope: "shared-work",
|
|
142
|
+
phase,
|
|
143
|
+
objective: `message to ${target}`,
|
|
144
|
+
outcomeText: `${lead}\n${result.detail}`,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
118
147
|
function renderInnerProgressStatus(status) {
|
|
119
148
|
if (status.processing === "pending") {
|
|
120
149
|
return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
@@ -817,7 +846,7 @@ exports.baseToolDefinitions = [
|
|
|
817
846
|
type: "function",
|
|
818
847
|
function: {
|
|
819
848
|
name: "send_message",
|
|
820
|
-
description: "send a message to a friend's session. the
|
|
849
|
+
description: "send a message to a friend's session. when the request is explicitly authorized from a trusted live chat, the harness will try to deliver immediately; otherwise it reports truthful queued/block/failure state.",
|
|
821
850
|
parameters: {
|
|
822
851
|
type: "object",
|
|
823
852
|
properties: {
|
|
@@ -843,9 +872,6 @@ exports.baseToolDefinitions = [
|
|
|
843
872
|
const pendingDir = isSelf
|
|
844
873
|
? (0, pending_1.getInnerDialogPendingDir)(agentName)
|
|
845
874
|
: (0, pending_1.getPendingDir)(agentName, friendId, channel, key);
|
|
846
|
-
fs.mkdirSync(pendingDir, { recursive: true });
|
|
847
|
-
const fileName = `${now}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
848
|
-
const filePath = path.join(pendingDir, fileName);
|
|
849
875
|
const delegatingBridgeId = findDelegatingBridgeId(ctx);
|
|
850
876
|
const delegatedFrom = isSelf
|
|
851
877
|
&& ctx?.currentSession
|
|
@@ -866,8 +892,8 @@ exports.baseToolDefinitions = [
|
|
|
866
892
|
timestamp: now,
|
|
867
893
|
...(delegatedFrom ? { delegatedFrom } : {}),
|
|
868
894
|
};
|
|
869
|
-
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
870
895
|
if (isSelf) {
|
|
896
|
+
writePendingEnvelope(pendingDir, envelope);
|
|
871
897
|
let wakeResponse = null;
|
|
872
898
|
try {
|
|
873
899
|
wakeResponse = await (0, socket_client_1.requestInnerWake)(agentName);
|
|
@@ -906,9 +932,103 @@ exports.baseToolDefinitions = [
|
|
|
906
932
|
surfaced: "nothing yet",
|
|
907
933
|
});
|
|
908
934
|
}
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
935
|
+
const deliveryResult = await (0, cross_chat_delivery_1.deliverCrossChatMessage)({
|
|
936
|
+
friendId,
|
|
937
|
+
channel,
|
|
938
|
+
key,
|
|
939
|
+
content,
|
|
940
|
+
intent: ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
941
|
+
? "explicit_cross_chat"
|
|
942
|
+
: "generic_outreach",
|
|
943
|
+
...(ctx?.currentSession && ctx.currentSession.friendId !== "self"
|
|
944
|
+
? {
|
|
945
|
+
authorizingSession: {
|
|
946
|
+
friendId: ctx.currentSession.friendId,
|
|
947
|
+
channel: ctx.currentSession.channel,
|
|
948
|
+
key: ctx.currentSession.key,
|
|
949
|
+
trustLevel: ctx?.context?.friend?.trustLevel,
|
|
950
|
+
},
|
|
951
|
+
}
|
|
952
|
+
: {}),
|
|
953
|
+
}, {
|
|
954
|
+
agentName,
|
|
955
|
+
queuePending: (message) => writePendingEnvelope(pendingDir, message),
|
|
956
|
+
deliverers: {
|
|
957
|
+
bluebubbles: async (request) => {
|
|
958
|
+
const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
|
|
959
|
+
const result = await sendProactiveBlueBubblesMessageToSession({
|
|
960
|
+
friendId: request.friendId,
|
|
961
|
+
sessionKey: request.key,
|
|
962
|
+
text: request.content,
|
|
963
|
+
intent: request.intent,
|
|
964
|
+
authorizingSession: request.authorizingSession,
|
|
965
|
+
});
|
|
966
|
+
if (result.delivered) {
|
|
967
|
+
return {
|
|
968
|
+
status: "delivered_now",
|
|
969
|
+
detail: "sent to the active bluebubbles chat now",
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if (result.reason === "missing_target") {
|
|
973
|
+
return {
|
|
974
|
+
status: "blocked",
|
|
975
|
+
detail: "bluebubbles could not resolve a routable target for that session",
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
if (result.reason === "send_error") {
|
|
979
|
+
return {
|
|
980
|
+
status: "failed",
|
|
981
|
+
detail: "bluebubbles send failed",
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
status: "unavailable",
|
|
986
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
987
|
+
};
|
|
988
|
+
},
|
|
989
|
+
teams: async (request) => {
|
|
990
|
+
if (!ctx?.botApi) {
|
|
991
|
+
return {
|
|
992
|
+
status: "unavailable",
|
|
993
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
const { sendProactiveTeamsMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/teams")));
|
|
997
|
+
const result = await sendProactiveTeamsMessageToSession({
|
|
998
|
+
friendId: request.friendId,
|
|
999
|
+
sessionKey: request.key,
|
|
1000
|
+
text: request.content,
|
|
1001
|
+
intent: request.intent,
|
|
1002
|
+
authorizingSession: request.authorizingSession,
|
|
1003
|
+
}, {
|
|
1004
|
+
botApi: ctx.botApi,
|
|
1005
|
+
});
|
|
1006
|
+
if (result.delivered) {
|
|
1007
|
+
return {
|
|
1008
|
+
status: "delivered_now",
|
|
1009
|
+
detail: "sent to the active teams chat now",
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
if (result.reason === "missing_target") {
|
|
1013
|
+
return {
|
|
1014
|
+
status: "blocked",
|
|
1015
|
+
detail: "teams could not resolve a routable target for that session",
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
if (result.reason === "send_error") {
|
|
1019
|
+
return {
|
|
1020
|
+
status: "failed",
|
|
1021
|
+
detail: "teams send failed",
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
return {
|
|
1025
|
+
status: "unavailable",
|
|
1026
|
+
detail: "live delivery unavailable right now; queued for the next active turn",
|
|
1027
|
+
};
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
});
|
|
1031
|
+
return renderCrossChatDeliveryStatus(`${friendId} on ${channel}/${key}`, deliveryResult);
|
|
912
1032
|
},
|
|
913
1033
|
},
|
|
914
1034
|
{
|
|
@@ -48,6 +48,7 @@ const identity_1 = require("../heart/identity");
|
|
|
48
48
|
const turn_coordinator_1 = require("../heart/turn-coordinator");
|
|
49
49
|
const context_1 = require("../mind/context");
|
|
50
50
|
const tokens_1 = require("../mind/friends/tokens");
|
|
51
|
+
const group_context_1 = require("../mind/friends/group-context");
|
|
51
52
|
const resolver_1 = require("../mind/friends/resolver");
|
|
52
53
|
const store_file_1 = require("../mind/friends/store-file");
|
|
53
54
|
const types_1 = require("../mind/friends/types");
|
|
@@ -97,6 +98,10 @@ function resolveFriendParams(event) {
|
|
|
97
98
|
channel: "bluebubbles",
|
|
98
99
|
};
|
|
99
100
|
}
|
|
101
|
+
function resolveGroupExternalId(event) {
|
|
102
|
+
const groupKey = event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.sender.externalId;
|
|
103
|
+
return `group:${groupKey}`;
|
|
104
|
+
}
|
|
100
105
|
/**
|
|
101
106
|
* Check if any participant in a group chat is a known family member.
|
|
102
107
|
* Looks up each participant handle in the friend store.
|
|
@@ -585,6 +590,16 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
|
|
|
585
590
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "already_processed" };
|
|
586
591
|
}
|
|
587
592
|
}
|
|
593
|
+
if (event.kind === "message" && event.chat.isGroup) {
|
|
594
|
+
await (0, group_context_1.upsertGroupContextParticipants)({
|
|
595
|
+
store,
|
|
596
|
+
participants: (event.chat.participantHandles ?? []).map((externalId) => ({
|
|
597
|
+
provider: "imessage-handle",
|
|
598
|
+
externalId,
|
|
599
|
+
})),
|
|
600
|
+
groupExternalId: resolveGroupExternalId(event),
|
|
601
|
+
});
|
|
602
|
+
}
|
|
588
603
|
// Build inbound user message (adapter concern: BB-specific content formatting)
|
|
589
604
|
const userMessage = {
|
|
590
605
|
role: "user",
|
|
@@ -842,21 +857,33 @@ function findImessageHandle(friend) {
|
|
|
842
857
|
}
|
|
843
858
|
return undefined;
|
|
844
859
|
}
|
|
860
|
+
function normalizeBlueBubblesSessionKey(sessionKey) {
|
|
861
|
+
const trimmed = sessionKey.trim();
|
|
862
|
+
if (trimmed.startsWith("chat_identifier_")) {
|
|
863
|
+
return `chat_identifier:${trimmed.slice("chat_identifier_".length)}`;
|
|
864
|
+
}
|
|
865
|
+
if (trimmed.startsWith("chat_")) {
|
|
866
|
+
return `chat:${trimmed.slice("chat_".length)}`;
|
|
867
|
+
}
|
|
868
|
+
return trimmed;
|
|
869
|
+
}
|
|
845
870
|
function extractChatIdentifierFromSessionKey(sessionKey) {
|
|
846
|
-
|
|
847
|
-
|
|
871
|
+
const normalizedKey = normalizeBlueBubblesSessionKey(sessionKey);
|
|
872
|
+
if (normalizedKey.startsWith("chat:")) {
|
|
873
|
+
const chatGuid = normalizedKey.slice("chat:".length).trim();
|
|
848
874
|
const parts = chatGuid.split(";");
|
|
849
875
|
return parts.length >= 3 ? parts[2]?.trim() || undefined : undefined;
|
|
850
876
|
}
|
|
851
|
-
if (
|
|
852
|
-
const identifier =
|
|
877
|
+
if (normalizedKey.startsWith("chat_identifier:")) {
|
|
878
|
+
const identifier = normalizedKey.slice("chat_identifier:".length).trim();
|
|
853
879
|
return identifier || undefined;
|
|
854
880
|
}
|
|
855
881
|
return undefined;
|
|
856
882
|
}
|
|
857
883
|
function buildChatRefForSessionKey(friend, sessionKey) {
|
|
858
|
-
|
|
859
|
-
|
|
884
|
+
const normalizedKey = normalizeBlueBubblesSessionKey(sessionKey);
|
|
885
|
+
if (normalizedKey.startsWith("chat:")) {
|
|
886
|
+
const chatGuid = normalizedKey.slice("chat:".length).trim();
|
|
860
887
|
if (!chatGuid)
|
|
861
888
|
return null;
|
|
862
889
|
return {
|
|
@@ -900,12 +927,20 @@ async function sendProactiveBlueBubblesMessageToSession(params, deps = {}) {
|
|
|
900
927
|
});
|
|
901
928
|
return { delivered: false, reason: "friend_not_found" };
|
|
902
929
|
}
|
|
903
|
-
|
|
930
|
+
const explicitCrossChatAuthorized = params.intent === "explicit_cross_chat"
|
|
931
|
+
&& types_1.TRUSTED_LEVELS.has(params.authorizingSession?.trustLevel ?? "stranger");
|
|
932
|
+
if (!explicitCrossChatAuthorized && !types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
|
|
904
933
|
(0, runtime_1.emitNervesEvent)({
|
|
905
934
|
component: "senses",
|
|
906
935
|
event: "senses.bluebubbles_proactive_trust_skip",
|
|
907
936
|
message: "proactive send skipped: trust level not allowed",
|
|
908
|
-
meta: {
|
|
937
|
+
meta: {
|
|
938
|
+
friendId: params.friendId,
|
|
939
|
+
sessionKey: params.sessionKey,
|
|
940
|
+
trustLevel: friend.trustLevel ?? "unknown",
|
|
941
|
+
intent: params.intent ?? "generic_outreach",
|
|
942
|
+
authorizingTrustLevel: params.authorizingSession?.trustLevel ?? null,
|
|
943
|
+
},
|
|
909
944
|
});
|
|
910
945
|
return { delivered: false, reason: "trust_skip" };
|
|
911
946
|
}
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -14,6 +14,7 @@ const tasks_1 = require("../repertoire/tasks");
|
|
|
14
14
|
const session_activity_1 = require("../heart/session-activity");
|
|
15
15
|
const active_work_1 = require("../heart/active-work");
|
|
16
16
|
const delegation_1 = require("../heart/delegation");
|
|
17
|
+
const target_resolution_1 = require("../heart/target-resolution");
|
|
17
18
|
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
18
19
|
const pending_1 = require("../mind/pending");
|
|
19
20
|
function emptyTaskBoard() {
|
|
@@ -137,6 +138,26 @@ async function handleInboundTurn(input) {
|
|
|
137
138
|
catch {
|
|
138
139
|
sessionActivity = [];
|
|
139
140
|
}
|
|
141
|
+
let targetCandidates = [];
|
|
142
|
+
try {
|
|
143
|
+
if (input.channel !== "inner") {
|
|
144
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
145
|
+
targetCandidates = await (0, target_resolution_1.listTargetSessionCandidates)({
|
|
146
|
+
sessionsDir: `${agentRoot}/state/sessions`,
|
|
147
|
+
friendsDir: `${agentRoot}/friends`,
|
|
148
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
149
|
+
currentSession: {
|
|
150
|
+
friendId: currentSession.friendId,
|
|
151
|
+
channel: currentSession.channel,
|
|
152
|
+
key: currentSession.key,
|
|
153
|
+
},
|
|
154
|
+
friendStore: input.friendStore,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
targetCandidates = [];
|
|
160
|
+
}
|
|
140
161
|
const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
|
|
141
162
|
currentSession,
|
|
142
163
|
currentObligation,
|
|
@@ -152,6 +173,7 @@ async function handleInboundTurn(input) {
|
|
|
152
173
|
}
|
|
153
174
|
})(),
|
|
154
175
|
friendActivity: sessionActivity,
|
|
176
|
+
targetCandidates,
|
|
155
177
|
});
|
|
156
178
|
const delegationDecision = (0, delegation_1.decideDelegation)({
|
|
157
179
|
channel: input.channel,
|