@ouro.bot/cli 0.1.0-alpha.10 → 0.1.0-alpha.12
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +1 -0
- package/dist/heart/daemon/daemon-cli.js +216 -91
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon.js +40 -9
- package/dist/heart/daemon/ouro-path-installer.js +161 -0
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +27 -2
- package/dist/heart/daemon/specialist-prompt.js +10 -5
- package/dist/heart/daemon/specialist-session.js +34 -7
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/identity.js +63 -1
- package/dist/heart/sense-truth.js +61 -0
- package/dist/mind/prompt.js +68 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
15
15
|
* 7. Return { hatchedAgentName } -- name from hatch_agent if called
|
|
16
16
|
*/
|
|
17
17
|
async function runSpecialistSession(deps) {
|
|
18
|
-
const { providerRuntime, systemPrompt, tools, execTool, readline, callbacks, signal, kickoffMessage } = deps;
|
|
18
|
+
const { providerRuntime, systemPrompt, tools, execTool, readline, callbacks, signal, kickoffMessage, suppressInput, restoreInput, flushMarkdown, writePrompt, } = deps;
|
|
19
19
|
(0, runtime_1.emitNervesEvent)({
|
|
20
20
|
component: "daemon",
|
|
21
21
|
event: "daemon.specialist_session_start",
|
|
@@ -28,6 +28,7 @@ async function runSpecialistSession(deps) {
|
|
|
28
28
|
let hatchedAgentName = null;
|
|
29
29
|
let done = false;
|
|
30
30
|
let isFirstTurn = true;
|
|
31
|
+
let currentAbort = null;
|
|
31
32
|
try {
|
|
32
33
|
while (!done) {
|
|
33
34
|
if (signal?.aborted)
|
|
@@ -39,16 +40,25 @@ async function runSpecialistSession(deps) {
|
|
|
39
40
|
}
|
|
40
41
|
else {
|
|
41
42
|
// Get user input
|
|
42
|
-
const userInput = await readline.question("> ");
|
|
43
|
-
if (!userInput.trim())
|
|
43
|
+
const userInput = await readline.question(writePrompt ? "" : "> ");
|
|
44
|
+
if (!userInput.trim()) {
|
|
45
|
+
if (writePrompt)
|
|
46
|
+
writePrompt();
|
|
44
47
|
continue;
|
|
48
|
+
}
|
|
45
49
|
messages.push({ role: "user", content: userInput });
|
|
46
50
|
}
|
|
47
51
|
providerRuntime.resetTurnState(messages);
|
|
52
|
+
// Suppress input during model execution
|
|
53
|
+
currentAbort = new AbortController();
|
|
54
|
+
const mergedSignal = signal;
|
|
55
|
+
if (suppressInput) {
|
|
56
|
+
suppressInput(() => currentAbort.abort());
|
|
57
|
+
}
|
|
48
58
|
// Inner loop: process tool calls until we get a final_answer or plain text
|
|
49
59
|
let turnDone = false;
|
|
50
60
|
while (!turnDone) {
|
|
51
|
-
if (
|
|
61
|
+
if (mergedSignal?.aborted || currentAbort.signal.aborted) {
|
|
52
62
|
done = true;
|
|
53
63
|
break;
|
|
54
64
|
}
|
|
@@ -57,7 +67,7 @@ async function runSpecialistSession(deps) {
|
|
|
57
67
|
messages,
|
|
58
68
|
activeTools: tools,
|
|
59
69
|
callbacks,
|
|
60
|
-
signal,
|
|
70
|
+
signal: mergedSignal,
|
|
61
71
|
});
|
|
62
72
|
// Build assistant message
|
|
63
73
|
const assistantMsg = {
|
|
@@ -73,7 +83,9 @@ async function runSpecialistSession(deps) {
|
|
|
73
83
|
}));
|
|
74
84
|
}
|
|
75
85
|
if (!result.toolCalls.length) {
|
|
76
|
-
// Plain text response -- push and re-prompt
|
|
86
|
+
// Plain text response -- flush markdown, push and re-prompt
|
|
87
|
+
if (flushMarkdown)
|
|
88
|
+
flushMarkdown();
|
|
77
89
|
messages.push(assistantMsg);
|
|
78
90
|
turnDone = true;
|
|
79
91
|
continue;
|
|
@@ -96,6 +108,8 @@ async function runSpecialistSession(deps) {
|
|
|
96
108
|
}
|
|
97
109
|
if (answer != null) {
|
|
98
110
|
callbacks.onTextChunk(answer);
|
|
111
|
+
if (flushMarkdown)
|
|
112
|
+
flushMarkdown();
|
|
99
113
|
messages.push(assistantMsg);
|
|
100
114
|
done = true;
|
|
101
115
|
turnDone = true;
|
|
@@ -114,7 +128,7 @@ async function runSpecialistSession(deps) {
|
|
|
114
128
|
// Execute tool calls
|
|
115
129
|
messages.push(assistantMsg);
|
|
116
130
|
for (const tc of result.toolCalls) {
|
|
117
|
-
if (
|
|
131
|
+
if (mergedSignal?.aborted)
|
|
118
132
|
break;
|
|
119
133
|
let args = {};
|
|
120
134
|
try {
|
|
@@ -141,9 +155,22 @@ async function runSpecialistSession(deps) {
|
|
|
141
155
|
}
|
|
142
156
|
// After processing tool calls, continue inner loop for tool result processing
|
|
143
157
|
}
|
|
158
|
+
// Restore input and show prompt for next turn
|
|
159
|
+
if (flushMarkdown)
|
|
160
|
+
flushMarkdown();
|
|
161
|
+
if (restoreInput)
|
|
162
|
+
restoreInput();
|
|
163
|
+
currentAbort = null;
|
|
164
|
+
if (!done) {
|
|
165
|
+
process.stdout.write("\n\n");
|
|
166
|
+
if (writePrompt)
|
|
167
|
+
writePrompt();
|
|
168
|
+
}
|
|
144
169
|
}
|
|
145
170
|
}
|
|
146
171
|
finally {
|
|
172
|
+
if (restoreInput)
|
|
173
|
+
restoreInput();
|
|
147
174
|
readline.close();
|
|
148
175
|
}
|
|
149
176
|
return { hatchedAgentName };
|
|
@@ -55,9 +55,18 @@ function listSubagentSources(subagentsDir) {
|
|
|
55
55
|
.map((name) => path.join(subagentsDir, name))
|
|
56
56
|
.sort((a, b) => a.localeCompare(b));
|
|
57
57
|
}
|
|
58
|
+
function pathExists(target) {
|
|
59
|
+
try {
|
|
60
|
+
fs.lstatSync(target);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
58
67
|
function ensureSymlink(source, target) {
|
|
59
68
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
60
|
-
if (
|
|
69
|
+
if (pathExists(target)) {
|
|
61
70
|
const stats = fs.lstatSync(target);
|
|
62
71
|
if (stats.isSymbolicLink()) {
|
|
63
72
|
const linkedPath = fs.readlinkSync(target);
|
package/dist/heart/identity.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
36
|
+
exports.DEFAULT_AGENT_SENSES = exports.DEFAULT_AGENT_PHRASES = exports.DEFAULT_AGENT_CONTEXT = void 0;
|
|
37
37
|
exports.buildDefaultAgentTemplate = buildDefaultAgentTemplate;
|
|
38
38
|
exports.getAgentName = getAgentName;
|
|
39
39
|
exports.getRepoRoot = getRepoRoot;
|
|
@@ -57,12 +57,73 @@ exports.DEFAULT_AGENT_PHRASES = {
|
|
|
57
57
|
tool: ["running tool"],
|
|
58
58
|
followup: ["processing"],
|
|
59
59
|
};
|
|
60
|
+
exports.DEFAULT_AGENT_SENSES = {
|
|
61
|
+
cli: { enabled: true },
|
|
62
|
+
teams: { enabled: false },
|
|
63
|
+
bluebubbles: { enabled: false },
|
|
64
|
+
};
|
|
65
|
+
function normalizeSenses(value, configFile) {
|
|
66
|
+
const defaults = {
|
|
67
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
68
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
69
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
70
|
+
};
|
|
71
|
+
if (value === undefined) {
|
|
72
|
+
return defaults;
|
|
73
|
+
}
|
|
74
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
level: "error",
|
|
77
|
+
event: "config_identity.error",
|
|
78
|
+
component: "config/identity",
|
|
79
|
+
message: "agent config has invalid senses block",
|
|
80
|
+
meta: { path: configFile },
|
|
81
|
+
});
|
|
82
|
+
throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
|
|
83
|
+
}
|
|
84
|
+
const raw = value;
|
|
85
|
+
const senseNames = ["cli", "teams", "bluebubbles"];
|
|
86
|
+
for (const senseName of senseNames) {
|
|
87
|
+
const rawSense = raw[senseName];
|
|
88
|
+
if (rawSense === undefined) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
92
|
+
(0, runtime_1.emitNervesEvent)({
|
|
93
|
+
level: "error",
|
|
94
|
+
event: "config_identity.error",
|
|
95
|
+
component: "config/identity",
|
|
96
|
+
message: "agent config has invalid sense config",
|
|
97
|
+
meta: { path: configFile, sense: senseName },
|
|
98
|
+
});
|
|
99
|
+
throw new Error(`agent.json at ${configFile} has invalid senses.${senseName} config.`);
|
|
100
|
+
}
|
|
101
|
+
const enabled = rawSense.enabled;
|
|
102
|
+
if (typeof enabled !== "boolean") {
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
level: "error",
|
|
105
|
+
event: "config_identity.error",
|
|
106
|
+
component: "config/identity",
|
|
107
|
+
message: "agent config has invalid sense enabled flag",
|
|
108
|
+
meta: { path: configFile, sense: senseName, enabled: enabled ?? null },
|
|
109
|
+
});
|
|
110
|
+
throw new Error(`agent.json at ${configFile} must include senses.${senseName}.enabled as boolean.`);
|
|
111
|
+
}
|
|
112
|
+
defaults[senseName] = { enabled };
|
|
113
|
+
}
|
|
114
|
+
return defaults;
|
|
115
|
+
}
|
|
60
116
|
function buildDefaultAgentTemplate(_agentName) {
|
|
61
117
|
return {
|
|
62
118
|
version: 1,
|
|
63
119
|
enabled: true,
|
|
64
120
|
provider: "anthropic",
|
|
65
121
|
context: { ...exports.DEFAULT_AGENT_CONTEXT },
|
|
122
|
+
senses: {
|
|
123
|
+
cli: { ...exports.DEFAULT_AGENT_SENSES.cli },
|
|
124
|
+
teams: { ...exports.DEFAULT_AGENT_SENSES.teams },
|
|
125
|
+
bluebubbles: { ...exports.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
126
|
+
},
|
|
66
127
|
phrases: {
|
|
67
128
|
thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
|
|
68
129
|
tool: [...exports.DEFAULT_AGENT_PHRASES.tool],
|
|
@@ -257,6 +318,7 @@ function loadAgentConfig() {
|
|
|
257
318
|
provider: rawProvider,
|
|
258
319
|
context: parsed.context,
|
|
259
320
|
logging: parsed.logging,
|
|
321
|
+
senses: normalizeSenses(parsed.senses, configFile),
|
|
260
322
|
phrases: parsed.phrases,
|
|
261
323
|
};
|
|
262
324
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSenseInventory = getSenseInventory;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
const identity_1 = require("./identity");
|
|
6
|
+
const SENSES = [
|
|
7
|
+
{ sense: "cli", label: "CLI", daemonManaged: false },
|
|
8
|
+
{ sense: "teams", label: "Teams", daemonManaged: true },
|
|
9
|
+
{ sense: "bluebubbles", label: "BlueBubbles", daemonManaged: true },
|
|
10
|
+
];
|
|
11
|
+
function configuredSenses(senses) {
|
|
12
|
+
return senses ?? {
|
|
13
|
+
cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
|
|
14
|
+
teams: { ...identity_1.DEFAULT_AGENT_SENSES.teams },
|
|
15
|
+
bluebubbles: { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
19
|
+
if (!enabled) {
|
|
20
|
+
return "disabled";
|
|
21
|
+
}
|
|
22
|
+
if (!daemonManaged) {
|
|
23
|
+
return "interactive";
|
|
24
|
+
}
|
|
25
|
+
if (runtimeInfo?.runtime === "error") {
|
|
26
|
+
return "error";
|
|
27
|
+
}
|
|
28
|
+
if (runtimeInfo?.runtime === "running") {
|
|
29
|
+
return "running";
|
|
30
|
+
}
|
|
31
|
+
if (runtimeInfo?.configured === false) {
|
|
32
|
+
return "needs_config";
|
|
33
|
+
}
|
|
34
|
+
return "ready";
|
|
35
|
+
}
|
|
36
|
+
function getSenseInventory(agent, runtime = {}) {
|
|
37
|
+
const senses = configuredSenses(agent.senses);
|
|
38
|
+
const inventory = SENSES.map(({ sense, label, daemonManaged }) => {
|
|
39
|
+
const enabled = senses[sense].enabled;
|
|
40
|
+
return {
|
|
41
|
+
sense,
|
|
42
|
+
label,
|
|
43
|
+
enabled,
|
|
44
|
+
daemonManaged,
|
|
45
|
+
status: resolveStatus(enabled, daemonManaged, runtime[sense]),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
(0, runtime_1.emitNervesEvent)({
|
|
49
|
+
component: "channels",
|
|
50
|
+
event: "channel.sense_inventory_built",
|
|
51
|
+
message: "built sense inventory",
|
|
52
|
+
meta: {
|
|
53
|
+
senses: inventory.map((entry) => ({
|
|
54
|
+
sense: entry.sense,
|
|
55
|
+
enabled: entry.enabled,
|
|
56
|
+
status: entry.status,
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return inventory;
|
|
61
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -51,6 +51,7 @@ const first_impressions_1 = require("./first-impressions");
|
|
|
51
51
|
const tasks_1 = require("../repertoire/tasks");
|
|
52
52
|
// Lazy-loaded psyche text cache
|
|
53
53
|
let _psycheCache = null;
|
|
54
|
+
let _senseStatusLinesCache = null;
|
|
54
55
|
function loadPsycheFile(name) {
|
|
55
56
|
try {
|
|
56
57
|
const psycheDir = path.join((0, identity_1.getAgentRoot)(), "psyche");
|
|
@@ -74,6 +75,7 @@ function loadPsyche() {
|
|
|
74
75
|
}
|
|
75
76
|
function resetPsycheCache() {
|
|
76
77
|
_psycheCache = null;
|
|
78
|
+
_senseStatusLinesCache = null;
|
|
77
79
|
}
|
|
78
80
|
const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
79
81
|
function resolveFriendName(friendId, friendsDir, agentName) {
|
|
@@ -194,6 +196,7 @@ function runtimeInfoSection(channel) {
|
|
|
194
196
|
lines.push(`agent: ${agentName}`);
|
|
195
197
|
lines.push(`cwd: ${process.cwd()}`);
|
|
196
198
|
lines.push(`channel: ${channel}`);
|
|
199
|
+
lines.push(`current sense: ${channel}`);
|
|
197
200
|
lines.push(`i can read and modify my own source code.`);
|
|
198
201
|
if (channel === "cli") {
|
|
199
202
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
@@ -204,8 +207,73 @@ function runtimeInfoSection(channel) {
|
|
|
204
207
|
else {
|
|
205
208
|
lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
|
|
206
209
|
}
|
|
210
|
+
lines.push("");
|
|
211
|
+
lines.push(...senseRuntimeGuidance(channel));
|
|
207
212
|
return lines.join("\n");
|
|
208
213
|
}
|
|
214
|
+
function hasTextField(record, key) {
|
|
215
|
+
return typeof record?.[key] === "string" && record[key].trim().length > 0;
|
|
216
|
+
}
|
|
217
|
+
function localSenseStatusLines() {
|
|
218
|
+
if (_senseStatusLinesCache) {
|
|
219
|
+
return [..._senseStatusLinesCache];
|
|
220
|
+
}
|
|
221
|
+
const config = (0, identity_1.loadAgentConfig)();
|
|
222
|
+
const senses = config.senses ?? {
|
|
223
|
+
cli: { enabled: true },
|
|
224
|
+
teams: { enabled: false },
|
|
225
|
+
bluebubbles: { enabled: false },
|
|
226
|
+
};
|
|
227
|
+
let payload = {};
|
|
228
|
+
try {
|
|
229
|
+
const raw = fs.readFileSync((0, identity_1.getAgentSecretsPath)(), "utf-8");
|
|
230
|
+
const parsed = JSON.parse(raw);
|
|
231
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
232
|
+
payload = parsed;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
payload = {};
|
|
237
|
+
}
|
|
238
|
+
const teams = payload.teams;
|
|
239
|
+
const bluebubbles = payload.bluebubbles;
|
|
240
|
+
const configured = {
|
|
241
|
+
cli: true,
|
|
242
|
+
teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
|
|
243
|
+
bluebubbles: hasTextField(bluebubbles, "serverUrl") && hasTextField(bluebubbles, "password"),
|
|
244
|
+
};
|
|
245
|
+
const rows = [
|
|
246
|
+
{ label: "CLI", status: "interactive" },
|
|
247
|
+
{
|
|
248
|
+
label: "Teams",
|
|
249
|
+
status: !senses.teams.enabled ? "disabled" : configured.teams ? "ready" : "needs_config",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
label: "BlueBubbles",
|
|
253
|
+
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "needs_config",
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
_senseStatusLinesCache = rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
257
|
+
return [..._senseStatusLinesCache];
|
|
258
|
+
}
|
|
259
|
+
function senseRuntimeGuidance(channel) {
|
|
260
|
+
const lines = ["available senses:"];
|
|
261
|
+
lines.push(...localSenseStatusLines());
|
|
262
|
+
lines.push("sense states:");
|
|
263
|
+
lines.push("- interactive = available when opened by the user instead of kept running by the daemon");
|
|
264
|
+
lines.push("- disabled = turned off in agent.json");
|
|
265
|
+
lines.push("- needs_config = enabled but missing required secrets.json values");
|
|
266
|
+
lines.push("- ready = enabled and configured; `ouro up` should bring it online");
|
|
267
|
+
lines.push("- running = enabled and currently active");
|
|
268
|
+
lines.push("- error = enabled but unhealthy");
|
|
269
|
+
lines.push("If asked how to enable another sense, I explain the relevant agent.json senses entry and required secrets.json fields instead of guessing.");
|
|
270
|
+
lines.push("teams setup truth: enable `senses.teams.enabled`, then provide `teams.clientId`, `teams.clientSecret`, and `teams.tenantId` in secrets.json.");
|
|
271
|
+
lines.push("bluebubbles setup truth: enable `senses.bluebubbles.enabled`, then provide `bluebubbles.serverUrl` and `bluebubbles.password` in secrets.json.");
|
|
272
|
+
if (channel === "cli") {
|
|
273
|
+
lines.push("cli is interactive: it is available when the user opens it, not something `ouro up` daemonizes.");
|
|
274
|
+
}
|
|
275
|
+
return lines;
|
|
276
|
+
}
|
|
209
277
|
function providerSection() {
|
|
210
278
|
return `## my provider\n${(0, core_1.getProviderDisplayLabel)()}`;
|
|
211
279
|
}
|