@ouro.bot/cli 0.1.0-alpha.11 → 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/dist/heart/daemon/daemon-cli.js +137 -1
- 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 +1 -1
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/sense-manager.js +266 -0
- 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
|
@@ -52,6 +52,139 @@ const ouro_path_installer_1 = require("./ouro-path-installer");
|
|
|
52
52
|
const subagent_installer_1 = require("./subagent-installer");
|
|
53
53
|
const hatch_flow_1 = require("./hatch-flow");
|
|
54
54
|
const specialist_orchestrator_1 = require("./specialist-orchestrator");
|
|
55
|
+
function stringField(value) {
|
|
56
|
+
return typeof value === "string" ? value : null;
|
|
57
|
+
}
|
|
58
|
+
function numberField(value) {
|
|
59
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
60
|
+
}
|
|
61
|
+
function booleanField(value) {
|
|
62
|
+
return typeof value === "boolean" ? value : null;
|
|
63
|
+
}
|
|
64
|
+
function parseStatusPayload(data) {
|
|
65
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
66
|
+
return null;
|
|
67
|
+
const raw = data;
|
|
68
|
+
const overview = raw.overview;
|
|
69
|
+
const senses = raw.senses;
|
|
70
|
+
const workers = raw.workers;
|
|
71
|
+
if (!overview || typeof overview !== "object" || Array.isArray(overview))
|
|
72
|
+
return null;
|
|
73
|
+
if (!Array.isArray(senses) || !Array.isArray(workers))
|
|
74
|
+
return null;
|
|
75
|
+
const parsedOverview = {
|
|
76
|
+
daemon: stringField(overview.daemon) ?? "unknown",
|
|
77
|
+
health: stringField(overview.health) ?? "unknown",
|
|
78
|
+
socketPath: stringField(overview.socketPath) ?? "unknown",
|
|
79
|
+
workerCount: numberField(overview.workerCount) ?? 0,
|
|
80
|
+
senseCount: numberField(overview.senseCount) ?? 0,
|
|
81
|
+
};
|
|
82
|
+
const parsedSenses = senses.map((entry) => {
|
|
83
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
84
|
+
return null;
|
|
85
|
+
const row = entry;
|
|
86
|
+
const agent = stringField(row.agent);
|
|
87
|
+
const sense = stringField(row.sense);
|
|
88
|
+
const status = stringField(row.status);
|
|
89
|
+
const detail = stringField(row.detail);
|
|
90
|
+
const enabled = booleanField(row.enabled);
|
|
91
|
+
if (!agent || !sense || !status || detail === null || enabled === null)
|
|
92
|
+
return null;
|
|
93
|
+
return {
|
|
94
|
+
agent,
|
|
95
|
+
sense,
|
|
96
|
+
label: stringField(row.label) ?? undefined,
|
|
97
|
+
enabled,
|
|
98
|
+
status,
|
|
99
|
+
detail,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
const parsedWorkers = workers.map((entry) => {
|
|
103
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
104
|
+
return null;
|
|
105
|
+
const row = entry;
|
|
106
|
+
const agent = stringField(row.agent);
|
|
107
|
+
const worker = stringField(row.worker);
|
|
108
|
+
const status = stringField(row.status);
|
|
109
|
+
const restartCount = numberField(row.restartCount);
|
|
110
|
+
const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
|
|
111
|
+
const pid = row.pid === null ? null : numberField(row.pid);
|
|
112
|
+
const pidInvalid = !hasPid || (row.pid !== null && pid === null);
|
|
113
|
+
if (!agent || !worker || !status || restartCount === null || pidInvalid)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
agent,
|
|
117
|
+
worker,
|
|
118
|
+
status,
|
|
119
|
+
pid,
|
|
120
|
+
restartCount,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
|
|
124
|
+
return null;
|
|
125
|
+
return {
|
|
126
|
+
overview: parsedOverview,
|
|
127
|
+
senses: parsedSenses,
|
|
128
|
+
workers: parsedWorkers,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function humanizeSenseName(sense, label) {
|
|
132
|
+
if (label)
|
|
133
|
+
return label;
|
|
134
|
+
if (sense === "cli")
|
|
135
|
+
return "CLI";
|
|
136
|
+
if (sense === "bluebubbles")
|
|
137
|
+
return "BlueBubbles";
|
|
138
|
+
if (sense === "teams")
|
|
139
|
+
return "Teams";
|
|
140
|
+
return sense;
|
|
141
|
+
}
|
|
142
|
+
function formatTable(headers, rows) {
|
|
143
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
|
|
144
|
+
const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
|
|
145
|
+
const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
|
|
146
|
+
return [
|
|
147
|
+
renderRow(headers),
|
|
148
|
+
divider,
|
|
149
|
+
...rows.map(renderRow),
|
|
150
|
+
].join("\n");
|
|
151
|
+
}
|
|
152
|
+
function formatDaemonStatusOutput(response, fallback) {
|
|
153
|
+
const payload = parseStatusPayload(response.data);
|
|
154
|
+
if (!payload)
|
|
155
|
+
return fallback;
|
|
156
|
+
const overviewRows = [
|
|
157
|
+
["Daemon", payload.overview.daemon],
|
|
158
|
+
["Socket", payload.overview.socketPath],
|
|
159
|
+
["Workers", String(payload.overview.workerCount)],
|
|
160
|
+
["Senses", String(payload.overview.senseCount)],
|
|
161
|
+
["Health", payload.overview.health],
|
|
162
|
+
];
|
|
163
|
+
const senseRows = payload.senses.map((row) => [
|
|
164
|
+
row.agent,
|
|
165
|
+
humanizeSenseName(row.sense, row.label),
|
|
166
|
+
row.enabled ? "ON" : "OFF",
|
|
167
|
+
row.status,
|
|
168
|
+
row.detail,
|
|
169
|
+
]);
|
|
170
|
+
const workerRows = payload.workers.map((row) => [
|
|
171
|
+
row.agent,
|
|
172
|
+
row.worker,
|
|
173
|
+
row.status,
|
|
174
|
+
row.pid === null ? "n/a" : String(row.pid),
|
|
175
|
+
String(row.restartCount),
|
|
176
|
+
]);
|
|
177
|
+
return [
|
|
178
|
+
"Overview",
|
|
179
|
+
formatTable(["Item", "Value"], overviewRows),
|
|
180
|
+
"",
|
|
181
|
+
"Senses",
|
|
182
|
+
formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
|
|
183
|
+
"",
|
|
184
|
+
"Workers",
|
|
185
|
+
formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
|
|
186
|
+
].join("\n");
|
|
187
|
+
}
|
|
55
188
|
async function ensureDaemonRunning(deps) {
|
|
56
189
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
57
190
|
if (alive) {
|
|
@@ -867,7 +1000,10 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
867
1000
|
}
|
|
868
1001
|
throw error;
|
|
869
1002
|
}
|
|
870
|
-
const
|
|
1003
|
+
const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
|
|
1004
|
+
const message = command.kind === "daemon.status"
|
|
1005
|
+
? formatDaemonStatusOutput(response, fallbackMessage)
|
|
1006
|
+
: fallbackMessage;
|
|
871
1007
|
deps.writeStdout(message);
|
|
872
1008
|
return message;
|
|
873
1009
|
}
|
|
@@ -8,6 +8,7 @@ const message_router_1 = require("./message-router");
|
|
|
8
8
|
const health_monitor_1 = require("./health-monitor");
|
|
9
9
|
const task_scheduler_1 = require("./task-scheduler");
|
|
10
10
|
const runtime_logging_1 = require("./runtime-logging");
|
|
11
|
+
const sense_manager_1 = require("./sense-manager");
|
|
11
12
|
function parseSocketPath(argv) {
|
|
12
13
|
const socketIndex = argv.indexOf("--socket");
|
|
13
14
|
if (socketIndex >= 0) {
|
|
@@ -25,16 +26,22 @@ const socketPath = parseSocketPath(process.argv);
|
|
|
25
26
|
message: "starting daemon entrypoint",
|
|
26
27
|
meta: { socketPath },
|
|
27
28
|
});
|
|
29
|
+
const managedAgents = ["ouroboros", "slugger"];
|
|
28
30
|
const processManager = new process_manager_1.DaemonProcessManager({
|
|
29
|
-
agents:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
agents: managedAgents.map((agent) => ({
|
|
32
|
+
name: agent,
|
|
33
|
+
entry: "heart/agent-entry.js",
|
|
34
|
+
channel: "inner-dialog",
|
|
35
|
+
autoStart: true,
|
|
36
|
+
})),
|
|
33
37
|
});
|
|
34
38
|
const scheduler = new task_scheduler_1.TaskDrivenScheduler({
|
|
35
|
-
agents: [
|
|
39
|
+
agents: [...managedAgents],
|
|
36
40
|
});
|
|
37
41
|
const router = new message_router_1.FileMessageRouter();
|
|
42
|
+
const senseManager = new sense_manager_1.DaemonSenseManager({
|
|
43
|
+
agents: [...managedAgents],
|
|
44
|
+
});
|
|
38
45
|
const healthMonitor = new health_monitor_1.HealthMonitor({
|
|
39
46
|
processManager,
|
|
40
47
|
scheduler,
|
|
@@ -51,6 +58,7 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
|
|
|
51
58
|
const daemon = new daemon_1.OuroDaemon({
|
|
52
59
|
socketPath,
|
|
53
60
|
processManager,
|
|
61
|
+
senseManager,
|
|
54
62
|
scheduler,
|
|
55
63
|
healthMonitor,
|
|
56
64
|
router,
|
|
@@ -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": {
|
|
@@ -39,7 +39,7 @@ const os = __importStar(require("os"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
41
|
const WRAPPER_SCRIPT = `#!/bin/sh
|
|
42
|
-
exec npx --yes @ouro.bot/cli "$@"
|
|
42
|
+
exec npx --yes @ouro.bot/cli@latest "$@"
|
|
43
43
|
`;
|
|
44
44
|
function detectShellProfile(homeDir, shell) {
|
|
45
45
|
if (!shell)
|
|
@@ -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;
|
|
@@ -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
|
}
|