@ouro.bot/cli 0.1.0-alpha.11 → 0.1.0-alpha.13
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/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +2 -2
- package/dist/heart/core.js +3 -2
- package/dist/heart/daemon/daemon-cli.js +202 -23
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon.js +40 -9
- package/dist/heart/daemon/hatch-flow.js +0 -10
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +21 -5
- package/dist/heart/daemon/ouro-uti.js +11 -2
- 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 +37 -94
- package/dist/heart/daemon/specialist-prompt.js +43 -8
- package/dist/heart/daemon/specialist-tools.js +161 -59
- 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/bundle-manifest.js +58 -0
- package/dist/mind/prompt.js +71 -0
- package/dist/senses/cli.js +162 -98
- package/package.json +7 -2
- package/dist/heart/daemon/specialist-session.js +0 -177
- package/dist/inner-worker-entry.js +0 -4
|
@@ -34,99 +34,201 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getSpecialistTools = getSpecialistTools;
|
|
37
|
-
exports.
|
|
37
|
+
exports.createSpecialistExecTool = createSpecialistExecTool;
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
39
40
|
const tools_base_1 = require("../../repertoire/tools-base");
|
|
40
41
|
const hatch_flow_1 = require("./hatch-flow");
|
|
41
42
|
const hatch_animation_1 = require("./hatch-animation");
|
|
43
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
42
44
|
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
-
const
|
|
45
|
+
const completeAdoptionTool = {
|
|
44
46
|
type: "function",
|
|
45
47
|
function: {
|
|
46
|
-
name: "
|
|
47
|
-
description: "
|
|
48
|
+
name: "complete_adoption",
|
|
49
|
+
description: "finalize the agent bundle and hatch the new agent. call this only when you have written all 5 psyche files and agent.json to the temp directory, and the human has approved the bundle.",
|
|
48
50
|
parameters: {
|
|
49
51
|
type: "object",
|
|
50
52
|
properties: {
|
|
51
53
|
name: {
|
|
52
54
|
type: "string",
|
|
53
|
-
description: "the name for the new agent (
|
|
55
|
+
description: "the PascalCase name for the new agent (e.g. 'Slugger')",
|
|
54
56
|
},
|
|
55
|
-
|
|
57
|
+
handoff_message: {
|
|
56
58
|
type: "string",
|
|
57
|
-
description: "
|
|
59
|
+
description: "a warm handoff message to display to the human after the agent is hatched",
|
|
58
60
|
},
|
|
59
61
|
},
|
|
60
|
-
required: ["name", "
|
|
62
|
+
required: ["name", "handoff_message"],
|
|
61
63
|
},
|
|
62
64
|
},
|
|
63
65
|
};
|
|
64
66
|
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
67
|
+
const writeFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "write_file");
|
|
65
68
|
const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
|
|
66
69
|
/**
|
|
67
70
|
* Returns the specialist's tool schema array.
|
|
68
71
|
*/
|
|
69
72
|
function getSpecialistTools() {
|
|
70
|
-
return [
|
|
73
|
+
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirTool.tool];
|
|
74
|
+
}
|
|
75
|
+
const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
|
|
76
|
+
function isPascalCase(name) {
|
|
77
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
78
|
+
}
|
|
79
|
+
function writeReadme(dir, purpose) {
|
|
80
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
81
|
+
const readmePath = path.join(dir, "README.md");
|
|
82
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
83
|
+
if (!fs.existsSync(readmePath)) {
|
|
84
|
+
fs.writeFileSync(readmePath, `# ${path.basename(dir)}\n\n${purpose}\n`, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function scaffoldBundle(bundleRoot) {
|
|
88
|
+
writeReadme(path.join(bundleRoot, "memory"), "Persistent memory store.");
|
|
89
|
+
writeReadme(path.join(bundleRoot, "memory", "daily"), "Daily memory entries.");
|
|
90
|
+
writeReadme(path.join(bundleRoot, "memory", "archive"), "Archived memory.");
|
|
91
|
+
writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
|
|
92
|
+
writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
|
|
93
|
+
writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
|
|
94
|
+
writeReadme(path.join(bundleRoot, "tasks", "one-shots"), "One-shot tasks.");
|
|
95
|
+
writeReadme(path.join(bundleRoot, "tasks", "ongoing"), "Ongoing tasks.");
|
|
96
|
+
writeReadme(path.join(bundleRoot, "skills"), "Local skill files.");
|
|
97
|
+
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
98
|
+
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
99
|
+
// Memory scaffold files
|
|
100
|
+
const memoryRoot = path.join(bundleRoot, "memory");
|
|
101
|
+
const factsPath = path.join(memoryRoot, "facts.jsonl");
|
|
102
|
+
const entitiesPath = path.join(memoryRoot, "entities.json");
|
|
103
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
104
|
+
if (!fs.existsSync(factsPath))
|
|
105
|
+
fs.writeFileSync(factsPath, "", "utf-8");
|
|
106
|
+
/* v8 ignore next -- defensive: guard against re-scaffold on existing bundle @preserve */
|
|
107
|
+
if (!fs.existsSync(entitiesPath))
|
|
108
|
+
fs.writeFileSync(entitiesPath, "{}\n", "utf-8");
|
|
109
|
+
// bundle-meta.json
|
|
110
|
+
const meta = (0, bundle_manifest_1.createBundleMeta)();
|
|
111
|
+
fs.writeFileSync(path.join(bundleRoot, "bundle-meta.json"), JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
112
|
+
}
|
|
113
|
+
function moveDir(src, dest) {
|
|
114
|
+
try {
|
|
115
|
+
fs.renameSync(src, dest);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* v8 ignore start -- cross-device fallback: only triggers on EXDEV (e.g. /tmp → different mount), untestable in CI @preserve */
|
|
119
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
120
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
121
|
+
/* v8 ignore stop */
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function execCompleteAdoption(args, deps) {
|
|
125
|
+
const name = args.name;
|
|
126
|
+
const handoffMessage = args.handoff_message;
|
|
127
|
+
if (!name) {
|
|
128
|
+
return "error: missing required 'name' parameter";
|
|
129
|
+
}
|
|
130
|
+
if (!isPascalCase(name)) {
|
|
131
|
+
return `error: name '${name}' must be PascalCase (e.g. 'Slugger', 'MyAgent')`;
|
|
132
|
+
}
|
|
133
|
+
// Validate psyche files exist
|
|
134
|
+
const psycheDir = path.join(deps.tempDir, "psyche");
|
|
135
|
+
const missingPsyche = PSYCHE_FILES.filter((f) => !fs.existsSync(path.join(psycheDir, f)));
|
|
136
|
+
if (missingPsyche.length > 0) {
|
|
137
|
+
return `error: missing psyche files in temp directory: ${missingPsyche.join(", ")}. write them first using write_file.`;
|
|
138
|
+
}
|
|
139
|
+
// Validate agent.json exists
|
|
140
|
+
const agentJsonPath = path.join(deps.tempDir, "agent.json");
|
|
141
|
+
if (!fs.existsSync(agentJsonPath)) {
|
|
142
|
+
return "error: agent.json not found in temp directory. write it first using write_file.";
|
|
143
|
+
}
|
|
144
|
+
// Validate target doesn't exist
|
|
145
|
+
const targetBundle = path.join(deps.bundlesRoot, `${name}.ouro`);
|
|
146
|
+
if (fs.existsSync(targetBundle)) {
|
|
147
|
+
return `error: bundle '${name}.ouro' already exists at ${deps.bundlesRoot}. choose a different name.`;
|
|
148
|
+
}
|
|
149
|
+
// Scaffold structural dirs into tempDir
|
|
150
|
+
scaffoldBundle(deps.tempDir);
|
|
151
|
+
// Move tempDir -> final bundle location
|
|
152
|
+
moveDir(deps.tempDir, targetBundle);
|
|
153
|
+
// Write secrets
|
|
154
|
+
try {
|
|
155
|
+
(0, hatch_flow_1.writeSecretsFile)(name, deps.provider, deps.credentials, deps.secretsRoot);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
// Rollback: remove the moved bundle
|
|
159
|
+
try {
|
|
160
|
+
fs.rmSync(targetBundle, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Best effort cleanup
|
|
164
|
+
}
|
|
165
|
+
return `error: failed to write secrets: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
166
|
+
}
|
|
167
|
+
// Play hatch animation
|
|
168
|
+
await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
|
|
169
|
+
// Display handoff message
|
|
170
|
+
/* v8 ignore next -- UI-only: handoff message display, covered by integration @preserve */
|
|
171
|
+
if (handoffMessage && deps.animationWriter) {
|
|
172
|
+
deps.animationWriter(`\n${handoffMessage}\n`);
|
|
173
|
+
}
|
|
174
|
+
(0, runtime_1.emitNervesEvent)({
|
|
175
|
+
component: "daemon",
|
|
176
|
+
event: "daemon.adoption_complete",
|
|
177
|
+
message: "adoption completed successfully",
|
|
178
|
+
meta: { agentName: name, bundlePath: targetBundle },
|
|
179
|
+
});
|
|
180
|
+
return JSON.stringify({ success: true, agentName: name, bundlePath: targetBundle });
|
|
71
181
|
}
|
|
72
182
|
/**
|
|
73
|
-
*
|
|
74
|
-
* Returns the tool result string.
|
|
183
|
+
* Create a specialist tool executor with the given dependencies captured in closure.
|
|
75
184
|
*/
|
|
76
|
-
|
|
185
|
+
function createSpecialistExecTool(deps) {
|
|
77
186
|
(0, runtime_1.emitNervesEvent)({
|
|
78
187
|
component: "daemon",
|
|
79
|
-
event: "daemon.
|
|
80
|
-
message: "
|
|
81
|
-
meta: {
|
|
188
|
+
event: "daemon.specialist_exec_tool_created",
|
|
189
|
+
message: "specialist exec tool created",
|
|
190
|
+
meta: { tempDir: deps.tempDir },
|
|
82
191
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
agentName,
|
|
90
|
-
humanName: args.humanName || deps.humanName,
|
|
91
|
-
provider: deps.provider,
|
|
92
|
-
credentials: deps.credentials,
|
|
93
|
-
};
|
|
94
|
-
// Pass identity dirs to prevent hatch flow from syncing to ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
95
|
-
// or cwd/AdoptionSpecialist.ouro/. The specialist already picked its identity; the hatch flow
|
|
96
|
-
// just needs a valid source dir to pick from for the hatchling's LORE.md seed.
|
|
97
|
-
const identitiesDir = deps.specialistIdentitiesDir;
|
|
98
|
-
const result = await (0, hatch_flow_1.runHatchFlow)(input, {
|
|
99
|
-
bundlesRoot: deps.bundlesRoot,
|
|
100
|
-
secretsRoot: deps.secretsRoot,
|
|
101
|
-
...(identitiesDir ? { specialistIdentitySourceDir: identitiesDir, specialistIdentityTargetDir: identitiesDir } : {}),
|
|
192
|
+
return async (name, args) => {
|
|
193
|
+
(0, runtime_1.emitNervesEvent)({
|
|
194
|
+
component: "daemon",
|
|
195
|
+
event: "daemon.specialist_tool_exec",
|
|
196
|
+
message: "executing specialist tool",
|
|
197
|
+
meta: { tool: name },
|
|
102
198
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`hatched ${agentName} successfully.`,
|
|
106
|
-
`bundle path: ${result.bundleRoot}`,
|
|
107
|
-
`identity seed: ${result.selectedIdentity}`,
|
|
108
|
-
`specialist secrets: ${result.specialistSecretsPath}`,
|
|
109
|
-
`hatchling secrets: ${result.hatchlingSecretsPath}`,
|
|
110
|
-
].join("\n");
|
|
111
|
-
}
|
|
112
|
-
if (name === "read_file") {
|
|
113
|
-
try {
|
|
114
|
-
return fs.readFileSync(args.path, "utf-8");
|
|
199
|
+
if (name === "complete_adoption") {
|
|
200
|
+
return execCompleteAdoption(args, deps);
|
|
115
201
|
}
|
|
116
|
-
|
|
117
|
-
|
|
202
|
+
if (name === "read_file") {
|
|
203
|
+
try {
|
|
204
|
+
return fs.readFileSync(args.path, "utf-8");
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
208
|
+
}
|
|
118
209
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
210
|
+
if (name === "write_file") {
|
|
211
|
+
try {
|
|
212
|
+
const dir = path.dirname(args.path);
|
|
213
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
214
|
+
fs.writeFileSync(args.path, args.content, "utf-8");
|
|
215
|
+
return `wrote ${args.path}`;
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
219
|
+
}
|
|
126
220
|
}
|
|
127
|
-
|
|
128
|
-
|
|
221
|
+
if (name === "list_directory") {
|
|
222
|
+
try {
|
|
223
|
+
return fs
|
|
224
|
+
.readdirSync(args.path, { withFileTypes: true })
|
|
225
|
+
.map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
|
|
226
|
+
.join("\n");
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive @preserve */ String(e)}`;
|
|
230
|
+
}
|
|
129
231
|
}
|
|
130
|
-
|
|
131
|
-
|
|
232
|
+
return `error: unknown tool '${name}'`;
|
|
233
|
+
};
|
|
132
234
|
}
|
|
@@ -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
|
+
}
|
|
@@ -34,6 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CANONICAL_BUNDLE_MANIFEST = void 0;
|
|
37
|
+
exports.getPackageVersion = getPackageVersion;
|
|
38
|
+
exports.createBundleMeta = createBundleMeta;
|
|
39
|
+
exports.backfillBundleMeta = backfillBundleMeta;
|
|
40
|
+
exports.resetBackfillTracking = resetBackfillTracking;
|
|
37
41
|
exports.isCanonicalBundlePath = isCanonicalBundlePath;
|
|
38
42
|
exports.findNonCanonicalBundlePaths = findNonCanonicalBundlePaths;
|
|
39
43
|
const fs = __importStar(require("fs"));
|
|
@@ -41,6 +45,7 @@ const path = __importStar(require("path"));
|
|
|
41
45
|
const runtime_1 = require("../nerves/runtime");
|
|
42
46
|
exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
43
47
|
{ path: "agent.json", kind: "file" },
|
|
48
|
+
{ path: "bundle-meta.json", kind: "file" },
|
|
44
49
|
{ path: "psyche/SOUL.md", kind: "file" },
|
|
45
50
|
{ path: "psyche/IDENTITY.md", kind: "file" },
|
|
46
51
|
{ path: "psyche/LORE.md", kind: "file" },
|
|
@@ -53,6 +58,59 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
53
58
|
{ path: "senses", kind: "dir" },
|
|
54
59
|
{ path: "senses/teams", kind: "dir" },
|
|
55
60
|
];
|
|
61
|
+
function getPackageVersion() {
|
|
62
|
+
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
63
|
+
const raw = fs.readFileSync(packageJsonPath, "utf-8");
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
(0, runtime_1.emitNervesEvent)({
|
|
66
|
+
component: "mind",
|
|
67
|
+
event: "mind.package_version_read",
|
|
68
|
+
message: "read package version",
|
|
69
|
+
meta: { version: parsed.version },
|
|
70
|
+
});
|
|
71
|
+
return parsed.version;
|
|
72
|
+
}
|
|
73
|
+
function createBundleMeta() {
|
|
74
|
+
return {
|
|
75
|
+
runtimeVersion: getPackageVersion(),
|
|
76
|
+
bundleSchemaVersion: 1,
|
|
77
|
+
lastUpdated: new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const _backfilledRoots = new Set();
|
|
81
|
+
/**
|
|
82
|
+
* If bundle-meta.json is missing from the agent root, create it with current runtime version.
|
|
83
|
+
* This backfills existing agent bundles that were created before bundle-meta.json was introduced.
|
|
84
|
+
* Only attempts once per bundleRoot per process.
|
|
85
|
+
*/
|
|
86
|
+
function backfillBundleMeta(bundleRoot) {
|
|
87
|
+
if (_backfilledRoots.has(bundleRoot))
|
|
88
|
+
return;
|
|
89
|
+
_backfilledRoots.add(bundleRoot);
|
|
90
|
+
const metaPath = path.join(bundleRoot, "bundle-meta.json");
|
|
91
|
+
try {
|
|
92
|
+
if (fs.existsSync(metaPath)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const meta = createBundleMeta();
|
|
96
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
97
|
+
(0, runtime_1.emitNervesEvent)({
|
|
98
|
+
component: "mind",
|
|
99
|
+
event: "mind.bundle_meta_backfill",
|
|
100
|
+
message: "backfilled missing bundle-meta.json",
|
|
101
|
+
meta: { bundleRoot },
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Non-blocking: if we can't write, that's okay
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Reset the backfill tracking set. Used in tests.
|
|
110
|
+
*/
|
|
111
|
+
function resetBackfillTracking() {
|
|
112
|
+
_backfilledRoots.clear();
|
|
113
|
+
}
|
|
56
114
|
const CANONICAL_FILE_PATHS = new Set(exports.CANONICAL_BUNDLE_MANIFEST
|
|
57
115
|
.filter((entry) => entry.kind === "file")
|
|
58
116
|
.map((entry) => entry.path));
|
package/dist/mind/prompt.js
CHANGED
|
@@ -47,10 +47,12 @@ const identity_1 = require("../heart/identity");
|
|
|
47
47
|
const os = __importStar(require("os"));
|
|
48
48
|
const channel_1 = require("./friends/channel");
|
|
49
49
|
const runtime_1 = require("../nerves/runtime");
|
|
50
|
+
const bundle_manifest_1 = require("./bundle-manifest");
|
|
50
51
|
const first_impressions_1 = require("./first-impressions");
|
|
51
52
|
const tasks_1 = require("../repertoire/tasks");
|
|
52
53
|
// Lazy-loaded psyche text cache
|
|
53
54
|
let _psycheCache = null;
|
|
55
|
+
let _senseStatusLinesCache = null;
|
|
54
56
|
function loadPsycheFile(name) {
|
|
55
57
|
try {
|
|
56
58
|
const psycheDir = path.join((0, identity_1.getAgentRoot)(), "psyche");
|
|
@@ -74,6 +76,7 @@ function loadPsyche() {
|
|
|
74
76
|
}
|
|
75
77
|
function resetPsycheCache() {
|
|
76
78
|
_psycheCache = null;
|
|
79
|
+
_senseStatusLinesCache = null;
|
|
77
80
|
}
|
|
78
81
|
const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
79
82
|
function resolveFriendName(friendId, friendsDir, agentName) {
|
|
@@ -194,6 +197,7 @@ function runtimeInfoSection(channel) {
|
|
|
194
197
|
lines.push(`agent: ${agentName}`);
|
|
195
198
|
lines.push(`cwd: ${process.cwd()}`);
|
|
196
199
|
lines.push(`channel: ${channel}`);
|
|
200
|
+
lines.push(`current sense: ${channel}`);
|
|
197
201
|
lines.push(`i can read and modify my own source code.`);
|
|
198
202
|
if (channel === "cli") {
|
|
199
203
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
@@ -204,8 +208,73 @@ function runtimeInfoSection(channel) {
|
|
|
204
208
|
else {
|
|
205
209
|
lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
|
|
206
210
|
}
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push(...senseRuntimeGuidance(channel));
|
|
207
213
|
return lines.join("\n");
|
|
208
214
|
}
|
|
215
|
+
function hasTextField(record, key) {
|
|
216
|
+
return typeof record?.[key] === "string" && record[key].trim().length > 0;
|
|
217
|
+
}
|
|
218
|
+
function localSenseStatusLines() {
|
|
219
|
+
if (_senseStatusLinesCache) {
|
|
220
|
+
return [..._senseStatusLinesCache];
|
|
221
|
+
}
|
|
222
|
+
const config = (0, identity_1.loadAgentConfig)();
|
|
223
|
+
const senses = config.senses ?? {
|
|
224
|
+
cli: { enabled: true },
|
|
225
|
+
teams: { enabled: false },
|
|
226
|
+
bluebubbles: { enabled: false },
|
|
227
|
+
};
|
|
228
|
+
let payload = {};
|
|
229
|
+
try {
|
|
230
|
+
const raw = fs.readFileSync((0, identity_1.getAgentSecretsPath)(), "utf-8");
|
|
231
|
+
const parsed = JSON.parse(raw);
|
|
232
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
233
|
+
payload = parsed;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
payload = {};
|
|
238
|
+
}
|
|
239
|
+
const teams = payload.teams;
|
|
240
|
+
const bluebubbles = payload.bluebubbles;
|
|
241
|
+
const configured = {
|
|
242
|
+
cli: true,
|
|
243
|
+
teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
|
|
244
|
+
bluebubbles: hasTextField(bluebubbles, "serverUrl") && hasTextField(bluebubbles, "password"),
|
|
245
|
+
};
|
|
246
|
+
const rows = [
|
|
247
|
+
{ label: "CLI", status: "interactive" },
|
|
248
|
+
{
|
|
249
|
+
label: "Teams",
|
|
250
|
+
status: !senses.teams.enabled ? "disabled" : configured.teams ? "ready" : "needs_config",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
label: "BlueBubbles",
|
|
254
|
+
status: !senses.bluebubbles.enabled ? "disabled" : configured.bluebubbles ? "ready" : "needs_config",
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
_senseStatusLinesCache = rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
258
|
+
return [..._senseStatusLinesCache];
|
|
259
|
+
}
|
|
260
|
+
function senseRuntimeGuidance(channel) {
|
|
261
|
+
const lines = ["available senses:"];
|
|
262
|
+
lines.push(...localSenseStatusLines());
|
|
263
|
+
lines.push("sense states:");
|
|
264
|
+
lines.push("- interactive = available when opened by the user instead of kept running by the daemon");
|
|
265
|
+
lines.push("- disabled = turned off in agent.json");
|
|
266
|
+
lines.push("- needs_config = enabled but missing required secrets.json values");
|
|
267
|
+
lines.push("- ready = enabled and configured; `ouro up` should bring it online");
|
|
268
|
+
lines.push("- running = enabled and currently active");
|
|
269
|
+
lines.push("- error = enabled but unhealthy");
|
|
270
|
+
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.");
|
|
271
|
+
lines.push("teams setup truth: enable `senses.teams.enabled`, then provide `teams.clientId`, `teams.clientSecret`, and `teams.tenantId` in secrets.json.");
|
|
272
|
+
lines.push("bluebubbles setup truth: enable `senses.bluebubbles.enabled`, then provide `bluebubbles.serverUrl` and `bluebubbles.password` in secrets.json.");
|
|
273
|
+
if (channel === "cli") {
|
|
274
|
+
lines.push("cli is interactive: it is available when the user opens it, not something `ouro up` daemonizes.");
|
|
275
|
+
}
|
|
276
|
+
return lines;
|
|
277
|
+
}
|
|
209
278
|
function providerSection() {
|
|
210
279
|
return `## my provider\n${(0, core_1.getProviderDisplayLabel)()}`;
|
|
211
280
|
}
|
|
@@ -319,6 +388,8 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
319
388
|
message: "buildSystem started",
|
|
320
389
|
meta: { channel, has_context: Boolean(context), tool_choice_required: Boolean(options?.toolChoiceRequired) },
|
|
321
390
|
});
|
|
391
|
+
// Backfill bundle-meta.json for existing agents that don't have one
|
|
392
|
+
(0, bundle_manifest_1.backfillBundleMeta)((0, identity_1.getAgentRoot)());
|
|
322
393
|
const system = [
|
|
323
394
|
soulSection(),
|
|
324
395
|
identitySection(),
|