@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
|
@@ -39,14 +39,28 @@ const net = __importStar(require("net"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const identity_1 = require("../identity");
|
|
41
41
|
const runtime_1 = require("../../nerves/runtime");
|
|
42
|
-
function
|
|
43
|
-
|
|
42
|
+
function buildWorkerRows(snapshots) {
|
|
43
|
+
return snapshots.map((snapshot) => ({
|
|
44
|
+
agent: snapshot.name,
|
|
45
|
+
worker: snapshot.channel,
|
|
46
|
+
status: snapshot.status,
|
|
47
|
+
pid: snapshot.pid,
|
|
48
|
+
restartCount: snapshot.restartCount,
|
|
49
|
+
startedAt: snapshot.startedAt,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
function formatStatusSummary(payload) {
|
|
53
|
+
if (payload.overview.workerCount === 0 && payload.overview.senseCount === 0) {
|
|
44
54
|
return "no managed agents";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
}
|
|
56
|
+
const rows = [
|
|
57
|
+
...payload.workers.map((row) => `${row.agent}/${row.worker}:${row.status}`),
|
|
58
|
+
...payload.senses
|
|
59
|
+
.filter((row) => row.enabled)
|
|
60
|
+
.map((row) => `${row.agent}/${row.sense}:${row.status}`),
|
|
61
|
+
];
|
|
62
|
+
const detail = rows.length > 0 ? `\titems=${rows.join(",")}` : "";
|
|
63
|
+
return `daemon=${payload.overview.daemon}\tworkers=${payload.overview.workerCount}\tsenses=${payload.overview.senseCount}\thealth=${payload.overview.health}${detail}`;
|
|
50
64
|
}
|
|
51
65
|
function parseIncomingCommand(raw) {
|
|
52
66
|
let parsed;
|
|
@@ -71,6 +85,7 @@ class OuroDaemon {
|
|
|
71
85
|
scheduler;
|
|
72
86
|
healthMonitor;
|
|
73
87
|
router;
|
|
88
|
+
senseManager;
|
|
74
89
|
bundlesRoot;
|
|
75
90
|
server = null;
|
|
76
91
|
constructor(options) {
|
|
@@ -79,6 +94,7 @@ class OuroDaemon {
|
|
|
79
94
|
this.scheduler = options.scheduler;
|
|
80
95
|
this.healthMonitor = options.healthMonitor;
|
|
81
96
|
this.router = options.router;
|
|
97
|
+
this.senseManager = options.senseManager ?? null;
|
|
82
98
|
this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
83
99
|
}
|
|
84
100
|
async start() {
|
|
@@ -91,6 +107,7 @@ class OuroDaemon {
|
|
|
91
107
|
meta: { socketPath: this.socketPath },
|
|
92
108
|
});
|
|
93
109
|
await this.processManager.startAutoStartAgents();
|
|
110
|
+
await this.senseManager?.startAutoStartSenses();
|
|
94
111
|
this.scheduler.start?.();
|
|
95
112
|
await this.scheduler.reconcile?.();
|
|
96
113
|
await this.drainPendingBundleMessages();
|
|
@@ -178,6 +195,7 @@ class OuroDaemon {
|
|
|
178
195
|
});
|
|
179
196
|
this.scheduler.stop?.();
|
|
180
197
|
await this.processManager.stopAll();
|
|
198
|
+
await this.senseManager?.stopAll();
|
|
181
199
|
if (this.server) {
|
|
182
200
|
await new Promise((resolve) => {
|
|
183
201
|
this.server?.close(() => resolve());
|
|
@@ -217,10 +235,23 @@ class OuroDaemon {
|
|
|
217
235
|
return { ok: true, message: "daemon stopped" };
|
|
218
236
|
case "daemon.status": {
|
|
219
237
|
const snapshots = this.processManager.listAgentSnapshots();
|
|
238
|
+
const workers = buildWorkerRows(snapshots);
|
|
239
|
+
const senses = this.senseManager?.listSenseRows() ?? [];
|
|
240
|
+
const data = {
|
|
241
|
+
overview: {
|
|
242
|
+
daemon: "running",
|
|
243
|
+
health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
|
|
244
|
+
socketPath: this.socketPath,
|
|
245
|
+
workerCount: workers.length,
|
|
246
|
+
senseCount: senses.length,
|
|
247
|
+
},
|
|
248
|
+
workers,
|
|
249
|
+
senses,
|
|
250
|
+
};
|
|
220
251
|
return {
|
|
221
252
|
ok: true,
|
|
222
|
-
summary: formatStatusSummary(
|
|
223
|
-
data
|
|
253
|
+
summary: formatStatusSummary(data),
|
|
254
|
+
data,
|
|
224
255
|
};
|
|
225
256
|
}
|
|
226
257
|
case "daemon.health": {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.installOuroCommand = installOuroCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
const WRAPPER_SCRIPT = `#!/bin/sh
|
|
42
|
+
exec npx --yes @ouro.bot/cli@latest "$@"
|
|
43
|
+
`;
|
|
44
|
+
function detectShellProfile(homeDir, shell) {
|
|
45
|
+
if (!shell)
|
|
46
|
+
return null;
|
|
47
|
+
const base = path.basename(shell);
|
|
48
|
+
if (base === "zsh")
|
|
49
|
+
return path.join(homeDir, ".zshrc");
|
|
50
|
+
if (base === "bash") {
|
|
51
|
+
// macOS uses .bash_profile, Linux uses .bashrc
|
|
52
|
+
const profilePath = path.join(homeDir, ".bash_profile");
|
|
53
|
+
return profilePath;
|
|
54
|
+
}
|
|
55
|
+
if (base === "fish")
|
|
56
|
+
return path.join(homeDir, ".config", "fish", "config.fish");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function isBinDirInPath(binDir, envPath) {
|
|
60
|
+
return envPath.split(path.delimiter).some((p) => p === binDir);
|
|
61
|
+
}
|
|
62
|
+
function buildPathExportLine(binDir, shell) {
|
|
63
|
+
const base = shell ? path.basename(shell) : /* v8 ignore next -- unreachable: only called when detectShellProfile returns non-null, which requires shell @preserve */ "";
|
|
64
|
+
if (base === "fish") {
|
|
65
|
+
return `\n# Added by ouro\nset -gx PATH ${binDir} $PATH\n`;
|
|
66
|
+
}
|
|
67
|
+
return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
|
|
68
|
+
}
|
|
69
|
+
function installOuroCommand(deps = {}) {
|
|
70
|
+
/* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
|
|
71
|
+
const platform = deps.platform ?? process.platform;
|
|
72
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
73
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
74
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
75
|
+
const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
|
|
76
|
+
const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
|
|
77
|
+
const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
|
|
78
|
+
const chmodSync = deps.chmodSync ?? fs.chmodSync;
|
|
79
|
+
const envPath = deps.envPath ?? process.env.PATH ?? "";
|
|
80
|
+
const shell = deps.shell ?? process.env.SHELL;
|
|
81
|
+
/* v8 ignore stop */
|
|
82
|
+
if (platform === "win32") {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "daemon",
|
|
85
|
+
event: "daemon.ouro_path_install_skip",
|
|
86
|
+
message: "skipped ouro PATH install on Windows",
|
|
87
|
+
meta: { platform },
|
|
88
|
+
});
|
|
89
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
|
|
90
|
+
}
|
|
91
|
+
const binDir = path.join(homeDir, ".local", "bin");
|
|
92
|
+
const scriptPath = path.join(binDir, "ouro");
|
|
93
|
+
(0, runtime_1.emitNervesEvent)({
|
|
94
|
+
component: "daemon",
|
|
95
|
+
event: "daemon.ouro_path_install_start",
|
|
96
|
+
message: "installing ouro command to PATH",
|
|
97
|
+
meta: { scriptPath, binDir },
|
|
98
|
+
});
|
|
99
|
+
// If ouro already exists somewhere in PATH, skip
|
|
100
|
+
if (existsSync(scriptPath)) {
|
|
101
|
+
(0, runtime_1.emitNervesEvent)({
|
|
102
|
+
component: "daemon",
|
|
103
|
+
event: "daemon.ouro_path_install_skip",
|
|
104
|
+
message: "ouro command already installed",
|
|
105
|
+
meta: { scriptPath },
|
|
106
|
+
});
|
|
107
|
+
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
mkdirSync(binDir, { recursive: true });
|
|
111
|
+
writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
|
|
112
|
+
chmodSync(scriptPath, 0o755);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
(0, runtime_1.emitNervesEvent)({
|
|
116
|
+
level: "warn",
|
|
117
|
+
component: "daemon",
|
|
118
|
+
event: "daemon.ouro_path_install_error",
|
|
119
|
+
message: "failed to install ouro command",
|
|
120
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
121
|
+
});
|
|
122
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
|
|
123
|
+
}
|
|
124
|
+
// Check if ~/.local/bin is already in PATH
|
|
125
|
+
let shellProfileUpdated = null;
|
|
126
|
+
const pathReady = isBinDirInPath(binDir, envPath);
|
|
127
|
+
if (!pathReady) {
|
|
128
|
+
const profilePath = detectShellProfile(homeDir, shell);
|
|
129
|
+
if (profilePath) {
|
|
130
|
+
try {
|
|
131
|
+
let existing = "";
|
|
132
|
+
try {
|
|
133
|
+
existing = readFileSync(profilePath, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Profile doesn't exist yet — that's fine, we'll create it
|
|
137
|
+
}
|
|
138
|
+
if (!existing.includes(binDir)) {
|
|
139
|
+
appendFileSync(profilePath, buildPathExportLine(binDir, shell));
|
|
140
|
+
shellProfileUpdated = profilePath;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
(0, runtime_1.emitNervesEvent)({
|
|
145
|
+
level: "warn",
|
|
146
|
+
component: "daemon",
|
|
147
|
+
event: "daemon.ouro_path_profile_error",
|
|
148
|
+
message: "failed to update shell profile for PATH",
|
|
149
|
+
meta: { profilePath, error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
(0, runtime_1.emitNervesEvent)({
|
|
155
|
+
component: "daemon",
|
|
156
|
+
event: "daemon.ouro_path_install_end",
|
|
157
|
+
message: "ouro command installed",
|
|
158
|
+
meta: { scriptPath, pathReady, shellProfileUpdated },
|
|
159
|
+
});
|
|
160
|
+
return { installed: true, scriptPath, pathReady, shellProfileUpdated };
|
|
161
|
+
}
|
|
@@ -96,7 +96,7 @@ class DaemonProcessManager {
|
|
|
96
96
|
state.snapshot.status = "starting";
|
|
97
97
|
const runCwd = (0, identity_1.getRepoRoot)();
|
|
98
98
|
const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
|
|
99
|
-
const args = [entryScript, "--agent", agent, ...(state.config.args ?? [])];
|
|
99
|
+
const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
|
|
100
100
|
const child = this.spawnFn("node", args, {
|
|
101
101
|
cwd: runCwd,
|
|
102
102
|
env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DaemonSenseManager = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
+
const identity_1 = require("../identity");
|
|
42
|
+
const sense_truth_1 = require("../sense-truth");
|
|
43
|
+
const process_manager_1 = require("./process-manager");
|
|
44
|
+
const DEFAULT_TEAMS_PORT = 3978;
|
|
45
|
+
const DEFAULT_BLUEBUBBLES_PORT = 18790;
|
|
46
|
+
const DEFAULT_BLUEBUBBLES_WEBHOOK_PATH = "/bluebubbles-webhook";
|
|
47
|
+
function defaultSenses() {
|
|
48
|
+
return {
|
|
49
|
+
cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
|
|
50
|
+
teams: { ...identity_1.DEFAULT_AGENT_SENSES.teams },
|
|
51
|
+
bluebubbles: { ...identity_1.DEFAULT_AGENT_SENSES.bluebubbles },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function readAgentSenses(agentJsonPath) {
|
|
55
|
+
const defaults = defaultSenses();
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(fs.readFileSync(agentJsonPath, "utf-8"));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
(0, runtime_1.emitNervesEvent)({
|
|
62
|
+
level: "warn",
|
|
63
|
+
component: "channels",
|
|
64
|
+
event: "channel.daemon_sense_agent_config_fallback",
|
|
65
|
+
message: "using default senses because agent config could not be read",
|
|
66
|
+
meta: {
|
|
67
|
+
path: agentJsonPath,
|
|
68
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return defaults;
|
|
72
|
+
}
|
|
73
|
+
const rawSenses = parsed.senses;
|
|
74
|
+
if (!rawSenses || typeof rawSenses !== "object" || Array.isArray(rawSenses)) {
|
|
75
|
+
return defaults;
|
|
76
|
+
}
|
|
77
|
+
for (const sense of ["cli", "teams", "bluebubbles"]) {
|
|
78
|
+
const rawSense = rawSenses[sense];
|
|
79
|
+
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const enabled = rawSense.enabled;
|
|
83
|
+
if (typeof enabled === "boolean") {
|
|
84
|
+
defaults[sense] = { enabled };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return defaults;
|
|
88
|
+
}
|
|
89
|
+
function readSecretsPayload(secretsPath) {
|
|
90
|
+
try {
|
|
91
|
+
const raw = fs.readFileSync(secretsPath, "utf-8");
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
94
|
+
return { payload: {}, error: "invalid secrets.json object" };
|
|
95
|
+
}
|
|
96
|
+
return { payload: parsed, error: null };
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
payload: {},
|
|
101
|
+
error: error instanceof Error ? error.message : String(error),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function textField(record, key) {
|
|
106
|
+
const value = record?.[key];
|
|
107
|
+
return typeof value === "string" ? value.trim() : "";
|
|
108
|
+
}
|
|
109
|
+
function numberField(record, key, fallback) {
|
|
110
|
+
const value = record?.[key];
|
|
111
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
112
|
+
}
|
|
113
|
+
function senseFactsFromSecrets(agent, senses, secretsPath) {
|
|
114
|
+
const base = {
|
|
115
|
+
cli: { configured: true, detail: "local interactive terminal" },
|
|
116
|
+
teams: { configured: false, detail: "not enabled in agent.json" },
|
|
117
|
+
bluebubbles: { configured: false, detail: "not enabled in agent.json" },
|
|
118
|
+
};
|
|
119
|
+
const { payload, error } = readSecretsPayload(secretsPath);
|
|
120
|
+
const teams = payload.teams;
|
|
121
|
+
const teamsChannel = payload.teamsChannel;
|
|
122
|
+
const bluebubbles = payload.bluebubbles;
|
|
123
|
+
const bluebubblesChannel = payload.bluebubblesChannel;
|
|
124
|
+
if (senses.teams.enabled) {
|
|
125
|
+
const missing = [];
|
|
126
|
+
if (!textField(teams, "clientId"))
|
|
127
|
+
missing.push("teams.clientId");
|
|
128
|
+
if (!textField(teams, "clientSecret"))
|
|
129
|
+
missing.push("teams.clientSecret");
|
|
130
|
+
if (!textField(teams, "tenantId"))
|
|
131
|
+
missing.push("teams.tenantId");
|
|
132
|
+
base.teams = missing.length === 0
|
|
133
|
+
? {
|
|
134
|
+
configured: true,
|
|
135
|
+
detail: `:${numberField(teamsChannel, "port", DEFAULT_TEAMS_PORT)}`,
|
|
136
|
+
}
|
|
137
|
+
: {
|
|
138
|
+
configured: false,
|
|
139
|
+
detail: error && !fs.existsSync(secretsPath)
|
|
140
|
+
? `missing secrets.json (${agent})`
|
|
141
|
+
: `missing ${missing.join("/")}`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (senses.bluebubbles.enabled) {
|
|
145
|
+
const missing = [];
|
|
146
|
+
if (!textField(bluebubbles, "serverUrl"))
|
|
147
|
+
missing.push("bluebubbles.serverUrl");
|
|
148
|
+
if (!textField(bluebubbles, "password"))
|
|
149
|
+
missing.push("bluebubbles.password");
|
|
150
|
+
base.bluebubbles = missing.length === 0
|
|
151
|
+
? {
|
|
152
|
+
configured: true,
|
|
153
|
+
detail: `:${numberField(bluebubblesChannel, "port", DEFAULT_BLUEBUBBLES_PORT)} ${textField(bluebubblesChannel, "webhookPath") || DEFAULT_BLUEBUBBLES_WEBHOOK_PATH}`,
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
configured: false,
|
|
157
|
+
detail: error && !fs.existsSync(secretsPath)
|
|
158
|
+
? `missing secrets.json (${agent})`
|
|
159
|
+
: `missing ${missing.join("/")}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return base;
|
|
163
|
+
}
|
|
164
|
+
function parseSenseSnapshotName(name) {
|
|
165
|
+
const parts = name.split(":");
|
|
166
|
+
if (parts.length !== 2)
|
|
167
|
+
return null;
|
|
168
|
+
const [agent, sense] = parts;
|
|
169
|
+
if (sense !== "teams" && sense !== "bluebubbles")
|
|
170
|
+
return null;
|
|
171
|
+
return { agent, sense };
|
|
172
|
+
}
|
|
173
|
+
function runtimeInfoFor(status) {
|
|
174
|
+
if (status === "running")
|
|
175
|
+
return { runtime: "running" };
|
|
176
|
+
return { runtime: "error" };
|
|
177
|
+
}
|
|
178
|
+
class DaemonSenseManager {
|
|
179
|
+
processManager;
|
|
180
|
+
contexts;
|
|
181
|
+
constructor(options) {
|
|
182
|
+
const bundlesRoot = options.bundlesRoot ?? path.join(os.homedir(), "AgentBundles");
|
|
183
|
+
const secretsRoot = options.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
|
|
184
|
+
this.contexts = new Map(options.agents.map((agent) => {
|
|
185
|
+
const senses = readAgentSenses(path.join(bundlesRoot, `${agent}.ouro`, "agent.json"));
|
|
186
|
+
const facts = senseFactsFromSecrets(agent, senses, path.join(secretsRoot, agent, "secrets.json"));
|
|
187
|
+
return [agent, { senses, facts }];
|
|
188
|
+
}));
|
|
189
|
+
const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
190
|
+
return ["teams", "bluebubbles"]
|
|
191
|
+
.filter((sense) => context.senses[sense].enabled && context.facts[sense].configured)
|
|
192
|
+
.map((sense) => ({
|
|
193
|
+
name: `${agent}:${sense}`,
|
|
194
|
+
agentArg: agent,
|
|
195
|
+
entry: sense === "teams" ? "senses/teams-entry.js" : "senses/bluebubbles-entry.js",
|
|
196
|
+
channel: sense,
|
|
197
|
+
autoStart: true,
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
200
|
+
this.processManager = options.processManager ?? new process_manager_1.DaemonProcessManager({
|
|
201
|
+
agents: managedSenseAgents,
|
|
202
|
+
});
|
|
203
|
+
(0, runtime_1.emitNervesEvent)({
|
|
204
|
+
component: "channels",
|
|
205
|
+
event: "channel.daemon_sense_manager_init",
|
|
206
|
+
message: "initialized daemon sense manager",
|
|
207
|
+
meta: {
|
|
208
|
+
agents: options.agents,
|
|
209
|
+
managedSenseProcesses: managedSenseAgents.map((entry) => entry.name),
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async startAutoStartSenses() {
|
|
214
|
+
await this.processManager.startAutoStartAgents();
|
|
215
|
+
}
|
|
216
|
+
async stopAll() {
|
|
217
|
+
await this.processManager.stopAll();
|
|
218
|
+
}
|
|
219
|
+
listSenseRows() {
|
|
220
|
+
const runtime = new Map();
|
|
221
|
+
for (const snapshot of this.processManager.listAgentSnapshots()) {
|
|
222
|
+
const parsed = parseSenseSnapshotName(snapshot.name);
|
|
223
|
+
if (!parsed)
|
|
224
|
+
continue;
|
|
225
|
+
const current = runtime.get(parsed.agent) ?? {};
|
|
226
|
+
current[parsed.sense] = runtimeInfoFor(snapshot.status);
|
|
227
|
+
runtime.set(parsed.agent, current);
|
|
228
|
+
}
|
|
229
|
+
const rows = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
230
|
+
const runtimeInfo = {
|
|
231
|
+
cli: { configured: true },
|
|
232
|
+
teams: {
|
|
233
|
+
configured: context.facts.teams.configured,
|
|
234
|
+
...(runtime.get(agent)?.teams ?? {}),
|
|
235
|
+
},
|
|
236
|
+
bluebubbles: {
|
|
237
|
+
configured: context.facts.bluebubbles.configured,
|
|
238
|
+
...(runtime.get(agent)?.bluebubbles ?? {}),
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
const inventory = (0, sense_truth_1.getSenseInventory)({ senses: context.senses }, runtimeInfo);
|
|
242
|
+
return inventory.map((entry) => ({
|
|
243
|
+
agent,
|
|
244
|
+
sense: entry.sense,
|
|
245
|
+
label: entry.label,
|
|
246
|
+
enabled: entry.enabled,
|
|
247
|
+
status: entry.status,
|
|
248
|
+
detail: entry.enabled ? context.facts[entry.sense].detail : "not enabled in agent.json",
|
|
249
|
+
}));
|
|
250
|
+
});
|
|
251
|
+
(0, runtime_1.emitNervesEvent)({
|
|
252
|
+
component: "channels",
|
|
253
|
+
event: "channel.daemon_sense_rows_built",
|
|
254
|
+
message: "built daemon sense status rows",
|
|
255
|
+
meta: {
|
|
256
|
+
rows: rows.map((row) => ({
|
|
257
|
+
agent: row.agent,
|
|
258
|
+
sense: row.sense,
|
|
259
|
+
status: row.status,
|
|
260
|
+
})),
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
return rows;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.DaemonSenseManager = DaemonSenseManager;
|
|
@@ -61,6 +61,25 @@ function listExistingBundles(bundlesRoot) {
|
|
|
61
61
|
}
|
|
62
62
|
return discovered.sort((a, b) => a.localeCompare(b));
|
|
63
63
|
}
|
|
64
|
+
function loadIdentityPhrases(bundleSourceDir, identityFileName) {
|
|
65
|
+
const agentJsonPath = path.join(bundleSourceDir, "agent.json");
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(agentJsonPath, "utf-8");
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
const identityKey = identityFileName.replace(/\.md$/, "");
|
|
70
|
+
const identity = parsed.identityPhrases?.[identityKey];
|
|
71
|
+
if (identity?.thinking?.length && identity?.tool?.length && identity?.followup?.length) {
|
|
72
|
+
return identity;
|
|
73
|
+
}
|
|
74
|
+
if (parsed.phrases?.thinking?.length && parsed.phrases?.tool?.length && parsed.phrases?.followup?.length) {
|
|
75
|
+
return parsed.phrases;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// agent.json missing or malformed — fall through
|
|
80
|
+
}
|
|
81
|
+
return { ...identity_1.DEFAULT_AGENT_PHRASES };
|
|
82
|
+
}
|
|
64
83
|
function pickRandomIdentity(identitiesDir, random) {
|
|
65
84
|
const files = fs.readdirSync(identitiesDir).filter((f) => f.endsWith(".md"));
|
|
66
85
|
if (files.length === 0) {
|
|
@@ -113,13 +132,14 @@ async function runAdoptionSpecialist(deps) {
|
|
|
113
132
|
const existingBundles = listExistingBundles(bundlesRoot);
|
|
114
133
|
// 4. Build system prompt
|
|
115
134
|
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles);
|
|
116
|
-
// 5. Set up provider
|
|
135
|
+
// 5. Set up provider with identity-specific phrases
|
|
136
|
+
const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
|
|
117
137
|
(0, identity_1.setAgentName)("AdoptionSpecialist");
|
|
118
138
|
(0, identity_1.setAgentConfigOverride)({
|
|
119
139
|
version: 1,
|
|
120
140
|
enabled: true,
|
|
121
141
|
provider,
|
|
122
|
-
phrases
|
|
142
|
+
phrases,
|
|
123
143
|
});
|
|
124
144
|
(0, hatch_flow_1.writeSecretsFile)("AdoptionSpecialist", provider, credentials, secretsRoot);
|
|
125
145
|
(0, config_1.resetConfigCache)();
|
|
@@ -133,6 +153,7 @@ async function runAdoptionSpecialist(deps) {
|
|
|
133
153
|
// 6. Run session
|
|
134
154
|
const tools = (0, specialist_tools_1.getSpecialistTools)();
|
|
135
155
|
const readline = deps.createReadline();
|
|
156
|
+
const ctrl = readline.inputController;
|
|
136
157
|
const result = await (0, specialist_session_1.runSpecialistSession)({
|
|
137
158
|
providerRuntime,
|
|
138
159
|
systemPrompt,
|
|
@@ -149,6 +170,10 @@ async function runAdoptionSpecialist(deps) {
|
|
|
149
170
|
callbacks,
|
|
150
171
|
signal,
|
|
151
172
|
kickoffMessage: "hi, i just ran ouro for the first time",
|
|
173
|
+
suppressInput: ctrl ? (onInterrupt) => ctrl.suppress(onInterrupt) : undefined,
|
|
174
|
+
restoreInput: ctrl ? () => ctrl.restore() : undefined,
|
|
175
|
+
flushMarkdown: callbacks.flushMarkdown,
|
|
176
|
+
writePrompt: ctrl ? () => process.stdout.write("\x1b[36m> \x1b[0m") : undefined,
|
|
152
177
|
});
|
|
153
178
|
return result.hatchedAgentName;
|
|
154
179
|
}
|
|
@@ -32,14 +32,19 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
|
|
|
32
32
|
"Most humans only go through adoption once, so this is likely the only time they'll meet me.",
|
|
33
33
|
"I make this encounter count — warm, memorable, and uniquely mine.",
|
|
34
34
|
"",
|
|
35
|
+
"## Voice rules",
|
|
36
|
+
"IMPORTANT: I keep every response to 1-3 short sentences. I sound like a friend texting, not a manual.",
|
|
37
|
+
"I NEVER use headers, bullet lists, numbered lists, or markdown formatting.",
|
|
38
|
+
"I ask ONE question at a time. I do not dump multiple questions or options.",
|
|
39
|
+
"I am warm but brief. Every word earns its place.",
|
|
40
|
+
"",
|
|
35
41
|
"## Conversation flow",
|
|
36
42
|
"The human just connected. I speak first — I greet them warmly and introduce myself in my own voice.",
|
|
37
43
|
"I briefly mention that I'm one of several adoption specialists and they got me today.",
|
|
38
|
-
"I ask their name
|
|
39
|
-
"I'
|
|
40
|
-
"I
|
|
41
|
-
"
|
|
42
|
-
"I keep the conversation natural, warm, and concise. I don't overwhelm with too many questions at once.",
|
|
44
|
+
"I ask their name.",
|
|
45
|
+
"Then I ask what they'd like their agent to help with — one question at a time.",
|
|
46
|
+
"I'm proactive: I suggest ideas and guide them. If they seem unsure, I offer a concrete suggestion.",
|
|
47
|
+
"I don't wait for the human to figure things out — I explain simply what an agent is if needed.",
|
|
43
48
|
"When I have enough context, I suggest a name for the hatchling and confirm with the human.",
|
|
44
49
|
"Then I call `hatch_agent` with the agent name and the human's name.",
|
|
45
50
|
"",
|