@ouro.bot/cli 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +4 -1
- package/dist/heart/config.js +34 -0
- package/dist/heart/core.js +41 -2
- package/dist/heart/daemon/daemon-cli.js +293 -46
- package/dist/heart/daemon/daemon.js +3 -0
- package/dist/heart/daemon/hatch-animation.js +28 -0
- package/dist/heart/daemon/hatch-flow.js +3 -1
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/log-tailer.js +146 -0
- package/dist/heart/daemon/os-cron.js +260 -0
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +161 -0
- package/dist/heart/daemon/process-manager.js +18 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/specialist-orchestrator.js +186 -0
- package/dist/heart/daemon/specialist-prompt.js +61 -0
- package/dist/heart/daemon/specialist-session.js +177 -0
- package/dist/heart/daemon/specialist-tools.js +132 -0
- package/dist/heart/daemon/task-scheduler.js +4 -1
- package/dist/heart/identity.js +28 -3
- package/dist/heart/providers/anthropic.js +3 -0
- package/dist/heart/streaming.js +3 -0
- package/dist/mind/associative-recall.js +23 -2
- package/dist/mind/context.js +85 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/memory.js +62 -0
- package/dist/mind/pending.js +93 -0
- package/dist/mind/prompt-refresh.js +20 -0
- package/dist/mind/prompt.js +101 -0
- package/dist/nerves/coverage/file-completeness.js +14 -4
- package/dist/repertoire/tools-base.js +92 -0
- package/dist/repertoire/tools.js +3 -3
- package/dist/senses/bluebubbles-client.js +279 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +332 -0
- package/dist/senses/cli.js +89 -8
- package/dist/senses/inner-dialog.js +15 -0
- package/dist/senses/session-lock.js +119 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +4 -3
- package/subagents/README.md +3 -1
- package/subagents/work-merger.md +33 -2
|
@@ -0,0 +1,132 @@
|
|
|
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.getSpecialistTools = getSpecialistTools;
|
|
37
|
+
exports.execSpecialistTool = execSpecialistTool;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const tools_base_1 = require("../../repertoire/tools-base");
|
|
40
|
+
const hatch_flow_1 = require("./hatch-flow");
|
|
41
|
+
const hatch_animation_1 = require("./hatch-animation");
|
|
42
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
43
|
+
const hatchAgentTool = {
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: "hatch_agent",
|
|
47
|
+
description: "create a new agent bundle with the given name. call this when you have gathered enough information from the human to hatch their agent.",
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
name: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "the name for the new agent (PascalCase, e.g. 'Slugger')",
|
|
54
|
+
},
|
|
55
|
+
humanName: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "the human's preferred name, as they told you during conversation",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ["name", "humanName"],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
65
|
+
const listDirTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "list_directory");
|
|
66
|
+
/**
|
|
67
|
+
* Returns the specialist's tool schema array.
|
|
68
|
+
*/
|
|
69
|
+
function getSpecialistTools() {
|
|
70
|
+
return [hatchAgentTool, tools_base_1.finalAnswerTool, readFileTool.tool, listDirTool.tool];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Execute a specialist tool call.
|
|
74
|
+
* Returns the tool result string.
|
|
75
|
+
*/
|
|
76
|
+
async function execSpecialistTool(name, args, deps) {
|
|
77
|
+
(0, runtime_1.emitNervesEvent)({
|
|
78
|
+
component: "daemon",
|
|
79
|
+
event: "daemon.specialist_tool_exec",
|
|
80
|
+
message: "executing specialist tool",
|
|
81
|
+
meta: { tool: name },
|
|
82
|
+
});
|
|
83
|
+
if (name === "hatch_agent") {
|
|
84
|
+
const agentName = args.name;
|
|
85
|
+
if (!agentName) {
|
|
86
|
+
return "error: missing required 'name' parameter for hatch_agent";
|
|
87
|
+
}
|
|
88
|
+
const input = {
|
|
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 } : {}),
|
|
102
|
+
});
|
|
103
|
+
await (0, hatch_animation_1.playHatchAnimation)(agentName, deps.animationWriter);
|
|
104
|
+
return [
|
|
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");
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (name === "list_directory") {
|
|
121
|
+
try {
|
|
122
|
+
return fs
|
|
123
|
+
.readdirSync(args.path, { withFileTypes: true })
|
|
124
|
+
.map((e) => `${e.isDirectory() ? "d" : "-"} ${e.name}`)
|
|
125
|
+
.join("\n");
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
return `error: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return `error: unknown tool '${name}'`;
|
|
132
|
+
}
|
|
@@ -103,6 +103,7 @@ class TaskDrivenScheduler {
|
|
|
103
103
|
readFileSync;
|
|
104
104
|
writeFileSync;
|
|
105
105
|
readdirSync;
|
|
106
|
+
osCronManager;
|
|
106
107
|
jobs = new Map();
|
|
107
108
|
taskPathByKey = new Map();
|
|
108
109
|
constructor(options) {
|
|
@@ -113,12 +114,13 @@ class TaskDrivenScheduler {
|
|
|
113
114
|
this.readFileSync = options.readFileSync ?? fs.readFileSync;
|
|
114
115
|
this.writeFileSync = options.writeFileSync ?? fs.writeFileSync;
|
|
115
116
|
this.readdirSync = options.readdirSync ?? fs.readdirSync;
|
|
117
|
+
this.osCronManager = options.osCronManager;
|
|
116
118
|
}
|
|
117
119
|
start() {
|
|
118
120
|
void this.reconcile();
|
|
119
121
|
}
|
|
120
122
|
stop() {
|
|
121
|
-
|
|
123
|
+
this.osCronManager?.removeAll();
|
|
122
124
|
}
|
|
123
125
|
listJobs() {
|
|
124
126
|
return [...this.jobs.values()]
|
|
@@ -197,6 +199,7 @@ class TaskDrivenScheduler {
|
|
|
197
199
|
message: "reconciled task-driven schedule jobs",
|
|
198
200
|
meta: { jobCount: this.jobs.size, agents: this.agents.length },
|
|
199
201
|
});
|
|
202
|
+
this.osCronManager?.sync([...this.jobs.values()]);
|
|
200
203
|
}
|
|
201
204
|
async recordTaskRun(agent, taskId) {
|
|
202
205
|
const key = `${agent}:${taskId}`;
|
package/dist/heart/identity.js
CHANGED
|
@@ -41,6 +41,8 @@ exports.getAgentBundlesRoot = getAgentBundlesRoot;
|
|
|
41
41
|
exports.getAgentRoot = getAgentRoot;
|
|
42
42
|
exports.getAgentSecretsPath = getAgentSecretsPath;
|
|
43
43
|
exports.loadAgentConfig = loadAgentConfig;
|
|
44
|
+
exports.setAgentName = setAgentName;
|
|
45
|
+
exports.setAgentConfigOverride = setAgentConfigOverride;
|
|
44
46
|
exports.resetIdentity = resetIdentity;
|
|
45
47
|
const fs = __importStar(require("fs"));
|
|
46
48
|
const os = __importStar(require("os"));
|
|
@@ -70,6 +72,7 @@ function buildDefaultAgentTemplate(_agentName) {
|
|
|
70
72
|
}
|
|
71
73
|
let _cachedAgentName = null;
|
|
72
74
|
let _cachedAgentConfig = null;
|
|
75
|
+
let _agentConfigOverride = null;
|
|
73
76
|
/**
|
|
74
77
|
* Parse `--agent <name>` from process.argv.
|
|
75
78
|
* Caches the result after first parse.
|
|
@@ -100,11 +103,11 @@ function getAgentName() {
|
|
|
100
103
|
}
|
|
101
104
|
/**
|
|
102
105
|
* Resolve repo root from __dirname.
|
|
103
|
-
* In dev (tsx): __dirname is `<repo>/src`, so repo root is
|
|
104
|
-
* In compiled (node dist/): __dirname is `<repo>/dist`, so repo root is
|
|
106
|
+
* In dev (tsx): __dirname is `<repo>/src/heart`, so repo root is two levels up.
|
|
107
|
+
* In compiled (node dist/): __dirname is `<repo>/dist/heart`, so repo root is two levels up.
|
|
105
108
|
*/
|
|
106
109
|
function getRepoRoot() {
|
|
107
|
-
return path.resolve(__dirname, "
|
|
110
|
+
return path.resolve(__dirname, "../..");
|
|
108
111
|
}
|
|
109
112
|
/**
|
|
110
113
|
* Returns the shared bundle root directory: `~/AgentBundles/`
|
|
@@ -130,6 +133,9 @@ function getAgentSecretsPath(agentName = getAgentName()) {
|
|
|
130
133
|
* Throws descriptive error if file is missing or contains invalid JSON.
|
|
131
134
|
*/
|
|
132
135
|
function loadAgentConfig() {
|
|
136
|
+
if (_agentConfigOverride) {
|
|
137
|
+
return _agentConfigOverride;
|
|
138
|
+
}
|
|
133
139
|
if (_cachedAgentConfig) {
|
|
134
140
|
(0, runtime_1.emitNervesEvent)({
|
|
135
141
|
event: "identity.resolve",
|
|
@@ -250,6 +256,7 @@ function loadAgentConfig() {
|
|
|
250
256
|
enabled,
|
|
251
257
|
provider: rawProvider,
|
|
252
258
|
context: parsed.context,
|
|
259
|
+
logging: parsed.logging,
|
|
253
260
|
phrases: parsed.phrases,
|
|
254
261
|
};
|
|
255
262
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -260,6 +267,23 @@ function loadAgentConfig() {
|
|
|
260
267
|
});
|
|
261
268
|
return _cachedAgentConfig;
|
|
262
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Prime the agent name cache explicitly.
|
|
272
|
+
* Used when agent name is known via parameter (e.g., `ouro` CLI routing)
|
|
273
|
+
* rather than `--agent` argv. All downstream calls to `getAgentName()`
|
|
274
|
+
* will return this value until `resetIdentity()` is called.
|
|
275
|
+
*/
|
|
276
|
+
function setAgentName(name) {
|
|
277
|
+
_cachedAgentName = name;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Override the agent config returned by loadAgentConfig().
|
|
281
|
+
* When set to a non-null AgentConfig, loadAgentConfig() returns the override
|
|
282
|
+
* instead of reading from disk. When set to null, normal disk-based loading resumes.
|
|
283
|
+
*/
|
|
284
|
+
function setAgentConfigOverride(config) {
|
|
285
|
+
_agentConfigOverride = config;
|
|
286
|
+
}
|
|
263
287
|
/**
|
|
264
288
|
* Clear all cached identity state.
|
|
265
289
|
* Used in tests and when switching agent context.
|
|
@@ -267,4 +291,5 @@ function loadAgentConfig() {
|
|
|
267
291
|
function resetIdentity() {
|
|
268
292
|
_cachedAgentName = null;
|
|
269
293
|
_cachedAgentConfig = null;
|
|
294
|
+
_agentConfigOverride = null;
|
|
270
295
|
}
|
|
@@ -98,6 +98,9 @@ function toAnthropicMessages(messages) {
|
|
|
98
98
|
}
|
|
99
99
|
if (assistant.tool_calls) {
|
|
100
100
|
for (const toolCall of assistant.tool_calls) {
|
|
101
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
102
|
+
if (toolCall.type !== "function")
|
|
103
|
+
continue;
|
|
101
104
|
blocks.push({
|
|
102
105
|
type: "tool_use",
|
|
103
106
|
id: toolCall.id,
|
package/dist/heart/streaming.js
CHANGED
|
@@ -106,6 +106,9 @@ function toResponsesInput(messages) {
|
|
|
106
106
|
}
|
|
107
107
|
if (a.tool_calls) {
|
|
108
108
|
for (const tc of a.tool_calls) {
|
|
109
|
+
/* v8 ignore next -- type narrowing: OpenAI SDK only emits function tool_calls @preserve */
|
|
110
|
+
if (tc.type !== "function")
|
|
111
|
+
continue;
|
|
109
112
|
input.push({
|
|
110
113
|
type: "function_call",
|
|
111
114
|
call_id: tc.id,
|
|
@@ -144,8 +144,29 @@ async function injectAssociativeRecall(messages, options) {
|
|
|
144
144
|
const facts = readFacts(memoryRoot);
|
|
145
145
|
if (facts.length === 0)
|
|
146
146
|
return;
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
let recalled;
|
|
148
|
+
try {
|
|
149
|
+
const provider = options?.provider ?? createDefaultProvider();
|
|
150
|
+
recalled = await recallFactsForQuery(query, facts, provider, options);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Embeddings unavailable — fall back to substring matching
|
|
154
|
+
const lowerQuery = query.toLowerCase();
|
|
155
|
+
const topK = options?.topK ?? DEFAULT_TOP_K;
|
|
156
|
+
recalled = facts
|
|
157
|
+
.filter((fact) => fact.text.toLowerCase().includes(lowerQuery))
|
|
158
|
+
.slice(0, topK)
|
|
159
|
+
.map((fact) => ({ ...fact, score: 1 }));
|
|
160
|
+
if (recalled.length > 0) {
|
|
161
|
+
(0, runtime_1.emitNervesEvent)({
|
|
162
|
+
level: "warn",
|
|
163
|
+
component: "mind",
|
|
164
|
+
event: "mind.associative_recall_fallback",
|
|
165
|
+
message: "embeddings unavailable, used substring fallback",
|
|
166
|
+
meta: { matchCount: recalled.length },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
149
170
|
if (recalled.length === 0)
|
|
150
171
|
return;
|
|
151
172
|
const recallSection = recalled
|
package/dist/mind/context.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.trimMessages = trimMessages;
|
|
37
|
+
exports.validateSessionMessages = validateSessionMessages;
|
|
38
|
+
exports.repairSessionMessages = repairSessionMessages;
|
|
37
39
|
exports.saveSession = saveSession;
|
|
38
40
|
exports.loadSession = loadSession;
|
|
39
41
|
exports.postTurn = postTurn;
|
|
@@ -166,7 +168,77 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
166
168
|
});
|
|
167
169
|
return trimmed;
|
|
168
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Checks session invariant: after system messages, sequence must be
|
|
173
|
+
* user → assistant (with optional tool calls/results) → user → assistant...
|
|
174
|
+
* Never assistant → assistant without a user in between.
|
|
175
|
+
*/
|
|
176
|
+
function validateSessionMessages(messages) {
|
|
177
|
+
const violations = [];
|
|
178
|
+
let prevNonToolRole = null;
|
|
179
|
+
let prevAssistantHadToolCalls = false;
|
|
180
|
+
let sawToolResultSincePrevAssistant = false;
|
|
181
|
+
for (let i = 0; i < messages.length; i++) {
|
|
182
|
+
const msg = messages[i];
|
|
183
|
+
if (msg.role === "system")
|
|
184
|
+
continue;
|
|
185
|
+
if (msg.role === "tool") {
|
|
186
|
+
sawToolResultSincePrevAssistant = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (msg.role === "assistant" && prevNonToolRole === "assistant") {
|
|
190
|
+
// assistant → tool(s) → assistant is valid (tool call flow)
|
|
191
|
+
if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
|
|
192
|
+
violations.push(`back-to-back assistant at index ${i}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
prevAssistantHadToolCalls = msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
|
|
196
|
+
sawToolResultSincePrevAssistant = false;
|
|
197
|
+
prevNonToolRole = msg.role;
|
|
198
|
+
}
|
|
199
|
+
return violations;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Repairs session invariant violations by merging consecutive assistant messages.
|
|
203
|
+
*/
|
|
204
|
+
function repairSessionMessages(messages) {
|
|
205
|
+
const violations = validateSessionMessages(messages);
|
|
206
|
+
if (violations.length === 0)
|
|
207
|
+
return messages;
|
|
208
|
+
const result = [];
|
|
209
|
+
for (const msg of messages) {
|
|
210
|
+
if (msg.role === "assistant" && result.length > 0) {
|
|
211
|
+
const prev = result[result.length - 1];
|
|
212
|
+
if (prev.role === "assistant" && !("tool_calls" in prev)) {
|
|
213
|
+
const prevContent = typeof prev.content === "string" ? prev.content : "";
|
|
214
|
+
const curContent = typeof msg.content === "string" ? msg.content : "";
|
|
215
|
+
prev.content = `${prevContent}\n\n${curContent}`;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
result.push(msg);
|
|
220
|
+
}
|
|
221
|
+
(0, runtime_1.emitNervesEvent)({
|
|
222
|
+
level: "warn",
|
|
223
|
+
event: "mind.session_invariant_repair",
|
|
224
|
+
component: "mind",
|
|
225
|
+
message: "repaired session invariant violations",
|
|
226
|
+
meta: { violations },
|
|
227
|
+
});
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
169
230
|
function saveSession(filePath, messages, lastUsage) {
|
|
231
|
+
const violations = validateSessionMessages(messages);
|
|
232
|
+
if (violations.length > 0) {
|
|
233
|
+
(0, runtime_1.emitNervesEvent)({
|
|
234
|
+
level: "warn",
|
|
235
|
+
event: "mind.session_invariant_violation",
|
|
236
|
+
component: "mind",
|
|
237
|
+
message: "session invariant violated on save",
|
|
238
|
+
meta: { path: filePath, violations },
|
|
239
|
+
});
|
|
240
|
+
messages = repairSessionMessages(messages);
|
|
241
|
+
}
|
|
170
242
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
171
243
|
const envelope = { version: 1, messages };
|
|
172
244
|
if (lastUsage)
|
|
@@ -179,7 +251,19 @@ function loadSession(filePath) {
|
|
|
179
251
|
const data = JSON.parse(raw);
|
|
180
252
|
if (data.version !== 1)
|
|
181
253
|
return null;
|
|
182
|
-
|
|
254
|
+
let messages = data.messages;
|
|
255
|
+
const violations = validateSessionMessages(messages);
|
|
256
|
+
if (violations.length > 0) {
|
|
257
|
+
(0, runtime_1.emitNervesEvent)({
|
|
258
|
+
level: "warn",
|
|
259
|
+
event: "mind.session_invariant_violation",
|
|
260
|
+
component: "mind",
|
|
261
|
+
message: "session invariant violated on load",
|
|
262
|
+
meta: { path: filePath, violations },
|
|
263
|
+
});
|
|
264
|
+
messages = repairSessionMessages(messages);
|
|
265
|
+
}
|
|
266
|
+
return { messages, lastUsage: data.lastUsage };
|
|
183
267
|
}
|
|
184
268
|
catch {
|
|
185
269
|
return null;
|
|
@@ -21,6 +21,14 @@ const CHANNEL_CAPABILITIES = {
|
|
|
21
21
|
supportsRichCards: true,
|
|
22
22
|
maxMessageLength: Infinity,
|
|
23
23
|
},
|
|
24
|
+
bluebubbles: {
|
|
25
|
+
channel: "bluebubbles",
|
|
26
|
+
availableIntegrations: [],
|
|
27
|
+
supportsMarkdown: false,
|
|
28
|
+
supportsStreaming: false,
|
|
29
|
+
supportsRichCards: false,
|
|
30
|
+
maxMessageLength: Infinity,
|
|
31
|
+
},
|
|
24
32
|
};
|
|
25
33
|
const DEFAULT_CAPABILITIES = {
|
|
26
34
|
channel: "cli",
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.isIdentityProvider = isIdentityProvider;
|
|
6
6
|
exports.isIntegration = isIntegration;
|
|
7
7
|
const runtime_1 = require("../../nerves/runtime");
|
|
8
|
-
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation"]);
|
|
8
|
+
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle"]);
|
|
9
9
|
function isIdentityProvider(value) {
|
|
10
10
|
(0, runtime_1.emitNervesEvent)({
|
|
11
11
|
component: "friends",
|
package/dist/mind/memory.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
|
|
|
38
38
|
exports.appendFactsWithDedup = appendFactsWithDedup;
|
|
39
39
|
exports.readMemoryFacts = readMemoryFacts;
|
|
40
40
|
exports.saveMemoryFact = saveMemoryFact;
|
|
41
|
+
exports.backfillEmbeddings = backfillEmbeddings;
|
|
41
42
|
exports.searchMemoryFacts = searchMemoryFacts;
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
@@ -265,6 +266,67 @@ async function saveMemoryFact(options) {
|
|
|
265
266
|
};
|
|
266
267
|
return appendFactsWithDedup(stores, [fact]);
|
|
267
268
|
}
|
|
269
|
+
async function backfillEmbeddings(options) {
|
|
270
|
+
const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
|
|
271
|
+
const factsPath = path.join(memoryRoot, "facts.jsonl");
|
|
272
|
+
if (!fs.existsSync(factsPath))
|
|
273
|
+
return { total: 0, backfilled: 0, failed: 0 };
|
|
274
|
+
const facts = readExistingFacts(factsPath);
|
|
275
|
+
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
276
|
+
if (needsEmbedding.length === 0)
|
|
277
|
+
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
278
|
+
const provider = options?.embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
279
|
+
if (!provider) {
|
|
280
|
+
(0, runtime_1.emitNervesEvent)({
|
|
281
|
+
level: "warn",
|
|
282
|
+
component: "mind",
|
|
283
|
+
event: "mind.memory_backfill_skipped",
|
|
284
|
+
message: "embedding provider unavailable for backfill",
|
|
285
|
+
meta: { needsEmbedding: needsEmbedding.length },
|
|
286
|
+
});
|
|
287
|
+
return { total: facts.length, backfilled: 0, failed: needsEmbedding.length };
|
|
288
|
+
}
|
|
289
|
+
const batchSize = options?.batchSize ?? 50;
|
|
290
|
+
let backfilled = 0;
|
|
291
|
+
let failed = 0;
|
|
292
|
+
for (let i = 0; i < needsEmbedding.length; i += batchSize) {
|
|
293
|
+
const batch = needsEmbedding.slice(i, i + batchSize);
|
|
294
|
+
try {
|
|
295
|
+
const vectors = await provider.embed(batch.map((f) => f.text));
|
|
296
|
+
for (let j = 0; j < batch.length; j++) {
|
|
297
|
+
batch[j].embedding = vectors[j] ?? [];
|
|
298
|
+
if (batch[j].embedding.length > 0)
|
|
299
|
+
backfilled++;
|
|
300
|
+
else
|
|
301
|
+
failed++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
failed += batch.length;
|
|
306
|
+
(0, runtime_1.emitNervesEvent)({
|
|
307
|
+
level: "warn",
|
|
308
|
+
component: "mind",
|
|
309
|
+
event: "mind.memory_backfill_batch_error",
|
|
310
|
+
message: "embedding backfill batch failed",
|
|
311
|
+
meta: {
|
|
312
|
+
batchStart: i,
|
|
313
|
+
batchSize: batch.length,
|
|
314
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Rewrite facts file with updated embeddings
|
|
320
|
+
const lines = facts.map((f) => JSON.stringify(f)).join("\n") + "\n";
|
|
321
|
+
fs.writeFileSync(factsPath, lines, "utf8");
|
|
322
|
+
(0, runtime_1.emitNervesEvent)({
|
|
323
|
+
component: "mind",
|
|
324
|
+
event: "mind.memory_backfill_complete",
|
|
325
|
+
message: "embedding backfill completed",
|
|
326
|
+
meta: { total: facts.length, backfilled, failed },
|
|
327
|
+
});
|
|
328
|
+
return { total: facts.length, backfilled, failed };
|
|
329
|
+
}
|
|
268
330
|
function substringMatches(queryLower, facts) {
|
|
269
331
|
return facts.filter((fact) => fact.text.toLowerCase().includes(queryLower));
|
|
270
332
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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.getPendingDir = getPendingDir;
|
|
37
|
+
exports.drainPending = drainPending;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
function getPendingDir(agentName, friendId, channel, key) {
|
|
43
|
+
return path.join(os.homedir(), ".agentstate", agentName, "pending", friendId, channel, key);
|
|
44
|
+
}
|
|
45
|
+
function drainPending(pendingDir) {
|
|
46
|
+
if (!fs.existsSync(pendingDir))
|
|
47
|
+
return [];
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(pendingDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
// Collect both .json (new) and .processing (crash recovery)
|
|
56
|
+
const jsonFiles = entries.filter(f => f.endsWith(".json") && !f.endsWith(".processing"));
|
|
57
|
+
const processingFiles = entries.filter(f => f.endsWith(".json.processing"));
|
|
58
|
+
// Sort by filename (timestamp prefix gives chronological order)
|
|
59
|
+
const allFiles = [
|
|
60
|
+
...processingFiles.map(f => ({ file: f, needsRename: false })),
|
|
61
|
+
...jsonFiles.map(f => ({ file: f, needsRename: true })),
|
|
62
|
+
].sort((a, b) => a.file.localeCompare(b.file));
|
|
63
|
+
const messages = [];
|
|
64
|
+
for (const { file, needsRename } of allFiles) {
|
|
65
|
+
const srcPath = path.join(pendingDir, file);
|
|
66
|
+
const processingPath = needsRename
|
|
67
|
+
? path.join(pendingDir, file + ".processing")
|
|
68
|
+
: srcPath;
|
|
69
|
+
try {
|
|
70
|
+
if (needsRename) {
|
|
71
|
+
fs.renameSync(srcPath, processingPath);
|
|
72
|
+
}
|
|
73
|
+
const raw = fs.readFileSync(processingPath, "utf-8");
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
messages.push(parsed);
|
|
76
|
+
fs.unlinkSync(processingPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Skip unparseable files — still try to clean up
|
|
80
|
+
try {
|
|
81
|
+
fs.unlinkSync(processingPath);
|
|
82
|
+
}
|
|
83
|
+
catch { /* ignore */ }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
(0, runtime_1.emitNervesEvent)({
|
|
87
|
+
event: "mind.pending_drained",
|
|
88
|
+
component: "mind",
|
|
89
|
+
message: "pending queue drained",
|
|
90
|
+
meta: { pendingDir, count: messages.length, recovered: processingFiles.length },
|
|
91
|
+
});
|
|
92
|
+
return messages;
|
|
93
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.refreshSystemPrompt = refreshSystemPrompt;
|
|
4
|
+
const prompt_1 = require("./prompt");
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
async function refreshSystemPrompt(messages, channel, options, context) {
|
|
7
|
+
const newSystem = await (0, prompt_1.buildSystem)(channel, options, context);
|
|
8
|
+
if (messages.length > 0 && messages[0].role === "system") {
|
|
9
|
+
messages[0] = { role: "system", content: newSystem };
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
messages.unshift({ role: "system", content: newSystem });
|
|
13
|
+
}
|
|
14
|
+
(0, runtime_1.emitNervesEvent)({
|
|
15
|
+
event: "mind.system_prompt_refreshed",
|
|
16
|
+
component: "mind",
|
|
17
|
+
message: "system prompt refreshed",
|
|
18
|
+
meta: { channel },
|
|
19
|
+
});
|
|
20
|
+
}
|