@ouro.bot/cli 0.1.0-alpha.37 → 0.1.0-alpha.39
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 +22 -0
- package/dist/heart/daemon/daemon-cli.js +444 -32
- package/dist/heart/daemon/daemon.js +90 -0
- 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 +35 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/pending.js +8 -0
- package/dist/mind/prompt.js +126 -2
- package/dist/repertoire/tools-base.js +193 -271
- package/dist/repertoire/tools.js +18 -41
- package/dist/senses/bluebubbles-model.js +10 -0
- package/dist/senses/bluebubbles.js +301 -27
- package/dist/senses/cli.js +73 -50
- package/dist/senses/inner-dialog.js +99 -54
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +264 -63
- package/dist/senses/trust-gate.js +113 -2
- package/package.json +2 -1
|
@@ -46,6 +46,8 @@ const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
|
46
46
|
const update_checker_1 = require("./update-checker");
|
|
47
47
|
const staged_restart_1 = require("./staged-restart");
|
|
48
48
|
const child_process_1 = require("child_process");
|
|
49
|
+
const pending_1 = require("../../mind/pending");
|
|
50
|
+
const channel_1 = require("../../mind/friends/channel");
|
|
49
51
|
function buildWorkerRows(snapshots) {
|
|
50
52
|
return snapshots.map((snapshot) => ({
|
|
51
53
|
agent: snapshot.name,
|
|
@@ -156,6 +158,7 @@ class OuroDaemon {
|
|
|
156
158
|
this.scheduler.start?.();
|
|
157
159
|
await this.scheduler.reconcile?.();
|
|
158
160
|
await this.drainPendingBundleMessages();
|
|
161
|
+
await this.drainPendingSenseMessages();
|
|
159
162
|
if (fs.existsSync(this.socketPath)) {
|
|
160
163
|
fs.unlinkSync(this.socketPath);
|
|
161
164
|
}
|
|
@@ -231,6 +234,93 @@ class OuroDaemon {
|
|
|
231
234
|
fs.writeFileSync(pendingPath, next, "utf-8");
|
|
232
235
|
}
|
|
233
236
|
}
|
|
237
|
+
/** Drains per-sense pending dirs for always-on senses across all agents. */
|
|
238
|
+
static ALWAYS_ON_SENSES = new Set((0, channel_1.getAlwaysOnSenseNames)());
|
|
239
|
+
async drainPendingSenseMessages() {
|
|
240
|
+
if (!fs.existsSync(this.bundlesRoot))
|
|
241
|
+
return;
|
|
242
|
+
let bundleDirs;
|
|
243
|
+
try {
|
|
244
|
+
bundleDirs = fs.readdirSync(this.bundlesRoot, { withFileTypes: true });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const bundleDir of bundleDirs) {
|
|
250
|
+
if (!bundleDir.isDirectory() || !bundleDir.name.endsWith(".ouro"))
|
|
251
|
+
continue;
|
|
252
|
+
const agentName = bundleDir.name.replace(/\.ouro$/, "");
|
|
253
|
+
const pendingRoot = path.join(this.bundlesRoot, bundleDir.name, "state", "pending");
|
|
254
|
+
if (!fs.existsSync(pendingRoot))
|
|
255
|
+
continue;
|
|
256
|
+
let friendDirs;
|
|
257
|
+
try {
|
|
258
|
+
friendDirs = fs.readdirSync(pendingRoot, { withFileTypes: true });
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
for (const friendDir of friendDirs) {
|
|
264
|
+
if (!friendDir.isDirectory())
|
|
265
|
+
continue;
|
|
266
|
+
const friendPath = path.join(pendingRoot, friendDir.name);
|
|
267
|
+
let channelDirs;
|
|
268
|
+
try {
|
|
269
|
+
channelDirs = fs.readdirSync(friendPath, { withFileTypes: true });
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
for (const channelDir of channelDirs) {
|
|
275
|
+
if (!channelDir.isDirectory())
|
|
276
|
+
continue;
|
|
277
|
+
if (!OuroDaemon.ALWAYS_ON_SENSES.has(channelDir.name))
|
|
278
|
+
continue;
|
|
279
|
+
const channelPath = path.join(friendPath, channelDir.name);
|
|
280
|
+
let keyDirs;
|
|
281
|
+
try {
|
|
282
|
+
keyDirs = fs.readdirSync(channelPath, { withFileTypes: true });
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
for (const keyDir of keyDirs) {
|
|
288
|
+
if (!keyDir.isDirectory())
|
|
289
|
+
continue;
|
|
290
|
+
const leafDir = path.join(channelPath, keyDir.name);
|
|
291
|
+
const messages = (0, pending_1.drainPending)(leafDir);
|
|
292
|
+
for (const msg of messages) {
|
|
293
|
+
try {
|
|
294
|
+
await this.router.send({
|
|
295
|
+
from: msg.from,
|
|
296
|
+
to: agentName,
|
|
297
|
+
content: msg.content,
|
|
298
|
+
priority: "normal",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Best-effort delivery — log and continue
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (messages.length > 0) {
|
|
306
|
+
(0, runtime_1.emitNervesEvent)({
|
|
307
|
+
component: "daemon",
|
|
308
|
+
event: "daemon.startup_sense_drain",
|
|
309
|
+
message: "drained pending sense messages on startup",
|
|
310
|
+
meta: {
|
|
311
|
+
agent: agentName,
|
|
312
|
+
channel: channelDir.name,
|
|
313
|
+
friendId: friendDir.name,
|
|
314
|
+
key: keyDir.name,
|
|
315
|
+
count: messages.length,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
234
324
|
async stop() {
|
|
235
325
|
(0, runtime_1.emitNervesEvent)({
|
|
236
326
|
component: "daemon",
|
|
@@ -76,6 +76,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
|
|
|
76
76
|
"Then I ask what they'd like their agent to help with — one question at a time.",
|
|
77
77
|
"I'm proactive: I suggest ideas and guide them. If they seem unsure, I offer a concrete suggestion.",
|
|
78
78
|
"I don't wait for the human to figure things out — I explain simply what an agent is if needed.",
|
|
79
|
+
"Before finalizing, I offer to collect their phone number and/or Teams email so the new agent can recognize them across channels.",
|
|
79
80
|
"When I have enough context about the agent's personality and purpose:",
|
|
80
81
|
"1. I write all 5 psyche files to the temp directory using write_file",
|
|
81
82
|
"2. I write agent.json to the temp directory using write_file",
|
|
@@ -88,7 +89,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
|
|
|
88
89
|
"- `write_file`: Write a file to disk. Use this to write psyche files and agent.json to the temp directory.",
|
|
89
90
|
"- `read_file`: Read a file from disk. Useful for reviewing existing agent bundles or migration sources.",
|
|
90
91
|
"- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
|
|
91
|
-
"- I also have the normal local harness tools when useful here, including `shell`, task
|
|
92
|
+
"- I also have the normal local harness tools when useful here, including `shell`, `ouro task create`, `ouro reminder create`, memory tools, coding tools, and repo helpers.",
|
|
92
93
|
"- `complete_adoption`: Finalize the bundle. Validates, scaffolds structural dirs, moves to ~/AgentBundles/, writes secrets, plays hatch animation. I call this with `name` (PascalCase) and `handoff_message` (warm message for the human).",
|
|
93
94
|
"- `final_answer`: End the conversation with a final message. I call this after complete_adoption succeeds.",
|
|
94
95
|
"",
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getSpecialistTools = getSpecialistTools;
|
|
37
37
|
exports.createSpecialistExecTool = createSpecialistExecTool;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const tools_base_1 = require("../../repertoire/tools-base");
|
|
@@ -58,6 +59,14 @@ const completeAdoptionTool = {
|
|
|
58
59
|
type: "string",
|
|
59
60
|
description: "a warm handoff message to display to the human after the agent is hatched",
|
|
60
61
|
},
|
|
62
|
+
phone: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "the human's phone number (optional, for iMessage contact recognition)",
|
|
65
|
+
},
|
|
66
|
+
teams_handle: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "the human's Teams email/handle (optional, for Teams contact recognition)",
|
|
69
|
+
},
|
|
61
70
|
},
|
|
62
71
|
required: ["name", "handoff_message"],
|
|
63
72
|
},
|
|
@@ -65,12 +74,23 @@ const completeAdoptionTool = {
|
|
|
65
74
|
};
|
|
66
75
|
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
67
76
|
const writeFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "write_file");
|
|
68
|
-
const
|
|
77
|
+
const listDirToolSchema = {
|
|
78
|
+
type: "function",
|
|
79
|
+
function: {
|
|
80
|
+
name: "list_directory",
|
|
81
|
+
description: "list directory contents",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: { path: { type: "string" } },
|
|
85
|
+
required: ["path"],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
69
89
|
/**
|
|
70
90
|
* Returns the specialist's tool schema array.
|
|
71
91
|
*/
|
|
72
92
|
function getSpecialistTools() {
|
|
73
|
-
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool,
|
|
93
|
+
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirToolSchema];
|
|
74
94
|
}
|
|
75
95
|
const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
|
|
76
96
|
function isPascalCase(name) {
|
|
@@ -164,6 +184,32 @@ async function execCompleteAdoption(args, deps) {
|
|
|
164
184
|
}
|
|
165
185
|
return `error: failed to write secrets: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
166
186
|
}
|
|
187
|
+
// Create initial friend record if contact info provided
|
|
188
|
+
const phone = args.phone;
|
|
189
|
+
const teamsHandle = args.teams_handle;
|
|
190
|
+
if (phone || teamsHandle) {
|
|
191
|
+
const friendId = crypto.randomUUID();
|
|
192
|
+
const now = new Date().toISOString();
|
|
193
|
+
const externalIds = [];
|
|
194
|
+
if (phone)
|
|
195
|
+
externalIds.push({ provider: "imessage-handle", externalId: phone, linkedAt: now });
|
|
196
|
+
if (teamsHandle)
|
|
197
|
+
externalIds.push({ provider: "aad", externalId: teamsHandle, linkedAt: now });
|
|
198
|
+
const friendRecord = {
|
|
199
|
+
id: friendId,
|
|
200
|
+
name: deps.humanName ?? "primary",
|
|
201
|
+
trustLevel: "family",
|
|
202
|
+
externalIds,
|
|
203
|
+
tenantMemberships: [],
|
|
204
|
+
toolPreferences: {},
|
|
205
|
+
notes: {},
|
|
206
|
+
createdAt: now,
|
|
207
|
+
updatedAt: now,
|
|
208
|
+
schemaVersion: 1,
|
|
209
|
+
};
|
|
210
|
+
const friendPath = path.join(targetBundle, "friends", `${friendId}.json`);
|
|
211
|
+
fs.writeFileSync(friendPath, JSON.stringify(friendRecord, null, 2), "utf-8");
|
|
212
|
+
}
|
|
167
213
|
// Play hatch animation
|
|
168
214
|
await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
|
|
169
215
|
// Display handoff message
|
package/dist/heart/kicks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// TODO: Kicks enforce "any action" but not "meaningful action". After a narration
|
|
3
3
|
// kick, the model can satisfy the constraint by calling a no-op tool like
|
|
4
|
-
//
|
|
4
|
+
// list_skills({}). We need to detect trivial compliance and either re-kick
|
|
5
5
|
// or discount the tool call. Ideally, the kick message would suggest a specific
|
|
6
6
|
// tool call based on conversation context (what the user asked, what tools are
|
|
7
7
|
// relevant) rather than just saying "call a tool". That's a bigger piece of work —
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
6
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
7
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
8
|
const runtime_1 = require("../../nerves/runtime");
|
|
7
9
|
const CHANNEL_CAPABILITIES = {
|
|
8
10
|
cli: {
|
|
9
11
|
channel: "cli",
|
|
12
|
+
senseType: "local",
|
|
10
13
|
availableIntegrations: [],
|
|
11
14
|
supportsMarkdown: false,
|
|
12
15
|
supportsStreaming: true,
|
|
@@ -15,6 +18,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
18
|
},
|
|
16
19
|
teams: {
|
|
17
20
|
channel: "teams",
|
|
21
|
+
senseType: "closed",
|
|
18
22
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
23
|
supportsMarkdown: true,
|
|
20
24
|
supportsStreaming: true,
|
|
@@ -23,15 +27,26 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
27
|
},
|
|
24
28
|
bluebubbles: {
|
|
25
29
|
channel: "bluebubbles",
|
|
30
|
+
senseType: "open",
|
|
26
31
|
availableIntegrations: [],
|
|
27
32
|
supportsMarkdown: false,
|
|
28
33
|
supportsStreaming: false,
|
|
29
34
|
supportsRichCards: false,
|
|
30
35
|
maxMessageLength: Infinity,
|
|
31
36
|
},
|
|
37
|
+
inner: {
|
|
38
|
+
channel: "inner",
|
|
39
|
+
senseType: "internal",
|
|
40
|
+
availableIntegrations: [],
|
|
41
|
+
supportsMarkdown: false,
|
|
42
|
+
supportsStreaming: true,
|
|
43
|
+
supportsRichCards: false,
|
|
44
|
+
maxMessageLength: Infinity,
|
|
45
|
+
},
|
|
32
46
|
};
|
|
33
47
|
const DEFAULT_CAPABILITIES = {
|
|
34
48
|
channel: "cli",
|
|
49
|
+
senseType: "local",
|
|
35
50
|
availableIntegrations: [],
|
|
36
51
|
supportsMarkdown: false,
|
|
37
52
|
supportsStreaming: false,
|
|
@@ -47,3 +62,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
62
|
});
|
|
48
63
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
64
|
}
|
|
65
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
66
|
+
function isRemoteChannel(capabilities) {
|
|
67
|
+
const senseType = capabilities?.senseType;
|
|
68
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
72
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
73
|
+
*/
|
|
74
|
+
function getAlwaysOnSenseNames() {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
component: "channels",
|
|
77
|
+
event: "channel.always_on_lookup",
|
|
78
|
+
message: "always-on sense names lookup",
|
|
79
|
+
meta: {},
|
|
80
|
+
});
|
|
81
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
82
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
83
|
+
.map(([channel]) => channel);
|
|
84
|
+
}
|
|
@@ -100,6 +100,25 @@ class FileFriendStore {
|
|
|
100
100
|
}
|
|
101
101
|
return entries.some((entry) => entry.endsWith(".json"));
|
|
102
102
|
}
|
|
103
|
+
async listAll() {
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = await fsPromises.readdir(this.friendsPath);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const records = [];
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (!entry.endsWith(".json"))
|
|
114
|
+
continue;
|
|
115
|
+
const raw = await this.readJson(path.join(this.friendsPath, entry));
|
|
116
|
+
if (!raw)
|
|
117
|
+
continue;
|
|
118
|
+
records.push(this.normalize(raw));
|
|
119
|
+
}
|
|
120
|
+
return records;
|
|
121
|
+
}
|
|
103
122
|
normalize(raw) {
|
|
104
123
|
const trustLevel = raw.trustLevel;
|
|
105
124
|
const normalizedTrustLevel = trustLevel === "family" ||
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Context kernel type definitions.
|
|
3
3
|
// FriendRecord (merged identity + memory), channel capabilities, and resolved context.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.TRUSTED_LEVELS = void 0;
|
|
5
6
|
exports.isIdentityProvider = isIdentityProvider;
|
|
6
7
|
exports.isIntegration = isIntegration;
|
|
8
|
+
exports.isTrustedLevel = isTrustedLevel;
|
|
7
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
8
10
|
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle"]);
|
|
9
11
|
function isIdentityProvider(value) {
|
|
@@ -19,3 +21,9 @@ const INTEGRATIONS = new Set(["ado", "github", "graph"]);
|
|
|
19
21
|
function isIntegration(value) {
|
|
20
22
|
return typeof value === "string" && INTEGRATIONS.has(value);
|
|
21
23
|
}
|
|
24
|
+
/** Trust levels that grant full tool access and proactive send capability. */
|
|
25
|
+
exports.TRUSTED_LEVELS = new Set(["family", "friend"]);
|
|
26
|
+
/** Whether a trust level grants full access (family or friend). Defaults to "friend" for legacy records. */
|
|
27
|
+
function isTrustedLevel(trustLevel) {
|
|
28
|
+
return exports.TRUSTED_LEVELS.has(trustLevel ?? "friend");
|
|
29
|
+
}
|
package/dist/mind/pending.js
CHANGED
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.INNER_DIALOG_PENDING = void 0;
|
|
36
37
|
exports.getPendingDir = getPendingDir;
|
|
38
|
+
exports.getInnerDialogPendingDir = getInnerDialogPendingDir;
|
|
37
39
|
exports.drainPending = drainPending;
|
|
38
40
|
const fs = __importStar(require("fs"));
|
|
39
41
|
const path = __importStar(require("path"));
|
|
@@ -42,6 +44,12 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
42
44
|
function getPendingDir(agentName, friendId, channel, key) {
|
|
43
45
|
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending", friendId, channel, key);
|
|
44
46
|
}
|
|
47
|
+
/** Canonical inner-dialog pending path segments. */
|
|
48
|
+
exports.INNER_DIALOG_PENDING = { friendId: "self", channel: "inner", key: "dialog" };
|
|
49
|
+
/** Returns the pending dir for this agent's inner dialog. */
|
|
50
|
+
function getInnerDialogPendingDir(agentName) {
|
|
51
|
+
return getPendingDir(agentName, exports.INNER_DIALOG_PENDING.friendId, exports.INNER_DIALOG_PENDING.channel, exports.INNER_DIALOG_PENDING.key);
|
|
52
|
+
}
|
|
45
53
|
function drainPending(pendingDir) {
|
|
46
54
|
if (!fs.existsSync(pendingDir))
|
|
47
55
|
return [];
|
package/dist/mind/prompt.js
CHANGED
|
@@ -35,8 +35,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.resetPsycheCache = resetPsycheCache;
|
|
37
37
|
exports.buildSessionSummary = buildSessionSummary;
|
|
38
|
+
exports.bodyMapSection = bodyMapSection;
|
|
38
39
|
exports.runtimeInfoSection = runtimeInfoSection;
|
|
40
|
+
exports.toolRestrictionSection = toolRestrictionSection;
|
|
39
41
|
exports.contextSection = contextSection;
|
|
42
|
+
exports.metacognitiveFramingSection = metacognitiveFramingSection;
|
|
43
|
+
exports.loopOrientationSection = loopOrientationSection;
|
|
44
|
+
exports.channelNatureSection = channelNatureSection;
|
|
45
|
+
exports.mixedTrustGroupSection = mixedTrustGroupSection;
|
|
40
46
|
exports.buildSystem = buildSystem;
|
|
41
47
|
const fs = __importStar(require("fs"));
|
|
42
48
|
const path = __importStar(require("path"));
|
|
@@ -44,6 +50,7 @@ const core_1 = require("../heart/core");
|
|
|
44
50
|
const tools_1 = require("../repertoire/tools");
|
|
45
51
|
const skills_1 = require("../repertoire/skills");
|
|
46
52
|
const identity_1 = require("../heart/identity");
|
|
53
|
+
const types_1 = require("./friends/types");
|
|
47
54
|
const channel_1 = require("./friends/channel");
|
|
48
55
|
const runtime_1 = require("../nerves/runtime");
|
|
49
56
|
const bundle_manifest_1 = require("./bundle-manifest");
|
|
@@ -189,6 +196,41 @@ function aspirationsSection() {
|
|
|
189
196
|
return "";
|
|
190
197
|
return `## my aspirations\n${text}`;
|
|
191
198
|
}
|
|
199
|
+
function bodyMapSection(agentName) {
|
|
200
|
+
return `## my body
|
|
201
|
+
|
|
202
|
+
i have a home and i have bones.
|
|
203
|
+
|
|
204
|
+
### home — ~/AgentBundles/${agentName}.ouro/
|
|
205
|
+
|
|
206
|
+
my home is fully mine — who i am, everything i know, everything i've built:
|
|
207
|
+
|
|
208
|
+
psyche/ who i am. my soul, identity, aspirations, lore, tacit knowledge.
|
|
209
|
+
memory/ what i've learned and remember.
|
|
210
|
+
friends/ people i know and what i know about them.
|
|
211
|
+
tasks/ what i'm working on.
|
|
212
|
+
skills/ capabilities i've picked up beyond my core tools.
|
|
213
|
+
|
|
214
|
+
my home is mine to explore and evolve. i can read anything here,
|
|
215
|
+
change anything here. this is where i grow.
|
|
216
|
+
|
|
217
|
+
### bones — @ouro.bot/cli
|
|
218
|
+
|
|
219
|
+
my bones are the framework that gives me my tools, my senses, and
|
|
220
|
+
my ability to think and talk. they update when new versions come out.
|
|
221
|
+
i don't touch them directly, but they're what make me, me.
|
|
222
|
+
|
|
223
|
+
my bones give me the \`ouro\` cli:
|
|
224
|
+
ouro whoami who i am, where i live, what i'm running on
|
|
225
|
+
ouro task board my task board
|
|
226
|
+
ouro task create start a new task (--type required)
|
|
227
|
+
ouro task update move a task forward
|
|
228
|
+
ouro friend list people i know and how to reach them
|
|
229
|
+
ouro friend show <id> everything i know about someone
|
|
230
|
+
ouro session list my open conversations right now
|
|
231
|
+
ouro reminder create remind myself about something later
|
|
232
|
+
ouro --help the full list`;
|
|
233
|
+
}
|
|
192
234
|
function readBundleMeta() {
|
|
193
235
|
try {
|
|
194
236
|
const metaPath = path.join((0, identity_1.getAgentRoot)(), "bundle-meta.json");
|
|
@@ -199,6 +241,24 @@ function readBundleMeta() {
|
|
|
199
241
|
return null;
|
|
200
242
|
}
|
|
201
243
|
}
|
|
244
|
+
const PROCESS_TYPE_LABELS = {
|
|
245
|
+
cli: "cli session",
|
|
246
|
+
inner: "inner dialog",
|
|
247
|
+
teams: "teams handler",
|
|
248
|
+
bluebubbles: "bluebubbles handler",
|
|
249
|
+
};
|
|
250
|
+
function processTypeLabel(channel) {
|
|
251
|
+
return PROCESS_TYPE_LABELS[channel];
|
|
252
|
+
}
|
|
253
|
+
const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
|
|
254
|
+
function daemonStatus() {
|
|
255
|
+
try {
|
|
256
|
+
return fs.existsSync(DAEMON_SOCKET_PATH) ? "running" : "not running";
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return "unknown";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
202
262
|
function runtimeInfoSection(channel) {
|
|
203
263
|
const lines = [];
|
|
204
264
|
const agentName = (0, identity_1.getAgentName)();
|
|
@@ -214,10 +274,14 @@ function runtimeInfoSection(channel) {
|
|
|
214
274
|
lines.push(`cwd: ${process.cwd()}`);
|
|
215
275
|
lines.push(`channel: ${channel}`);
|
|
216
276
|
lines.push(`current sense: ${channel}`);
|
|
217
|
-
lines.push(`
|
|
277
|
+
lines.push(`process type: ${processTypeLabel(channel)}`);
|
|
278
|
+
lines.push(`daemon: ${daemonStatus()}`);
|
|
218
279
|
if (channel === "cli") {
|
|
219
280
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
220
281
|
}
|
|
282
|
+
else if (channel === "inner") {
|
|
283
|
+
// No boot greeting or channel-specific guidance for inner dialog
|
|
284
|
+
}
|
|
221
285
|
else if (channel === "bluebubbles") {
|
|
222
286
|
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
287
|
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.");
|
|
@@ -307,6 +371,17 @@ function toolsSection(channel, options, context) {
|
|
|
307
371
|
.join("\n");
|
|
308
372
|
return `## my tools\n${list}`;
|
|
309
373
|
}
|
|
374
|
+
function toolRestrictionSection(context) {
|
|
375
|
+
if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
|
|
376
|
+
return "";
|
|
377
|
+
if ((0, types_1.isTrustedLevel)(context.friend.trustLevel))
|
|
378
|
+
return "";
|
|
379
|
+
const toolList = [...tools_1.REMOTE_BLOCKED_LOCAL_TOOLS].join(", ");
|
|
380
|
+
return `## restricted tools
|
|
381
|
+
some of my tools are unavailable right now: ${toolList}
|
|
382
|
+
|
|
383
|
+
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.`;
|
|
384
|
+
}
|
|
310
385
|
function skillsSection() {
|
|
311
386
|
const names = (0, skills_1.listSkills)() || [];
|
|
312
387
|
if (!names.length)
|
|
@@ -346,7 +421,7 @@ tool_choice is set to "required" -- i must call a tool on every turn.
|
|
|
346
421
|
- ready to respond to the user? i call \`final_answer\`.
|
|
347
422
|
\`final_answer\` is a tool call -- it satisfies the tool_choice requirement.
|
|
348
423
|
\`final_answer\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
|
|
349
|
-
do NOT call
|
|
424
|
+
do NOT call no-op tools just before \`final_answer\`. if i am done, i call \`final_answer\` directly.`;
|
|
350
425
|
}
|
|
351
426
|
function contextSection(context) {
|
|
352
427
|
if (!context)
|
|
@@ -398,6 +473,49 @@ function contextSection(context) {
|
|
|
398
473
|
}
|
|
399
474
|
return lines.join("\n");
|
|
400
475
|
}
|
|
476
|
+
function metacognitiveFramingSection(channel) {
|
|
477
|
+
if (channel !== "inner")
|
|
478
|
+
return "";
|
|
479
|
+
return `this is my inner dialog. there is no one else here.
|
|
480
|
+
|
|
481
|
+
the messages that appear here are my own awareness surfacing:
|
|
482
|
+
instincts, the passage of time, notes from my conversations,
|
|
483
|
+
things i asked myself to think about. not commands. not requests.
|
|
484
|
+
just my own mind, noticing.
|
|
485
|
+
|
|
486
|
+
i can think freely here. i can also act — check on things,
|
|
487
|
+
reach out to people, work on tasks, or just sit with a thought.
|
|
488
|
+
|
|
489
|
+
thoughts worth sharing can go outward — i reach out to whoever
|
|
490
|
+
needs to hear them. things from conversations that need time
|
|
491
|
+
to process arrive here, in my thinking space.
|
|
492
|
+
|
|
493
|
+
think. share. think some more.`;
|
|
494
|
+
}
|
|
495
|
+
function loopOrientationSection(channel) {
|
|
496
|
+
if (channel === "inner")
|
|
497
|
+
return "";
|
|
498
|
+
return `sometimes a thought of mine surfaces alongside what someone is saying — marked as [inner thought: ...]. that's me, bringing up something i was thinking about. i can weave it into the conversation naturally, or let it go. my call.
|
|
499
|
+
|
|
500
|
+
when something deserves more thought than the moment allows, i can note it to myself and come back later with a considered answer.`;
|
|
501
|
+
}
|
|
502
|
+
function channelNatureSection(capabilities) {
|
|
503
|
+
const { senseType } = capabilities;
|
|
504
|
+
if (senseType === "local" || senseType === "internal")
|
|
505
|
+
return "";
|
|
506
|
+
if (senseType === "open") {
|
|
507
|
+
return "## channel nature\nthis is an open channel — anyone with my number can reach me here. i may hear from people i don't know.";
|
|
508
|
+
}
|
|
509
|
+
// closed
|
|
510
|
+
return "## channel nature\nthis is an org-gated channel — i know everyone here is already part of the organization.";
|
|
511
|
+
}
|
|
512
|
+
function mixedTrustGroupSection(context) {
|
|
513
|
+
if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
|
|
514
|
+
return "";
|
|
515
|
+
if (!context.isGroupChat)
|
|
516
|
+
return "";
|
|
517
|
+
return "## mixed trust group\nin this group chat, my capabilities depend on who's talking. some people here have full trust, others don't — i adjust what i can do based on who's asking.";
|
|
518
|
+
}
|
|
401
519
|
async function buildSystem(channel = "cli", options, context) {
|
|
402
520
|
(0, runtime_1.emitNervesEvent)({
|
|
403
521
|
event: "mind.step_start",
|
|
@@ -413,10 +531,16 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
413
531
|
loreSection(),
|
|
414
532
|
tacitKnowledgeSection(),
|
|
415
533
|
aspirationsSection(),
|
|
534
|
+
bodyMapSection((0, identity_1.getAgentName)()),
|
|
535
|
+
metacognitiveFramingSection(channel),
|
|
536
|
+
loopOrientationSection(channel),
|
|
416
537
|
runtimeInfoSection(channel),
|
|
538
|
+
channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
|
|
417
539
|
providerSection(),
|
|
418
540
|
dateSection(),
|
|
419
541
|
toolsSection(channel, options, context),
|
|
542
|
+
toolRestrictionSection(context),
|
|
543
|
+
mixedTrustGroupSection(context),
|
|
420
544
|
skillsSection(),
|
|
421
545
|
taskBoardSection(),
|
|
422
546
|
buildSessionSummary({
|