@ouro.bot/cli 0.1.0-alpha.31 → 0.1.0-alpha.33
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/README.md +2 -5
- package/changelog.json +14 -0
- package/dist/heart/config.js +10 -9
- package/dist/heart/identity.js +11 -3
- package/dist/mind/bundle-manifest.js +1 -0
- package/dist/mind/pending.js +2 -2
- package/dist/mind/prompt.js +2 -2
- package/dist/repertoire/coding/manager.js +1 -2
- package/dist/repertoire/tools-base.js +3 -3
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools.js +16 -4
- package/dist/senses/bluebubbles-mutation-log.js +2 -2
- package/dist/senses/bluebubbles.js +115 -20
- package/dist/senses/teams.js +1 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,13 +164,10 @@ Plus 2 optional:
|
|
|
164
164
|
|
|
165
165
|
- **FriendRecord** (`friends/types.ts`): the single merged type for a person the agent knows. Contains `displayName`, `externalIds[]` (cross-provider identity links), `toolPreferences` (keyed by integration name), `notes` (general friend knowledge), `tenantMemberships`, timestamps, and schema version.
|
|
166
166
|
- **FriendStore** (`friends/store.ts`): domain-specific persistence interface (`get`, `put`, `delete`, `findByExternalId`).
|
|
167
|
-
- **FileFriendStore** (`friends/store-file.ts`):
|
|
168
|
-
- **Agent knowledge** (`{agentRoot}/friends/{uuid}.json`): id, displayName, toolPreferences, notes, timestamps, schemaVersion. Committed to the repo -- no PII.
|
|
169
|
-
- **PII bridge** (`~/.agentstate/{agentName}/friends/{uuid}.json`): id, externalIds, tenantMemberships, schemaVersion. Local-only -- contains PII.
|
|
170
|
-
- `get()` merges both backends. `put()` splits and writes both. `findByExternalId()` scans PII bridge, then merges with agent knowledge.
|
|
167
|
+
- **FileFriendStore** (`friends/store-file.ts`): bundle-local JSON storage at `{agentRoot}/friends/{uuid}.json`. Friend identity, notes, externalIds, tenantMemberships, timestamps, and schemaVersion are kept together in the bundle.
|
|
171
168
|
- **Channel** (`friends/channel.ts`): `ChannelCapabilities` -- what the current channel supports (markdown, streaming, rich cards, max message length, available integrations).
|
|
172
169
|
- **FriendResolver** (`friends/resolver.ts`): find-or-create by external ID. First encounter creates a new FriendRecord with system-provided name and empty notes/preferences. Returning friends are found via `findByExternalId()`. DisplayName is never overwritten on existing records.
|
|
173
|
-
- **Session paths**:
|
|
170
|
+
- **Session paths**: `{agentRoot}/state/sessions/{friendUuid}/{channel}/{sessionId}.json`. Each friend gets their own session directory inside the bundle-local runtime state area.
|
|
174
171
|
|
|
175
172
|
Design principles: don't persist what you can re-derive; conversation IS the cache; the model manages memory freeform via `save_friend_note`; toolPreferences go to tool descriptions (not system prompt); notes go to system prompt (not tool descriptions).
|
|
176
173
|
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.33",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Agent-owned runtime state now lives inside each bundle's `state/` directory instead of under `~/.agentstate/<agent>/...`, so sessions, logs, pending messages, coding session persistence, and BlueBubbles mutation logs stay co-located with the rest of the bundle.",
|
|
8
|
+
"Bundle-local state paths now stay durable on Azure too, and `state/` is treated as canonical bundle content while secrets continue to live in `~/.agentsecrets/<agent>/secrets.json`."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.32",
|
|
13
|
+
"changes": [
|
|
14
|
+
"BlueBubbles now treats thread-vs-top-level placement as an agent choice instead of a hard harness mirror. Slugger can deliberately stay in the current reply lane, widen back to top-level, or target another active thread when that makes the conversation flow better.",
|
|
15
|
+
"BlueBubbles turns now surface inbound lane metadata and recent active lanes from the shared chat trunk, so the model gets enough context to choose the right reply placement without splitting the conversation into separate persisted thread sessions."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.31",
|
|
6
20
|
"changes": [
|
package/dist/heart/config.js
CHANGED
|
@@ -51,11 +51,11 @@ exports.getBlueBubblesChannelConfig = getBlueBubblesChannelConfig;
|
|
|
51
51
|
exports.getIntegrationsConfig = getIntegrationsConfig;
|
|
52
52
|
exports.getOpenAIEmbeddingsApiKey = getOpenAIEmbeddingsApiKey;
|
|
53
53
|
exports.getLogsDir = getLogsDir;
|
|
54
|
+
exports.resolveSessionPath = resolveSessionPath;
|
|
54
55
|
exports.sessionPath = sessionPath;
|
|
55
56
|
exports.logPath = logPath;
|
|
56
57
|
const fs = __importStar(require("fs"));
|
|
57
58
|
const path = __importStar(require("path"));
|
|
58
|
-
const os = __importStar(require("os"));
|
|
59
59
|
const identity_1 = require("./identity");
|
|
60
60
|
const runtime_1 = require("../nerves/runtime");
|
|
61
61
|
const DEFAULT_SECRETS_TEMPLATE = {
|
|
@@ -339,20 +339,21 @@ function getOpenAIEmbeddingsApiKey() {
|
|
|
339
339
|
return getIntegrationsConfig().openaiEmbeddingsApiKey;
|
|
340
340
|
}
|
|
341
341
|
function getLogsDir() {
|
|
342
|
-
return path.join(
|
|
342
|
+
return path.join((0, identity_1.getAgentRoot)(), "state", "logs");
|
|
343
343
|
}
|
|
344
344
|
function sanitizeKey(key) {
|
|
345
345
|
return key.replace(/[/:]/g, "_");
|
|
346
346
|
}
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const dir = path.join(homeBase, ".agentstate", (0, identity_1.getAgentName)(), "sessions", friendId, channel);
|
|
353
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
347
|
+
function resolveSessionPath(friendId, channel, key, options) {
|
|
348
|
+
const dir = path.join((0, identity_1.getAgentRoot)(), "state", "sessions", friendId, channel);
|
|
349
|
+
if (options?.ensureDir) {
|
|
350
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
351
|
+
}
|
|
354
352
|
return path.join(dir, sanitizeKey(key) + ".json");
|
|
355
353
|
}
|
|
354
|
+
function sessionPath(friendId, channel, key) {
|
|
355
|
+
return resolveSessionPath(friendId, channel, key, { ensureDir: true });
|
|
356
|
+
}
|
|
356
357
|
function logPath(channel, key) {
|
|
357
358
|
return path.join(getLogsDir(), channel, sanitizeKey(key) + ".ndjson");
|
|
358
359
|
}
|
package/dist/heart/identity.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.getAgentName = getAgentName;
|
|
|
39
39
|
exports.getRepoRoot = getRepoRoot;
|
|
40
40
|
exports.getAgentBundlesRoot = getAgentBundlesRoot;
|
|
41
41
|
exports.getAgentRoot = getAgentRoot;
|
|
42
|
+
exports.getAgentStateRoot = getAgentStateRoot;
|
|
42
43
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
44
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
45
|
exports.setAgentName = setAgentName;
|
|
@@ -175,13 +176,20 @@ function getRepoRoot() {
|
|
|
175
176
|
* Returns the shared bundle root directory: `~/AgentBundles/`
|
|
176
177
|
*/
|
|
177
178
|
function getAgentBundlesRoot() {
|
|
178
|
-
|
|
179
|
+
const homeBase = process.env.WEBSITE_SITE_NAME ? "/home" : os.homedir();
|
|
180
|
+
return path.join(homeBase, "AgentBundles");
|
|
179
181
|
}
|
|
180
182
|
/**
|
|
181
183
|
* Returns the agent-specific bundle directory: `~/AgentBundles/<agentName>.ouro/`
|
|
182
184
|
*/
|
|
183
|
-
function getAgentRoot() {
|
|
184
|
-
return path.join(getAgentBundlesRoot(), `${
|
|
185
|
+
function getAgentRoot(agentName = getAgentName()) {
|
|
186
|
+
return path.join(getAgentBundlesRoot(), `${agentName}.ouro`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns the bundle-local runtime state directory: `~/AgentBundles/<agentName>.ouro/state/`
|
|
190
|
+
*/
|
|
191
|
+
function getAgentStateRoot(agentName = getAgentName()) {
|
|
192
|
+
return path.join(getAgentRoot(agentName), "state");
|
|
185
193
|
}
|
|
186
194
|
/**
|
|
187
195
|
* Returns the conventional secrets path: `~/.agentsecrets/<agentName>/secrets.json`
|
|
@@ -54,6 +54,7 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
54
54
|
{ path: "psyche/ASPIRATIONS.md", kind: "file" },
|
|
55
55
|
{ path: "psyche/memory", kind: "dir" },
|
|
56
56
|
{ path: "friends", kind: "dir" },
|
|
57
|
+
{ path: "state", kind: "dir" },
|
|
57
58
|
{ path: "tasks", kind: "dir" },
|
|
58
59
|
{ path: "skills", kind: "dir" },
|
|
59
60
|
{ path: "senses", kind: "dir" },
|
package/dist/mind/pending.js
CHANGED
|
@@ -37,10 +37,10 @@ exports.getPendingDir = getPendingDir;
|
|
|
37
37
|
exports.drainPending = drainPending;
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
-
const
|
|
40
|
+
const identity_1 = require("../heart/identity");
|
|
41
41
|
const runtime_1 = require("../nerves/runtime");
|
|
42
42
|
function getPendingDir(agentName, friendId, channel, key) {
|
|
43
|
-
return path.join(
|
|
43
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending", friendId, channel, key);
|
|
44
44
|
}
|
|
45
45
|
function drainPending(pendingDir) {
|
|
46
46
|
if (!fs.existsSync(pendingDir))
|
package/dist/mind/prompt.js
CHANGED
|
@@ -44,7 +44,6 @@ const core_1 = require("../heart/core");
|
|
|
44
44
|
const tools_1 = require("../repertoire/tools");
|
|
45
45
|
const skills_1 = require("../repertoire/skills");
|
|
46
46
|
const identity_1 = require("../heart/identity");
|
|
47
|
-
const os = __importStar(require("os"));
|
|
48
47
|
const channel_1 = require("./friends/channel");
|
|
49
48
|
const runtime_1 = require("../nerves/runtime");
|
|
50
49
|
const bundle_manifest_1 = require("./bundle-manifest");
|
|
@@ -221,6 +220,7 @@ function runtimeInfoSection(channel) {
|
|
|
221
220
|
}
|
|
222
221
|
else if (channel === "bluebubbles") {
|
|
223
222
|
lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
|
|
223
|
+
lines.push("when a bluebubbles turn arrives from a thread, the harness tells me the current lane and any recent active thread ids. if widening back to top-level or routing into a different active thread is the better move, i use bluebubbles_set_reply_target before final_answer.");
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
226
226
|
lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
|
|
@@ -420,7 +420,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
420
420
|
skillsSection(),
|
|
421
421
|
taskBoardSection(),
|
|
422
422
|
buildSessionSummary({
|
|
423
|
-
sessionsDir: path.join(
|
|
423
|
+
sessionsDir: path.join((0, identity_1.getAgentRoot)(), "state", "sessions"),
|
|
424
424
|
friendsDir: path.join((0, identity_1.getAgentRoot)(), "friends"),
|
|
425
425
|
agentName: (0, identity_1.getAgentName)(),
|
|
426
426
|
currentFriendId: context?.friend?.id,
|
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CodingSessionManager = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const identity_1 = require("../../heart/identity");
|
|
41
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -49,7 +48,7 @@ function safeAgentName() {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
function defaultStateFilePath(agentName) {
|
|
52
|
-
return path.join(
|
|
51
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "coding", "sessions.json");
|
|
53
52
|
}
|
|
54
53
|
function isPidAlive(pid) {
|
|
55
54
|
try {
|
|
@@ -41,10 +41,10 @@ const skills_1 = require("./skills");
|
|
|
41
41
|
const config_1 = require("../heart/config");
|
|
42
42
|
const runtime_1 = require("../nerves/runtime");
|
|
43
43
|
const identity_1 = require("../heart/identity");
|
|
44
|
-
const os = __importStar(require("os"));
|
|
45
44
|
const tasks_1 = require("./tasks");
|
|
46
45
|
const tools_1 = require("./coding/tools");
|
|
47
46
|
const memory_1 = require("../mind/memory");
|
|
47
|
+
const pending_1 = require("../mind/pending");
|
|
48
48
|
const postIt = (msg) => `post-it from past you:\n${msg}`;
|
|
49
49
|
function normalizeOptionalText(value) {
|
|
50
50
|
if (typeof value !== "string")
|
|
@@ -692,7 +692,7 @@ exports.baseToolDefinitions = [
|
|
|
692
692
|
const channel = args.channel;
|
|
693
693
|
const key = args.key || "session";
|
|
694
694
|
const count = parseInt(args.messageCount || "20", 10);
|
|
695
|
-
const sessFile =
|
|
695
|
+
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
696
696
|
const raw = fs.readFileSync(sessFile, "utf-8");
|
|
697
697
|
const data = JSON.parse(raw);
|
|
698
698
|
const messages = (data.messages || [])
|
|
@@ -741,7 +741,7 @@ exports.baseToolDefinitions = [
|
|
|
741
741
|
const key = args.key || "session";
|
|
742
742
|
const content = args.content;
|
|
743
743
|
const now = Date.now();
|
|
744
|
-
const pendingDir =
|
|
744
|
+
const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, channel, key);
|
|
745
745
|
fs.mkdirSync(pendingDir, { recursive: true });
|
|
746
746
|
const fileName = `${now}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
747
747
|
const filePath = path.join(pendingDir, fileName);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bluebubblesToolDefinitions = void 0;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
exports.bluebubblesToolDefinitions = [
|
|
6
|
+
{
|
|
7
|
+
tool: {
|
|
8
|
+
type: "function",
|
|
9
|
+
function: {
|
|
10
|
+
name: "bluebubbles_set_reply_target",
|
|
11
|
+
description: "choose where the current iMessage turn should land. use this when you want to widen back to top-level or route your update into a specific active thread in the same chat.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
target: {
|
|
16
|
+
type: "string",
|
|
17
|
+
enum: ["current_lane", "top_level", "thread"],
|
|
18
|
+
description: "current_lane mirrors the current inbound lane, top_level answers in the main chat, and thread targets a specific active thread.",
|
|
19
|
+
},
|
|
20
|
+
threadOriginatorGuid: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "required when target=thread; use one of the thread ids surfaced in the inbound iMessage context.",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
required: ["target"],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
handler: (args, ctx) => {
|
|
30
|
+
const target = typeof args.target === "string" ? args.target.trim() : "";
|
|
31
|
+
const controller = ctx?.bluebubblesReplyTarget;
|
|
32
|
+
if (!controller) {
|
|
33
|
+
(0, runtime_1.emitNervesEvent)({
|
|
34
|
+
level: "warn",
|
|
35
|
+
component: "tools",
|
|
36
|
+
event: "tool.error",
|
|
37
|
+
message: "bluebubbles reply target missing controller",
|
|
38
|
+
meta: { target },
|
|
39
|
+
});
|
|
40
|
+
return "bluebubbles reply targeting is not available in this context.";
|
|
41
|
+
}
|
|
42
|
+
if (target === "current_lane") {
|
|
43
|
+
const result = controller.setSelection({ target: "current_lane" });
|
|
44
|
+
(0, runtime_1.emitNervesEvent)({
|
|
45
|
+
component: "tools",
|
|
46
|
+
event: "tool.end",
|
|
47
|
+
message: "bluebubbles reply target updated",
|
|
48
|
+
meta: { target: "current_lane", success: true },
|
|
49
|
+
});
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
if (target === "top_level") {
|
|
53
|
+
const result = controller.setSelection({ target: "top_level" });
|
|
54
|
+
(0, runtime_1.emitNervesEvent)({
|
|
55
|
+
component: "tools",
|
|
56
|
+
event: "tool.end",
|
|
57
|
+
message: "bluebubbles reply target updated",
|
|
58
|
+
meta: { target: "top_level", success: true },
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (target === "thread") {
|
|
63
|
+
const threadOriginatorGuid = typeof args.threadOriginatorGuid === "string" ? args.threadOriginatorGuid.trim() : "";
|
|
64
|
+
if (!threadOriginatorGuid) {
|
|
65
|
+
(0, runtime_1.emitNervesEvent)({
|
|
66
|
+
level: "warn",
|
|
67
|
+
component: "tools",
|
|
68
|
+
event: "tool.error",
|
|
69
|
+
message: "bluebubbles reply target missing thread id",
|
|
70
|
+
meta: { target: "thread" },
|
|
71
|
+
});
|
|
72
|
+
return "threadOriginatorGuid is required when target=thread.";
|
|
73
|
+
}
|
|
74
|
+
const result = controller.setSelection({ target: "thread", threadOriginatorGuid });
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
component: "tools",
|
|
77
|
+
event: "tool.end",
|
|
78
|
+
message: "bluebubbles reply target updated",
|
|
79
|
+
meta: { target: "thread", success: true },
|
|
80
|
+
});
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
level: "warn",
|
|
85
|
+
component: "tools",
|
|
86
|
+
event: "tool.error",
|
|
87
|
+
message: "bluebubbles reply target invalid target",
|
|
88
|
+
meta: { target },
|
|
89
|
+
});
|
|
90
|
+
return "target must be one of: current_lane, top_level, thread.";
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.execTool = execTool;
|
|
|
7
7
|
exports.summarizeArgs = summarizeArgs;
|
|
8
8
|
const tools_base_1 = require("./tools-base");
|
|
9
9
|
const tools_teams_1 = require("./tools-teams");
|
|
10
|
+
const tools_bluebubbles_1 = require("./tools-bluebubbles");
|
|
10
11
|
const ado_semantic_1 = require("./ado-semantic");
|
|
11
12
|
const tools_github_1 = require("./tools-github");
|
|
12
13
|
const runtime_1 = require("../nerves/runtime");
|
|
@@ -17,7 +18,13 @@ Object.defineProperty(exports, "finalAnswerTool", { enumerable: true, get: funct
|
|
|
17
18
|
var tools_teams_2 = require("./tools-teams");
|
|
18
19
|
Object.defineProperty(exports, "teamsTools", { enumerable: true, get: function () { return tools_teams_2.teamsTools; } });
|
|
19
20
|
// All tool definitions in a single registry
|
|
20
|
-
const allDefinitions = [
|
|
21
|
+
const allDefinitions = [
|
|
22
|
+
...tools_base_1.baseToolDefinitions,
|
|
23
|
+
...tools_bluebubbles_1.bluebubblesToolDefinitions,
|
|
24
|
+
...tools_teams_1.teamsToolDefinitions,
|
|
25
|
+
...ado_semantic_1.adoSemanticToolDefinitions,
|
|
26
|
+
...tools_github_1.githubToolDefinitions,
|
|
27
|
+
];
|
|
21
28
|
const REMOTE_BLOCKED_LOCAL_TOOLS = new Set(["shell", "read_file", "write_file", "git_commit", "gh_cli"]);
|
|
22
29
|
function isRemoteChannel(capabilities) {
|
|
23
30
|
return capabilities?.channel === "teams" || capabilities?.channel === "bluebubbles";
|
|
@@ -61,15 +68,18 @@ function applyPreference(tool, pref) {
|
|
|
61
68
|
// When toolPreferences is provided, matching preferences are appended to tool descriptions.
|
|
62
69
|
function getToolsForChannel(capabilities, toolPreferences, context) {
|
|
63
70
|
const baseTools = baseToolsForCapabilities(capabilities, context);
|
|
71
|
+
const bluebubblesTools = capabilities?.channel === "bluebubbles"
|
|
72
|
+
? tools_bluebubbles_1.bluebubblesToolDefinitions.map((d) => d.tool)
|
|
73
|
+
: [];
|
|
64
74
|
if (!capabilities || capabilities.availableIntegrations.length === 0) {
|
|
65
|
-
return baseTools;
|
|
75
|
+
return [...baseTools, ...bluebubblesTools];
|
|
66
76
|
}
|
|
67
77
|
const available = new Set(capabilities.availableIntegrations);
|
|
68
78
|
const channelDefs = [...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
69
79
|
// Include tools whose integration is available, plus channel tools with no integration gate (e.g. teams_send_message)
|
|
70
80
|
const integrationDefs = channelDefs.filter((d) => d.integration ? available.has(d.integration) : capabilities.channel === "teams");
|
|
71
81
|
if (!toolPreferences || Object.keys(toolPreferences).length === 0) {
|
|
72
|
-
return [...baseTools, ...integrationDefs.map((d) => d.tool)];
|
|
82
|
+
return [...baseTools, ...bluebubblesTools, ...integrationDefs.map((d) => d.tool)];
|
|
73
83
|
}
|
|
74
84
|
// Build a map of integration -> preference text for fast lookup
|
|
75
85
|
const prefMap = new Map();
|
|
@@ -82,7 +92,7 @@ function getToolsForChannel(capabilities, toolPreferences, context) {
|
|
|
82
92
|
const pref = prefMap.get(d.integration);
|
|
83
93
|
return pref ? applyPreference(d.tool, pref) : d.tool;
|
|
84
94
|
});
|
|
85
|
-
return [...baseTools, ...enrichedIntegrationTools];
|
|
95
|
+
return [...baseTools, ...bluebubblesTools, ...enrichedIntegrationTools];
|
|
86
96
|
}
|
|
87
97
|
// Check whether a tool requires user confirmation before execution.
|
|
88
98
|
// Reads from ToolDefinition.confirmationRequired instead of a separate Set.
|
|
@@ -205,6 +215,8 @@ function summarizeArgs(name, args) {
|
|
|
205
215
|
return summarizeKeyValues(args, ["sessionId", "input"]);
|
|
206
216
|
if (name === "coding_kill")
|
|
207
217
|
return summarizeKeyValues(args, ["sessionId"]);
|
|
218
|
+
if (name === "bluebubbles_set_reply_target")
|
|
219
|
+
return summarizeKeyValues(args, ["target", "threadOriginatorGuid"]);
|
|
208
220
|
if (name === "claude")
|
|
209
221
|
return summarizeKeyValues(args, ["prompt"]);
|
|
210
222
|
if (name === "web_search")
|
|
@@ -36,14 +36,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.getBlueBubblesMutationLogPath = getBlueBubblesMutationLogPath;
|
|
37
37
|
exports.recordBlueBubblesMutation = recordBlueBubblesMutation;
|
|
38
38
|
const fs = __importStar(require("node:fs"));
|
|
39
|
-
const os = __importStar(require("node:os"));
|
|
40
39
|
const path = __importStar(require("node:path"));
|
|
41
40
|
const runtime_1 = require("../nerves/runtime");
|
|
41
|
+
const identity_1 = require("../heart/identity");
|
|
42
42
|
function sanitizeKey(key) {
|
|
43
43
|
return key.replace(/[/:]/g, "_");
|
|
44
44
|
}
|
|
45
45
|
function getBlueBubblesMutationLogPath(agentName, sessionKey) {
|
|
46
|
-
return path.join(
|
|
46
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "mutations", `${sanitizeKey(sessionKey)}.ndjson`);
|
|
47
47
|
}
|
|
48
48
|
function recordBlueBubblesMutation(agentName, event) {
|
|
49
49
|
const filePath = getBlueBubblesMutationLogPath(agentName, event.chat.sessionKey);
|
|
@@ -84,8 +84,82 @@ function resolveFriendParams(event) {
|
|
|
84
84
|
channel: "bluebubbles",
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
87
|
+
function extractMessageText(content) {
|
|
88
|
+
if (typeof content === "string")
|
|
89
|
+
return content;
|
|
90
|
+
if (!Array.isArray(content))
|
|
91
|
+
return "";
|
|
92
|
+
return content
|
|
93
|
+
.map((part) => {
|
|
94
|
+
if (part && typeof part === "object" && "type" in part && part.type === "text" && typeof part.text === "string") {
|
|
95
|
+
return part.text;
|
|
96
|
+
}
|
|
97
|
+
return "";
|
|
98
|
+
})
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join("\n");
|
|
101
|
+
}
|
|
102
|
+
function extractHistoricalLaneSummary(messages) {
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const summaries = [];
|
|
105
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
106
|
+
const message = messages[index];
|
|
107
|
+
if (message.role !== "user")
|
|
108
|
+
continue;
|
|
109
|
+
const text = extractMessageText(message.content);
|
|
110
|
+
if (!text)
|
|
111
|
+
continue;
|
|
112
|
+
const firstLine = text.split("\n")[0].trim();
|
|
113
|
+
const threadMatch = firstLine.match(/thread id: ([^\]|]+)/i);
|
|
114
|
+
const laneKey = threadMatch
|
|
115
|
+
? `thread:${threadMatch[1].trim()}`
|
|
116
|
+
: /top[-_]level/i.test(firstLine)
|
|
117
|
+
? "top_level"
|
|
118
|
+
: null;
|
|
119
|
+
if (!laneKey || seen.has(laneKey))
|
|
120
|
+
continue;
|
|
121
|
+
seen.add(laneKey);
|
|
122
|
+
const snippet = text
|
|
123
|
+
.split("\n")
|
|
124
|
+
.slice(1)
|
|
125
|
+
.map((line) => line.trim())
|
|
126
|
+
.find(Boolean)
|
|
127
|
+
?.slice(0, 80) ?? "(no recent text)";
|
|
128
|
+
summaries.push({
|
|
129
|
+
key: laneKey,
|
|
130
|
+
label: laneKey === "top_level" ? "top_level" : laneKey,
|
|
131
|
+
snippet,
|
|
132
|
+
});
|
|
133
|
+
if (summaries.length >= 5)
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
return summaries;
|
|
137
|
+
}
|
|
138
|
+
function buildConversationScopePrefix(event, existingMessages) {
|
|
139
|
+
if (event.kind !== "message") {
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
const summaries = extractHistoricalLaneSummary(existingMessages);
|
|
143
|
+
const lines = [];
|
|
144
|
+
if (event.threadOriginatorGuid?.trim()) {
|
|
145
|
+
lines.push(`[conversation scope: existing chat trunk | current inbound lane: thread | current thread id: ${event.threadOriginatorGuid.trim()} | default outbound target: current_lane]`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
lines.push("[conversation scope: existing chat trunk | current inbound lane: top_level | default outbound target: top_level]");
|
|
149
|
+
}
|
|
150
|
+
if (summaries.length > 0) {
|
|
151
|
+
lines.push("[recent active lanes]");
|
|
152
|
+
for (const summary of summaries) {
|
|
153
|
+
lines.push(`- ${summary.label}: ${summary.snippet}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (event.threadOriginatorGuid?.trim() || summaries.some((summary) => summary.key.startsWith("thread:"))) {
|
|
157
|
+
lines.push("[routing control: use bluebubbles_set_reply_target with target=top_level to widen back out, or target=thread plus a listed thread id to route into a specific active thread]");
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
function buildInboundText(event, existingMessages) {
|
|
162
|
+
const metadataPrefix = buildConversationScopePrefix(event, existingMessages);
|
|
89
163
|
const baseText = event.repairNotice?.trim()
|
|
90
164
|
? `${event.textForAgent}\n[${event.repairNotice.trim()}]`
|
|
91
165
|
: event.textForAgent;
|
|
@@ -98,17 +172,8 @@ function buildInboundText(event) {
|
|
|
98
172
|
}
|
|
99
173
|
return `${event.sender.displayName}: ${scopedText}`;
|
|
100
174
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
return "";
|
|
104
|
-
}
|
|
105
|
-
if (event.threadOriginatorGuid?.trim()) {
|
|
106
|
-
return `[conversation scope: existing chat trunk | current turn: thread reply | thread id: ${event.threadOriginatorGuid.trim()}]`;
|
|
107
|
-
}
|
|
108
|
-
return "[conversation scope: existing chat trunk | current turn: top-level]";
|
|
109
|
-
}
|
|
110
|
-
function buildInboundContent(event) {
|
|
111
|
-
const text = buildInboundText(event);
|
|
175
|
+
function buildInboundContent(event, existingMessages) {
|
|
176
|
+
const text = buildInboundText(event, existingMessages);
|
|
112
177
|
if (event.kind !== "message" || !event.inputPartsForAgent || event.inputPartsForAgent.length === 0) {
|
|
113
178
|
return text;
|
|
114
179
|
}
|
|
@@ -117,7 +182,33 @@ function buildInboundContent(event) {
|
|
|
117
182
|
...event.inputPartsForAgent,
|
|
118
183
|
];
|
|
119
184
|
}
|
|
120
|
-
function
|
|
185
|
+
function createReplyTargetController(event) {
|
|
186
|
+
let selection = event.kind === "message" && event.threadOriginatorGuid?.trim()
|
|
187
|
+
? { target: "current_lane" }
|
|
188
|
+
: { target: "top_level" };
|
|
189
|
+
return {
|
|
190
|
+
getReplyToMessageGuid() {
|
|
191
|
+
if (event.kind !== "message")
|
|
192
|
+
return undefined;
|
|
193
|
+
if (selection.target === "top_level")
|
|
194
|
+
return undefined;
|
|
195
|
+
if (selection.target === "thread")
|
|
196
|
+
return selection.threadOriginatorGuid.trim();
|
|
197
|
+
return event.threadOriginatorGuid?.trim() ? event.messageGuid : undefined;
|
|
198
|
+
},
|
|
199
|
+
setSelection(next) {
|
|
200
|
+
selection = next;
|
|
201
|
+
if (next.target === "top_level") {
|
|
202
|
+
return "bluebubbles reply target set to top_level";
|
|
203
|
+
}
|
|
204
|
+
if (next.target === "thread") {
|
|
205
|
+
return `bluebubbles reply target set to thread:${next.threadOriginatorGuid}`;
|
|
206
|
+
}
|
|
207
|
+
return "bluebubbles reply target set to current_lane";
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function createBlueBubblesCallbacks(client, chat, replyTarget) {
|
|
121
212
|
let textBuffer = "";
|
|
122
213
|
const phrases = (0, phrases_1.getPhrases)();
|
|
123
214
|
const activity = (0, debug_activity_1.createDebugActivityController)({
|
|
@@ -128,7 +219,7 @@ function createBlueBubblesCallbacks(client, chat, replyToMessageGuid) {
|
|
|
128
219
|
const sent = await client.sendText({
|
|
129
220
|
chat,
|
|
130
221
|
text,
|
|
131
|
-
replyToMessageGuid,
|
|
222
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
132
223
|
});
|
|
133
224
|
return sent.messageGuid;
|
|
134
225
|
},
|
|
@@ -136,7 +227,7 @@ function createBlueBubblesCallbacks(client, chat, replyToMessageGuid) {
|
|
|
136
227
|
await client.sendText({
|
|
137
228
|
chat,
|
|
138
229
|
text,
|
|
139
|
-
replyToMessageGuid,
|
|
230
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
140
231
|
});
|
|
141
232
|
},
|
|
142
233
|
setTyping: async (active) => {
|
|
@@ -222,7 +313,7 @@ function createBlueBubblesCallbacks(client, chat, replyToMessageGuid) {
|
|
|
222
313
|
await client.sendText({
|
|
223
314
|
chat,
|
|
224
315
|
text: trimmed,
|
|
225
|
-
replyToMessageGuid,
|
|
316
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
226
317
|
});
|
|
227
318
|
},
|
|
228
319
|
async finish() {
|
|
@@ -295,17 +386,21 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
|
295
386
|
const store = resolvedDeps.createFriendStore();
|
|
296
387
|
const resolver = resolvedDeps.createFriendResolver(store, resolveFriendParams(event));
|
|
297
388
|
const context = await resolver.resolve();
|
|
389
|
+
const replyTarget = createReplyTargetController(event);
|
|
298
390
|
const toolContext = {
|
|
299
391
|
signin: async () => undefined,
|
|
300
392
|
friendStore: store,
|
|
301
393
|
summarize: (0, core_1.createSummarize)(),
|
|
302
394
|
context,
|
|
395
|
+
bluebubblesReplyTarget: {
|
|
396
|
+
setSelection: (selection) => replyTarget.setSelection(selection),
|
|
397
|
+
},
|
|
303
398
|
codingFeedback: {
|
|
304
399
|
send: async (message) => {
|
|
305
400
|
await client.sendText({
|
|
306
401
|
chat: event.chat,
|
|
307
402
|
text: message,
|
|
308
|
-
replyToMessageGuid:
|
|
403
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
309
404
|
});
|
|
310
405
|
},
|
|
311
406
|
},
|
|
@@ -331,8 +426,8 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
|
331
426
|
const messages = existing?.messages && existing.messages.length > 0
|
|
332
427
|
? existing.messages
|
|
333
428
|
: [{ role: "system", content: await resolvedDeps.buildSystem("bluebubbles", undefined, context) }];
|
|
334
|
-
messages.push({ role: "user", content: buildInboundContent(event) });
|
|
335
|
-
const callbacks = createBlueBubblesCallbacks(client, event.chat,
|
|
429
|
+
messages.push({ role: "user", content: buildInboundContent(event, existing?.messages ?? messages) });
|
|
430
|
+
const callbacks = createBlueBubblesCallbacks(client, event.chat, replyTarget);
|
|
336
431
|
const controller = new AbortController();
|
|
337
432
|
const agentOptions = {
|
|
338
433
|
toolContext,
|
package/dist/senses/teams.js
CHANGED
|
@@ -431,12 +431,7 @@ async function withConversationLock(convId, fn) {
|
|
|
431
431
|
// Create a fresh friend store per request so mkdirSync re-runs if directories
|
|
432
432
|
// are deleted while the process is alive.
|
|
433
433
|
function getFriendStore() {
|
|
434
|
-
|
|
435
|
-
// Use /home/.agentstate/ (persistent) when WEBSITE_SITE_NAME is set.
|
|
436
|
-
/* v8 ignore next 3 -- Azure vs local path branch; environment-specific @preserve */
|
|
437
|
-
const friendsPath = process.env.WEBSITE_SITE_NAME
|
|
438
|
-
? path.join("/home", ".agentstate", (0, identity_1.getAgentName)(), "friends")
|
|
439
|
-
: path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
434
|
+
const friendsPath = path.join((0, identity_1.getAgentRoot)(), "friends");
|
|
440
435
|
return new store_file_1.FileFriendStore(friendsPath);
|
|
441
436
|
}
|
|
442
437
|
// Handle an incoming Teams message
|