@ouro.bot/cli 0.1.0-alpha.37 → 0.1.0-alpha.38
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 +12 -0
- package/dist/heart/daemon/daemon-cli.js +359 -32
- package/dist/heart/daemon/specialist-prompt.js +2 -1
- package/dist/heart/daemon/specialist-tools.js +48 -2
- package/dist/heart/kicks.js +1 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/prompt.js +122 -2
- package/dist/repertoire/tools-base.js +193 -271
- package/dist/repertoire/tools.js +8 -26
- package/dist/senses/bluebubbles.js +178 -0
- package/dist/senses/cli.js +28 -10
- package/dist/senses/inner-dialog.js +28 -26
- package/dist/senses/teams.js +179 -0
- package/package.json +2 -1
|
@@ -35,7 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.handleBlueBubblesEvent = handleBlueBubblesEvent;
|
|
37
37
|
exports.createBlueBubblesWebhookHandler = createBlueBubblesWebhookHandler;
|
|
38
|
+
exports.drainAndSendPendingBlueBubbles = drainAndSendPendingBlueBubbles;
|
|
38
39
|
exports.startBlueBubblesApp = startBlueBubblesApp;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
39
41
|
const http = __importStar(require("node:http"));
|
|
40
42
|
const path = __importStar(require("node:path"));
|
|
41
43
|
const core_1 = require("../heart/core");
|
|
@@ -541,6 +543,182 @@ function createBlueBubblesWebhookHandler(deps = {}) {
|
|
|
541
543
|
}
|
|
542
544
|
};
|
|
543
545
|
}
|
|
546
|
+
const PROACTIVE_SEND_ALLOWED_TRUST = new Set(["family", "friend"]);
|
|
547
|
+
function findImessageHandle(friend) {
|
|
548
|
+
for (const ext of friend.externalIds) {
|
|
549
|
+
if (ext.provider === "imessage-handle" && !ext.externalId.startsWith("group:")) {
|
|
550
|
+
return ext.externalId;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
function scanPendingBlueBubblesFiles(pendingRoot) {
|
|
556
|
+
const results = [];
|
|
557
|
+
let friendIds;
|
|
558
|
+
try {
|
|
559
|
+
friendIds = fs.readdirSync(pendingRoot);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
return results;
|
|
563
|
+
}
|
|
564
|
+
for (const friendId of friendIds) {
|
|
565
|
+
const bbDir = path.join(pendingRoot, friendId, "bluebubbles");
|
|
566
|
+
let keys;
|
|
567
|
+
try {
|
|
568
|
+
keys = fs.readdirSync(bbDir);
|
|
569
|
+
}
|
|
570
|
+
catch {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
for (const key of keys) {
|
|
574
|
+
const keyDir = path.join(bbDir, key);
|
|
575
|
+
let files;
|
|
576
|
+
try {
|
|
577
|
+
files = fs.readdirSync(keyDir);
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
for (const file of files.filter((f) => f.endsWith(".json")).sort()) {
|
|
583
|
+
const filePath = path.join(keyDir, file);
|
|
584
|
+
try {
|
|
585
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
586
|
+
results.push({ friendId, key, filePath, content });
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
// skip unreadable files
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return results;
|
|
595
|
+
}
|
|
596
|
+
async function drainAndSendPendingBlueBubbles(deps = {}, pendingRoot) {
|
|
597
|
+
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
598
|
+
const root = pendingRoot ?? path.join((0, identity_1.getAgentRoot)(), "state", "pending");
|
|
599
|
+
const client = resolvedDeps.createClient();
|
|
600
|
+
const store = resolvedDeps.createFriendStore();
|
|
601
|
+
const pendingFiles = scanPendingBlueBubblesFiles(root);
|
|
602
|
+
const result = { sent: 0, skipped: 0, failed: 0 };
|
|
603
|
+
for (const { friendId, filePath, content } of pendingFiles) {
|
|
604
|
+
let parsed;
|
|
605
|
+
try {
|
|
606
|
+
parsed = JSON.parse(content);
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
result.failed++;
|
|
610
|
+
try {
|
|
611
|
+
fs.unlinkSync(filePath);
|
|
612
|
+
}
|
|
613
|
+
catch { /* ignore */ }
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
const messageText = typeof parsed.content === "string" ? parsed.content : "";
|
|
617
|
+
if (!messageText.trim()) {
|
|
618
|
+
result.skipped++;
|
|
619
|
+
try {
|
|
620
|
+
fs.unlinkSync(filePath);
|
|
621
|
+
}
|
|
622
|
+
catch { /* ignore */ }
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
let friend;
|
|
626
|
+
try {
|
|
627
|
+
friend = await store.get(friendId);
|
|
628
|
+
}
|
|
629
|
+
catch {
|
|
630
|
+
friend = null;
|
|
631
|
+
}
|
|
632
|
+
if (!friend) {
|
|
633
|
+
result.skipped++;
|
|
634
|
+
try {
|
|
635
|
+
fs.unlinkSync(filePath);
|
|
636
|
+
}
|
|
637
|
+
catch { /* ignore */ }
|
|
638
|
+
(0, runtime_1.emitNervesEvent)({
|
|
639
|
+
level: "warn",
|
|
640
|
+
component: "senses",
|
|
641
|
+
event: "senses.bluebubbles_proactive_no_friend",
|
|
642
|
+
message: "proactive send skipped: friend not found",
|
|
643
|
+
meta: { friendId },
|
|
644
|
+
});
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (!PROACTIVE_SEND_ALLOWED_TRUST.has(friend.trustLevel ?? "stranger")) {
|
|
648
|
+
result.skipped++;
|
|
649
|
+
try {
|
|
650
|
+
fs.unlinkSync(filePath);
|
|
651
|
+
}
|
|
652
|
+
catch { /* ignore */ }
|
|
653
|
+
(0, runtime_1.emitNervesEvent)({
|
|
654
|
+
component: "senses",
|
|
655
|
+
event: "senses.bluebubbles_proactive_trust_skip",
|
|
656
|
+
message: "proactive send skipped: trust level not allowed",
|
|
657
|
+
meta: { friendId, trustLevel: friend.trustLevel ?? "unknown" },
|
|
658
|
+
});
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const handle = findImessageHandle(friend);
|
|
662
|
+
if (!handle) {
|
|
663
|
+
result.skipped++;
|
|
664
|
+
try {
|
|
665
|
+
fs.unlinkSync(filePath);
|
|
666
|
+
}
|
|
667
|
+
catch { /* ignore */ }
|
|
668
|
+
(0, runtime_1.emitNervesEvent)({
|
|
669
|
+
level: "warn",
|
|
670
|
+
component: "senses",
|
|
671
|
+
event: "senses.bluebubbles_proactive_no_handle",
|
|
672
|
+
message: "proactive send skipped: no iMessage handle found",
|
|
673
|
+
meta: { friendId },
|
|
674
|
+
});
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
const chat = {
|
|
678
|
+
chatIdentifier: handle,
|
|
679
|
+
isGroup: false,
|
|
680
|
+
sessionKey: friendId,
|
|
681
|
+
sendTarget: { kind: "chat_identifier", value: handle },
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
await client.sendText({ chat, text: messageText });
|
|
685
|
+
result.sent++;
|
|
686
|
+
try {
|
|
687
|
+
fs.unlinkSync(filePath);
|
|
688
|
+
}
|
|
689
|
+
catch { /* ignore */ }
|
|
690
|
+
(0, runtime_1.emitNervesEvent)({
|
|
691
|
+
component: "senses",
|
|
692
|
+
event: "senses.bluebubbles_proactive_sent",
|
|
693
|
+
message: "proactive bluebubbles message sent",
|
|
694
|
+
meta: { friendId, handle },
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
catch (error) {
|
|
698
|
+
result.failed++;
|
|
699
|
+
(0, runtime_1.emitNervesEvent)({
|
|
700
|
+
level: "error",
|
|
701
|
+
component: "senses",
|
|
702
|
+
event: "senses.bluebubbles_proactive_send_error",
|
|
703
|
+
message: "proactive bluebubbles send failed",
|
|
704
|
+
meta: {
|
|
705
|
+
friendId,
|
|
706
|
+
handle,
|
|
707
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (result.sent > 0 || result.skipped > 0 || result.failed > 0) {
|
|
713
|
+
(0, runtime_1.emitNervesEvent)({
|
|
714
|
+
component: "senses",
|
|
715
|
+
event: "senses.bluebubbles_proactive_drain_complete",
|
|
716
|
+
message: "bluebubbles proactive drain complete",
|
|
717
|
+
meta: { sent: result.sent, skipped: result.skipped, failed: result.failed },
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
544
722
|
function startBlueBubblesApp(deps = {}) {
|
|
545
723
|
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
546
724
|
resolvedDeps.createClient();
|
package/dist/senses/cli.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MarkdownStreamer = exports.InputController = exports.Spinner = void 0;
|
|
37
|
+
exports.formatPendingPrefix = formatPendingPrefix;
|
|
37
38
|
exports.handleSigint = handleSigint;
|
|
38
39
|
exports.addHistory = addHistory;
|
|
39
40
|
exports.renderMarkdown = renderMarkdown;
|
|
@@ -65,6 +66,19 @@ const session_lock_1 = require("./session-lock");
|
|
|
65
66
|
const update_hooks_1 = require("../heart/daemon/update-hooks");
|
|
66
67
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
67
68
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
69
|
+
/**
|
|
70
|
+
* Format pending messages as content-prefix strings for injection into
|
|
71
|
+
* the next user message. Self-messages (from === agentName) become
|
|
72
|
+
* `[inner thought: {content}]`, inter-agent messages become
|
|
73
|
+
* `[message from {name}: {content}]`.
|
|
74
|
+
*/
|
|
75
|
+
function formatPendingPrefix(messages, agentName) {
|
|
76
|
+
return messages
|
|
77
|
+
.map((msg) => msg.from === agentName
|
|
78
|
+
? `[inner thought: ${msg.content}]`
|
|
79
|
+
: `[message from ${msg.from}: ${msg.content}]`)
|
|
80
|
+
.join("\n");
|
|
81
|
+
}
|
|
68
82
|
// spinner that only touches stderr, cleans up after itself
|
|
69
83
|
// exported for direct testability (stop-without-start branch)
|
|
70
84
|
class Spinner {
|
|
@@ -606,7 +620,8 @@ async function runCliSession(options) {
|
|
|
606
620
|
echoRows += Math.ceil((2 + line.length) / cols);
|
|
607
621
|
}
|
|
608
622
|
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
609
|
-
|
|
623
|
+
const prefix = options.getContentPrefix?.();
|
|
624
|
+
messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
|
|
610
625
|
addHistory(history, input);
|
|
611
626
|
currentAbort = new AbortController();
|
|
612
627
|
const traceId = (0, nerves_1.createTraceId)();
|
|
@@ -715,20 +730,18 @@ async function main(agentName, options) {
|
|
|
715
730
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
716
731
|
? existing.messages
|
|
717
732
|
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", undefined, resolvedContext) }];
|
|
718
|
-
// Pending queue drain:
|
|
733
|
+
// Pending queue drain: format as content-prefix for next user message
|
|
719
734
|
const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "cli", "session");
|
|
720
|
-
|
|
735
|
+
let pendingPrefix;
|
|
736
|
+
const drainToPrefix = () => {
|
|
721
737
|
const pending = (0, pending_1.drainPending)(pendingDir);
|
|
722
738
|
if (pending.length === 0)
|
|
723
739
|
return 0;
|
|
724
|
-
|
|
725
|
-
sessionMessages.push({ role: "user", name: "harness", content: `[proactive message from ${msg.from}]` });
|
|
726
|
-
sessionMessages.push({ role: "assistant", content: msg.content });
|
|
727
|
-
}
|
|
740
|
+
pendingPrefix = formatPendingPrefix(pending, (0, identity_1.getAgentName)());
|
|
728
741
|
return pending.length;
|
|
729
742
|
};
|
|
730
|
-
// Startup drain:
|
|
731
|
-
const startupCount =
|
|
743
|
+
// Startup drain: collect offline messages as prefix for next user message
|
|
744
|
+
const startupCount = drainToPrefix();
|
|
732
745
|
if (startupCount > 0) {
|
|
733
746
|
(0, context_1.saveSession)(sessPath, sessionMessages);
|
|
734
747
|
}
|
|
@@ -753,10 +766,15 @@ async function main(agentName, options) {
|
|
|
753
766
|
}
|
|
754
767
|
return { allowed: true };
|
|
755
768
|
},
|
|
769
|
+
getContentPrefix: () => {
|
|
770
|
+
const prefix = pendingPrefix;
|
|
771
|
+
pendingPrefix = undefined;
|
|
772
|
+
return prefix;
|
|
773
|
+
},
|
|
756
774
|
onTurnEnd: async (msgs, result) => {
|
|
757
775
|
(0, context_1.postTurn)(msgs, sessPath, result.usage);
|
|
758
776
|
await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result.usage);
|
|
759
|
-
|
|
777
|
+
drainToPrefix();
|
|
760
778
|
await (0, prompt_refresh_1.refreshSystemPrompt)(msgs, "cli", undefined, resolvedContext);
|
|
761
779
|
},
|
|
762
780
|
onNewSession: () => {
|
|
@@ -48,12 +48,13 @@ const identity_1 = require("../heart/identity");
|
|
|
48
48
|
const context_1 = require("../mind/context");
|
|
49
49
|
const prompt_1 = require("../mind/prompt");
|
|
50
50
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
51
|
+
const pending_1 = require("../mind/pending");
|
|
51
52
|
const nerves_1 = require("../nerves");
|
|
52
53
|
const runtime_1 = require("../nerves/runtime");
|
|
53
54
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
54
55
|
{
|
|
55
56
|
id: "heartbeat_checkin",
|
|
56
|
-
prompt: "
|
|
57
|
+
prompt: "...time passing. anything stirring?",
|
|
57
58
|
enabled: true,
|
|
58
59
|
},
|
|
59
60
|
];
|
|
@@ -68,19 +69,8 @@ function readAspirations(agentRoot) {
|
|
|
68
69
|
function loadInnerDialogInstincts() {
|
|
69
70
|
return [...DEFAULT_INNER_DIALOG_INSTINCTS];
|
|
70
71
|
}
|
|
71
|
-
function buildInnerDialogBootstrapMessage(
|
|
72
|
-
|
|
73
|
-
return [
|
|
74
|
-
"Inner dialog boot.",
|
|
75
|
-
"",
|
|
76
|
-
"## aspirations",
|
|
77
|
-
aspirationText,
|
|
78
|
-
"",
|
|
79
|
-
"## current state",
|
|
80
|
-
stateSummary,
|
|
81
|
-
"",
|
|
82
|
-
"Orient yourself, decide what to do next, and make meaningful progress.",
|
|
83
|
-
].join("\n");
|
|
72
|
+
function buildInnerDialogBootstrapMessage(_aspirations, _stateSummary) {
|
|
73
|
+
return "waking up. settling in.\n\nwhat needs my attention?";
|
|
84
74
|
}
|
|
85
75
|
function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
86
76
|
if (nonCanonicalPaths.length === 0)
|
|
@@ -95,17 +85,14 @@ function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
|
95
85
|
...listed,
|
|
96
86
|
].join("\n");
|
|
97
87
|
}
|
|
98
|
-
function buildInstinctUserMessage(instincts,
|
|
88
|
+
function buildInstinctUserMessage(instincts, _reason, state) {
|
|
99
89
|
const active = instincts.find((instinct) => instinct.enabled !== false) ?? DEFAULT_INNER_DIALOG_INSTINCTS[0];
|
|
100
|
-
const checkpoint = state.checkpoint?.trim()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`checkpoint: ${checkpoint}`,
|
|
107
|
-
"resume_instruction: continue from the checkpoint if still valid; otherwise revise and proceed.",
|
|
108
|
-
].join("\n");
|
|
90
|
+
const checkpoint = state.checkpoint?.trim();
|
|
91
|
+
const lines = [active.prompt];
|
|
92
|
+
if (checkpoint) {
|
|
93
|
+
lines.push(`\nlast i remember: ${checkpoint}`);
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
109
96
|
}
|
|
110
97
|
function contentToText(content) {
|
|
111
98
|
if (typeof content === "string")
|
|
@@ -176,7 +163,7 @@ async function runInnerDialogTurn(options) {
|
|
|
176
163
|
lastHeartbeatAt: now().toISOString(),
|
|
177
164
|
};
|
|
178
165
|
if (messages.length === 0) {
|
|
179
|
-
const systemPrompt = await (0, prompt_1.buildSystem)("
|
|
166
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true });
|
|
180
167
|
messages.push({ role: "system", content: systemPrompt });
|
|
181
168
|
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
182
169
|
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
@@ -194,6 +181,21 @@ async function runInnerDialogTurn(options) {
|
|
|
194
181
|
const instinctPrompt = buildInstinctUserMessage(instincts, reason, state);
|
|
195
182
|
messages.push({ role: "user", content: instinctPrompt });
|
|
196
183
|
}
|
|
184
|
+
const pendingMessages = (0, pending_1.drainPending)((0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), "self", "inner", "dialog"));
|
|
185
|
+
if (pendingMessages.length > 0) {
|
|
186
|
+
const lastUserIdx = messages.length - 1;
|
|
187
|
+
const lastUser = messages[lastUserIdx];
|
|
188
|
+
/* v8 ignore next -- defensive: all code paths push a user message before here @preserve */
|
|
189
|
+
if (lastUser?.role === "user" && typeof lastUser.content === "string") {
|
|
190
|
+
const section = pendingMessages
|
|
191
|
+
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
192
|
+
.join("\n");
|
|
193
|
+
messages[lastUserIdx] = {
|
|
194
|
+
...lastUser,
|
|
195
|
+
content: `${lastUser.content}\n\n## pending messages\n${section}`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
197
199
|
const inboxMessages = options?.drainInbox?.() ?? [];
|
|
198
200
|
if (inboxMessages.length > 0) {
|
|
199
201
|
const lastUserIdx = messages.length - 1;
|
|
@@ -211,7 +213,7 @@ async function runInnerDialogTurn(options) {
|
|
|
211
213
|
}
|
|
212
214
|
const callbacks = createInnerDialogCallbacks();
|
|
213
215
|
const traceId = (0, nerves_1.createTraceId)();
|
|
214
|
-
const result = await (0, core_1.runAgent)(messages, callbacks, "
|
|
216
|
+
const result = await (0, core_1.runAgent)(messages, callbacks, "inner", options?.signal, {
|
|
215
217
|
traceId,
|
|
216
218
|
toolChoiceRequired: true,
|
|
217
219
|
skipConfirmation: true,
|
package/dist/senses/teams.js
CHANGED
|
@@ -40,7 +40,9 @@ exports.createTeamsCallbacks = createTeamsCallbacks;
|
|
|
40
40
|
exports.resolvePendingConfirmation = resolvePendingConfirmation;
|
|
41
41
|
exports.withConversationLock = withConversationLock;
|
|
42
42
|
exports.handleTeamsMessage = handleTeamsMessage;
|
|
43
|
+
exports.drainAndSendPendingTeams = drainAndSendPendingTeams;
|
|
43
44
|
exports.startTeamsApp = startTeamsApp;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
44
46
|
const teams_apps_1 = require("@microsoft/teams.apps");
|
|
45
47
|
const teams_dev_1 = require("@microsoft/teams.dev");
|
|
46
48
|
const core_1 = require("../heart/core");
|
|
@@ -737,6 +739,183 @@ function registerBotHandlers(app, label) {
|
|
|
737
739
|
(0, runtime_1.emitNervesEvent)({ level: "error", event: "channel.app_error", component: "channels", message: `[${label}] ${msg}`, meta: {} });
|
|
738
740
|
});
|
|
739
741
|
}
|
|
742
|
+
const TEAMS_PROACTIVE_ALLOWED_TRUST = new Set(["family", "friend"]);
|
|
743
|
+
function findAadObjectId(friend) {
|
|
744
|
+
for (const ext of friend.externalIds) {
|
|
745
|
+
if (ext.provider === "aad" && !ext.externalId.startsWith("group:")) {
|
|
746
|
+
return { aadObjectId: ext.externalId, tenantId: ext.tenantId };
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return undefined;
|
|
750
|
+
}
|
|
751
|
+
function scanPendingTeamsFiles(pendingRoot) {
|
|
752
|
+
const results = [];
|
|
753
|
+
let friendIds;
|
|
754
|
+
try {
|
|
755
|
+
friendIds = fs.readdirSync(pendingRoot);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
return results;
|
|
759
|
+
}
|
|
760
|
+
for (const friendId of friendIds) {
|
|
761
|
+
const teamsDir = path.join(pendingRoot, friendId, "teams");
|
|
762
|
+
let keys;
|
|
763
|
+
try {
|
|
764
|
+
keys = fs.readdirSync(teamsDir);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
for (const key of keys) {
|
|
770
|
+
const keyDir = path.join(teamsDir, key);
|
|
771
|
+
let files;
|
|
772
|
+
try {
|
|
773
|
+
files = fs.readdirSync(keyDir);
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
for (const file of files.filter((f) => f.endsWith(".json")).sort()) {
|
|
779
|
+
const filePath = path.join(keyDir, file);
|
|
780
|
+
try {
|
|
781
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
782
|
+
results.push({ friendId, key, filePath, content });
|
|
783
|
+
}
|
|
784
|
+
catch {
|
|
785
|
+
// skip unreadable files
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return results;
|
|
791
|
+
}
|
|
792
|
+
async function drainAndSendPendingTeams(store, botApi, pendingRoot) {
|
|
793
|
+
const root = pendingRoot ?? path.join((0, identity_1.getAgentRoot)(), "state", "pending");
|
|
794
|
+
const pendingFiles = scanPendingTeamsFiles(root);
|
|
795
|
+
const result = { sent: 0, skipped: 0, failed: 0 };
|
|
796
|
+
const conversations = botApi.conversations;
|
|
797
|
+
for (const { friendId, filePath, content } of pendingFiles) {
|
|
798
|
+
let parsed;
|
|
799
|
+
try {
|
|
800
|
+
parsed = JSON.parse(content);
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
result.failed++;
|
|
804
|
+
try {
|
|
805
|
+
fs.unlinkSync(filePath);
|
|
806
|
+
}
|
|
807
|
+
catch { /* ignore */ }
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const messageText = typeof parsed.content === "string" ? parsed.content : "";
|
|
811
|
+
if (!messageText.trim()) {
|
|
812
|
+
result.skipped++;
|
|
813
|
+
try {
|
|
814
|
+
fs.unlinkSync(filePath);
|
|
815
|
+
}
|
|
816
|
+
catch { /* ignore */ }
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
let friend;
|
|
820
|
+
try {
|
|
821
|
+
friend = await store.get(friendId);
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
friend = null;
|
|
825
|
+
}
|
|
826
|
+
if (!friend) {
|
|
827
|
+
result.skipped++;
|
|
828
|
+
try {
|
|
829
|
+
fs.unlinkSync(filePath);
|
|
830
|
+
}
|
|
831
|
+
catch { /* ignore */ }
|
|
832
|
+
(0, runtime_1.emitNervesEvent)({
|
|
833
|
+
level: "warn",
|
|
834
|
+
component: "senses",
|
|
835
|
+
event: "senses.teams_proactive_no_friend",
|
|
836
|
+
message: "proactive send skipped: friend not found",
|
|
837
|
+
meta: { friendId },
|
|
838
|
+
});
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (!TEAMS_PROACTIVE_ALLOWED_TRUST.has(friend.trustLevel ?? "stranger")) {
|
|
842
|
+
result.skipped++;
|
|
843
|
+
try {
|
|
844
|
+
fs.unlinkSync(filePath);
|
|
845
|
+
}
|
|
846
|
+
catch { /* ignore */ }
|
|
847
|
+
(0, runtime_1.emitNervesEvent)({
|
|
848
|
+
component: "senses",
|
|
849
|
+
event: "senses.teams_proactive_trust_skip",
|
|
850
|
+
message: "proactive send skipped: trust level not allowed",
|
|
851
|
+
meta: { friendId, trustLevel: friend.trustLevel ?? "unknown" },
|
|
852
|
+
});
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const aadInfo = findAadObjectId(friend);
|
|
856
|
+
if (!aadInfo) {
|
|
857
|
+
result.skipped++;
|
|
858
|
+
try {
|
|
859
|
+
fs.unlinkSync(filePath);
|
|
860
|
+
}
|
|
861
|
+
catch { /* ignore */ }
|
|
862
|
+
(0, runtime_1.emitNervesEvent)({
|
|
863
|
+
level: "warn",
|
|
864
|
+
component: "senses",
|
|
865
|
+
event: "senses.teams_proactive_no_aad_id",
|
|
866
|
+
message: "proactive send skipped: no AAD object ID found",
|
|
867
|
+
meta: { friendId },
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const conversation = await conversations.create({
|
|
873
|
+
bot: { id: botApi.id },
|
|
874
|
+
members: [{ id: aadInfo.aadObjectId, role: "user", name: friend.name || aadInfo.aadObjectId }],
|
|
875
|
+
tenantId: aadInfo.tenantId,
|
|
876
|
+
isGroup: false,
|
|
877
|
+
});
|
|
878
|
+
await conversations.activities(conversation.id).create({
|
|
879
|
+
type: "message",
|
|
880
|
+
text: messageText,
|
|
881
|
+
});
|
|
882
|
+
result.sent++;
|
|
883
|
+
try {
|
|
884
|
+
fs.unlinkSync(filePath);
|
|
885
|
+
}
|
|
886
|
+
catch { /* ignore */ }
|
|
887
|
+
(0, runtime_1.emitNervesEvent)({
|
|
888
|
+
component: "senses",
|
|
889
|
+
event: "senses.teams_proactive_sent",
|
|
890
|
+
message: "proactive teams message sent",
|
|
891
|
+
meta: { friendId, aadObjectId: aadInfo.aadObjectId },
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
catch (error) {
|
|
895
|
+
result.failed++;
|
|
896
|
+
(0, runtime_1.emitNervesEvent)({
|
|
897
|
+
level: "error",
|
|
898
|
+
component: "senses",
|
|
899
|
+
event: "senses.teams_proactive_send_error",
|
|
900
|
+
message: "proactive teams send failed",
|
|
901
|
+
meta: {
|
|
902
|
+
friendId,
|
|
903
|
+
aadObjectId: aadInfo.aadObjectId,
|
|
904
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (result.sent > 0 || result.skipped > 0 || result.failed > 0) {
|
|
910
|
+
(0, runtime_1.emitNervesEvent)({
|
|
911
|
+
component: "senses",
|
|
912
|
+
event: "senses.teams_proactive_drain_complete",
|
|
913
|
+
message: "teams proactive drain complete",
|
|
914
|
+
meta: { sent: result.sent, skipped: result.skipped, failed: result.failed },
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
740
919
|
// Start the Teams app in DevtoolsPlugin mode (local dev) or Bot Service mode (real Teams).
|
|
741
920
|
// Mode is determined by getTeamsConfig().clientId.
|
|
742
921
|
// Text is always accumulated in textBuffer and flushed periodically (chunked streaming).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.38",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
36
36
|
"@microsoft/teams.apps": "^2.0.5",
|
|
37
37
|
"@microsoft/teams.dev": "^2.0.5",
|
|
38
|
+
"fast-glob": "^3.3.3",
|
|
38
39
|
"openai": "^6.27.0",
|
|
39
40
|
"semver": "^7.7.4"
|
|
40
41
|
},
|