@ouro.bot/cli 0.1.0-alpha.8 → 0.1.0-alpha.81
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 +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/assets/ouroboros.png +0 -0
- package/changelog.json +468 -0
- package/dist/heart/active-work.js +218 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +89 -0
- package/dist/heart/config.js +68 -23
- package/dist/heart/core.js +452 -93
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +430 -0
- package/dist/heart/daemon/daemon-cli.js +1779 -247
- package/dist/heart/daemon/daemon-entry.js +55 -6
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +216 -10
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -82
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +260 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/ouro-version-manager.js +164 -0
- package/dist/heart/daemon/process-manager.js +14 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +307 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +53 -84
- package/dist/heart/daemon/specialist-prompt.js +63 -11
- package/dist/heart/daemon/specialist-tools.js +211 -60
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +126 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +141 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/providers/anthropic.js +74 -9
- package/dist/heart/providers/azure.js +86 -7
- package/dist/heart/providers/github-copilot.js +149 -0
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +12 -3
- package/dist/heart/safe-workspace.js +228 -0
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/session-activity.js +169 -0
- package/dist/heart/session-recall.js +116 -0
- package/dist/heart/streaming.js +100 -22
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +27 -11
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +445 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +195 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +686 -251
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +95 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +894 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +405 -156
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +377 -83
- package/dist/senses/pipeline.js +307 -0
- package/dist/senses/teams.js +573 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
6
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
7
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
8
|
const runtime_1 = require("../../nerves/runtime");
|
|
7
9
|
const CHANNEL_CAPABILITIES = {
|
|
8
10
|
cli: {
|
|
9
11
|
channel: "cli",
|
|
12
|
+
senseType: "local",
|
|
10
13
|
availableIntegrations: [],
|
|
11
14
|
supportsMarkdown: false,
|
|
12
15
|
supportsStreaming: true,
|
|
@@ -15,6 +18,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
18
|
},
|
|
16
19
|
teams: {
|
|
17
20
|
channel: "teams",
|
|
21
|
+
senseType: "closed",
|
|
18
22
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
23
|
supportsMarkdown: true,
|
|
20
24
|
supportsStreaming: true,
|
|
@@ -23,15 +27,26 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
27
|
},
|
|
24
28
|
bluebubbles: {
|
|
25
29
|
channel: "bluebubbles",
|
|
30
|
+
senseType: "open",
|
|
26
31
|
availableIntegrations: [],
|
|
27
32
|
supportsMarkdown: false,
|
|
28
33
|
supportsStreaming: false,
|
|
29
34
|
supportsRichCards: false,
|
|
30
35
|
maxMessageLength: Infinity,
|
|
31
36
|
},
|
|
37
|
+
inner: {
|
|
38
|
+
channel: "inner",
|
|
39
|
+
senseType: "internal",
|
|
40
|
+
availableIntegrations: [],
|
|
41
|
+
supportsMarkdown: false,
|
|
42
|
+
supportsStreaming: true,
|
|
43
|
+
supportsRichCards: false,
|
|
44
|
+
maxMessageLength: Infinity,
|
|
45
|
+
},
|
|
32
46
|
};
|
|
33
47
|
const DEFAULT_CAPABILITIES = {
|
|
34
48
|
channel: "cli",
|
|
49
|
+
senseType: "local",
|
|
35
50
|
availableIntegrations: [],
|
|
36
51
|
supportsMarkdown: false,
|
|
37
52
|
supportsStreaming: false,
|
|
@@ -47,3 +62,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
62
|
});
|
|
48
63
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
64
|
}
|
|
65
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
66
|
+
function isRemoteChannel(capabilities) {
|
|
67
|
+
const senseType = capabilities?.senseType;
|
|
68
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
72
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
73
|
+
*/
|
|
74
|
+
function getAlwaysOnSenseNames() {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
component: "channels",
|
|
77
|
+
event: "channel.always_on_lookup",
|
|
78
|
+
message: "always-on sense names lookup",
|
|
79
|
+
meta: {},
|
|
80
|
+
});
|
|
81
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
82
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
83
|
+
.map(([channel]) => channel);
|
|
84
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertGroupContextParticipants = upsertGroupContextParticipants;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
6
|
+
const CURRENT_SCHEMA_VERSION = 1;
|
|
7
|
+
function normalizeDisplayName(externalId, displayName) {
|
|
8
|
+
const trimmed = displayName?.trim();
|
|
9
|
+
return trimmed && trimmed.length > 0 ? trimmed : externalId;
|
|
10
|
+
}
|
|
11
|
+
function buildNameNotes(name, now) {
|
|
12
|
+
return name !== "Unknown"
|
|
13
|
+
? { name: { value: name, savedAt: now } }
|
|
14
|
+
: {};
|
|
15
|
+
}
|
|
16
|
+
function dedupeParticipants(participants) {
|
|
17
|
+
const deduped = new Map();
|
|
18
|
+
for (const participant of participants) {
|
|
19
|
+
const externalId = participant.externalId.trim();
|
|
20
|
+
if (!externalId)
|
|
21
|
+
continue;
|
|
22
|
+
const key = `${participant.provider}:${externalId}`;
|
|
23
|
+
if (!deduped.has(key)) {
|
|
24
|
+
deduped.set(key, {
|
|
25
|
+
...participant,
|
|
26
|
+
externalId,
|
|
27
|
+
displayName: participant.displayName?.trim() || undefined,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return Array.from(deduped.values());
|
|
32
|
+
}
|
|
33
|
+
function createGroupExternalId(provider, groupExternalId, linkedAt) {
|
|
34
|
+
return {
|
|
35
|
+
provider,
|
|
36
|
+
externalId: groupExternalId,
|
|
37
|
+
linkedAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function shouldPromoteToAcquaintance(friend) {
|
|
41
|
+
return (friend.trustLevel ?? "stranger") === "stranger";
|
|
42
|
+
}
|
|
43
|
+
function createAcquaintanceRecord(participant, groupExternalId, linkedAt) {
|
|
44
|
+
const name = normalizeDisplayName(participant.externalId, participant.displayName);
|
|
45
|
+
return {
|
|
46
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
47
|
+
name,
|
|
48
|
+
role: "acquaintance",
|
|
49
|
+
trustLevel: "acquaintance",
|
|
50
|
+
connections: [],
|
|
51
|
+
externalIds: [
|
|
52
|
+
{
|
|
53
|
+
provider: participant.provider,
|
|
54
|
+
externalId: participant.externalId,
|
|
55
|
+
linkedAt,
|
|
56
|
+
},
|
|
57
|
+
createGroupExternalId(participant.provider, groupExternalId, linkedAt),
|
|
58
|
+
],
|
|
59
|
+
tenantMemberships: [],
|
|
60
|
+
toolPreferences: {},
|
|
61
|
+
notes: buildNameNotes(name, linkedAt),
|
|
62
|
+
totalTokens: 0,
|
|
63
|
+
createdAt: linkedAt,
|
|
64
|
+
updatedAt: linkedAt,
|
|
65
|
+
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function upsertGroupContextParticipants(input) {
|
|
69
|
+
(0, runtime_1.emitNervesEvent)({
|
|
70
|
+
component: "friends",
|
|
71
|
+
event: "friends.group_context_upsert_start",
|
|
72
|
+
message: "upserting shared-group participant context",
|
|
73
|
+
meta: {
|
|
74
|
+
participantCount: input.participants.length,
|
|
75
|
+
hasGroupExternalId: input.groupExternalId.trim().length > 0,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const groupExternalId = input.groupExternalId.trim();
|
|
79
|
+
if (!groupExternalId) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const now = input.now ?? (() => new Date().toISOString());
|
|
83
|
+
const participants = dedupeParticipants(input.participants);
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const participant of participants) {
|
|
86
|
+
const linkedAt = now();
|
|
87
|
+
const existing = await input.store.findByExternalId(participant.provider, participant.externalId);
|
|
88
|
+
if (!existing) {
|
|
89
|
+
const created = createAcquaintanceRecord(participant, groupExternalId, linkedAt);
|
|
90
|
+
await input.store.put(created.id, created);
|
|
91
|
+
results.push({
|
|
92
|
+
friendId: created.id,
|
|
93
|
+
name: created.name,
|
|
94
|
+
trustLevel: "acquaintance",
|
|
95
|
+
created: true,
|
|
96
|
+
updated: false,
|
|
97
|
+
addedGroupExternalId: true,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const hasGroupExternalId = existing.externalIds.some((externalId) => externalId.externalId === groupExternalId);
|
|
102
|
+
const promoteToAcquaintance = shouldPromoteToAcquaintance(existing);
|
|
103
|
+
const trustLevel = promoteToAcquaintance
|
|
104
|
+
? "acquaintance"
|
|
105
|
+
: existing.trustLevel;
|
|
106
|
+
const role = promoteToAcquaintance
|
|
107
|
+
? "acquaintance"
|
|
108
|
+
: existing.role;
|
|
109
|
+
const updatedExternalIds = hasGroupExternalId
|
|
110
|
+
? existing.externalIds
|
|
111
|
+
: [...existing.externalIds, createGroupExternalId(participant.provider, groupExternalId, linkedAt)];
|
|
112
|
+
const updated = promoteToAcquaintance || !hasGroupExternalId;
|
|
113
|
+
const record = updated
|
|
114
|
+
? {
|
|
115
|
+
...existing,
|
|
116
|
+
role,
|
|
117
|
+
trustLevel,
|
|
118
|
+
externalIds: updatedExternalIds,
|
|
119
|
+
updatedAt: linkedAt,
|
|
120
|
+
}
|
|
121
|
+
: existing;
|
|
122
|
+
if (updated) {
|
|
123
|
+
await input.store.put(record.id, record);
|
|
124
|
+
}
|
|
125
|
+
results.push({
|
|
126
|
+
friendId: record.id,
|
|
127
|
+
name: record.name,
|
|
128
|
+
trustLevel,
|
|
129
|
+
created: false,
|
|
130
|
+
updated,
|
|
131
|
+
addedGroupExternalId: !hasGroupExternalId,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "friends",
|
|
136
|
+
event: "friends.group_context_upsert_end",
|
|
137
|
+
message: "upserted shared-group participant context",
|
|
138
|
+
meta: {
|
|
139
|
+
participantCount: participants.length,
|
|
140
|
+
updatedCount: results.filter((result) => result.created || result.updated).length,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
@@ -100,6 +100,25 @@ class FileFriendStore {
|
|
|
100
100
|
}
|
|
101
101
|
return entries.some((entry) => entry.endsWith(".json"));
|
|
102
102
|
}
|
|
103
|
+
async listAll() {
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = await fsPromises.readdir(this.friendsPath);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const records = [];
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (!entry.endsWith(".json"))
|
|
114
|
+
continue;
|
|
115
|
+
const raw = await this.readJson(path.join(this.friendsPath, entry));
|
|
116
|
+
if (!raw)
|
|
117
|
+
continue;
|
|
118
|
+
records.push(this.normalize(raw));
|
|
119
|
+
}
|
|
120
|
+
return records;
|
|
121
|
+
}
|
|
103
122
|
normalize(raw) {
|
|
104
123
|
const trustLevel = raw.trustLevel;
|
|
105
124
|
const normalizedTrustLevel = trustLevel === "family" ||
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeTrustContext = describeTrustContext;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
function findRelatedGroupId(friend) {
|
|
6
|
+
return friend.externalIds.find((externalId) => externalId.externalId.startsWith("group:"))?.externalId;
|
|
7
|
+
}
|
|
8
|
+
function resolveLevel(friend) {
|
|
9
|
+
return friend.trustLevel ?? "stranger";
|
|
10
|
+
}
|
|
11
|
+
function describeTrustContext(input) {
|
|
12
|
+
const level = resolveLevel(input.friend);
|
|
13
|
+
const relatedGroupId = findRelatedGroupId(input.friend);
|
|
14
|
+
const explanation = level === "family" || level === "friend"
|
|
15
|
+
? {
|
|
16
|
+
level,
|
|
17
|
+
basis: "direct",
|
|
18
|
+
summary: level === "family"
|
|
19
|
+
? "direct family trust"
|
|
20
|
+
: "direct trusted relationship",
|
|
21
|
+
why: "this relationship is directly trusted rather than inferred through a shared group or cold first contact.",
|
|
22
|
+
permits: [
|
|
23
|
+
"local operations when appropriate",
|
|
24
|
+
"proactive follow-through",
|
|
25
|
+
"full collaborative problem solving",
|
|
26
|
+
],
|
|
27
|
+
constraints: [],
|
|
28
|
+
}
|
|
29
|
+
: level === "acquaintance"
|
|
30
|
+
? {
|
|
31
|
+
level,
|
|
32
|
+
basis: "shared_group",
|
|
33
|
+
summary: relatedGroupId
|
|
34
|
+
? "known through the shared project group"
|
|
35
|
+
: "known through a shared group context",
|
|
36
|
+
why: relatedGroupId
|
|
37
|
+
? `this trust comes from the shared group context ${relatedGroupId}, not from direct endorsement.`
|
|
38
|
+
: "this trust comes from shared group context rather than direct endorsement.",
|
|
39
|
+
permits: [
|
|
40
|
+
"group-safe coordination",
|
|
41
|
+
"normal conversation inside the shared context",
|
|
42
|
+
],
|
|
43
|
+
constraints: [
|
|
44
|
+
"guarded local actions",
|
|
45
|
+
"do not assume broad private authority",
|
|
46
|
+
],
|
|
47
|
+
relatedGroupId,
|
|
48
|
+
}
|
|
49
|
+
: {
|
|
50
|
+
level,
|
|
51
|
+
basis: "unknown",
|
|
52
|
+
summary: "truly unknown first-contact context",
|
|
53
|
+
why: "this person is not known through direct trust or a shared group context.",
|
|
54
|
+
permits: [
|
|
55
|
+
"safe first-contact orientation only",
|
|
56
|
+
],
|
|
57
|
+
constraints: [
|
|
58
|
+
"first contact does not reach the full model on open channels",
|
|
59
|
+
"no local or privileged actions",
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
(0, runtime_1.emitNervesEvent)({
|
|
63
|
+
component: "friends",
|
|
64
|
+
event: "friends.trust_explained",
|
|
65
|
+
message: "built explicit trust explanation",
|
|
66
|
+
meta: {
|
|
67
|
+
channel: input.channel,
|
|
68
|
+
level: explanation.level,
|
|
69
|
+
basis: explanation.basis,
|
|
70
|
+
hasRelatedGroup: Boolean(explanation.relatedGroupId),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return explanation;
|
|
74
|
+
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Context kernel type definitions.
|
|
3
3
|
// FriendRecord (merged identity + memory), channel capabilities, and resolved context.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.TRUSTED_LEVELS = void 0;
|
|
5
6
|
exports.isIdentityProvider = isIdentityProvider;
|
|
6
7
|
exports.isIntegration = isIntegration;
|
|
8
|
+
exports.isTrustedLevel = isTrustedLevel;
|
|
7
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
8
10
|
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle"]);
|
|
9
11
|
function isIdentityProvider(value) {
|
|
@@ -19,3 +21,9 @@ const INTEGRATIONS = new Set(["ado", "github", "graph"]);
|
|
|
19
21
|
function isIntegration(value) {
|
|
20
22
|
return typeof value === "string" && INTEGRATIONS.has(value);
|
|
21
23
|
}
|
|
24
|
+
/** Trust levels that grant full tool access and proactive send capability. */
|
|
25
|
+
exports.TRUSTED_LEVELS = new Set(["family", "friend"]);
|
|
26
|
+
/** Whether a trust level grants full access (family or friend). Defaults to "friend" for legacy records. */
|
|
27
|
+
function isTrustedLevel(trustLevel) {
|
|
28
|
+
return exports.TRUSTED_LEVELS.has(trustLevel ?? "friend");
|
|
29
|
+
}
|
package/dist/mind/memory.js
CHANGED
|
@@ -33,7 +33,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.__memoryTestUtils = void 0;
|
|
37
36
|
exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
|
|
38
37
|
exports.appendFactsWithDedup = appendFactsWithDedup;
|
|
39
38
|
exports.readMemoryFacts = readMemoryFacts;
|
|
@@ -46,7 +45,9 @@ const crypto_1 = require("crypto");
|
|
|
46
45
|
const config_1 = require("../heart/config");
|
|
47
46
|
const identity_1 = require("../heart/identity");
|
|
48
47
|
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
const associative_recall_1 = require("./associative-recall");
|
|
49
49
|
const DEDUP_THRESHOLD = 0.6;
|
|
50
|
+
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
50
51
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
51
52
|
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
52
53
|
class OpenAIEmbeddingProvider {
|
|
@@ -120,9 +121,16 @@ function readExistingFacts(factsPath) {
|
|
|
120
121
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
121
122
|
if (!raw)
|
|
122
123
|
return [];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
const facts = [];
|
|
125
|
+
for (const line of raw.split("\n")) {
|
|
126
|
+
try {
|
|
127
|
+
facts.push(JSON.parse(line));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return facts;
|
|
126
134
|
}
|
|
127
135
|
function readEntityIndex(entitiesPath) {
|
|
128
136
|
if (!fs.existsSync(entitiesPath))
|
|
@@ -170,13 +178,24 @@ function appendDailyFact(dailyDir, fact) {
|
|
|
170
178
|
const dayPath = path.join(dailyDir, `${day}.jsonl`);
|
|
171
179
|
fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
|
|
172
180
|
}
|
|
173
|
-
function appendFactsWithDedup(stores, incoming) {
|
|
181
|
+
function appendFactsWithDedup(stores, incoming, options) {
|
|
174
182
|
const existing = readExistingFacts(stores.factsPath);
|
|
175
183
|
const all = [...existing];
|
|
176
184
|
let added = 0;
|
|
177
185
|
let skipped = 0;
|
|
186
|
+
const semanticThreshold = options?.semanticThreshold;
|
|
178
187
|
for (const fact of incoming) {
|
|
179
|
-
const duplicate = all.some((prior) =>
|
|
188
|
+
const duplicate = all.some((prior) => {
|
|
189
|
+
if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
|
|
190
|
+
return true;
|
|
191
|
+
if (semanticThreshold !== undefined &&
|
|
192
|
+
Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
|
|
193
|
+
Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
|
|
194
|
+
fact.embedding.length === prior.embedding.length) {
|
|
195
|
+
return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
});
|
|
180
199
|
if (duplicate) {
|
|
181
200
|
skipped++;
|
|
182
201
|
continue;
|
|
@@ -195,24 +214,6 @@ function appendFactsWithDedup(stores, incoming) {
|
|
|
195
214
|
});
|
|
196
215
|
return { added, skipped };
|
|
197
216
|
}
|
|
198
|
-
function cosineSimilarity(left, right) {
|
|
199
|
-
if (left.length === 0 || right.length === 0 || left.length !== right.length)
|
|
200
|
-
return 0;
|
|
201
|
-
let dot = 0;
|
|
202
|
-
let leftNorm = 0;
|
|
203
|
-
let rightNorm = 0;
|
|
204
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
205
|
-
dot += left[i] * right[i];
|
|
206
|
-
leftNorm += left[i] * left[i];
|
|
207
|
-
rightNorm += right[i] * right[i];
|
|
208
|
-
}
|
|
209
|
-
if (leftNorm === 0 || rightNorm === 0)
|
|
210
|
-
return 0;
|
|
211
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
212
|
-
}
|
|
213
|
-
exports.__memoryTestUtils = {
|
|
214
|
-
cosineSimilarity,
|
|
215
|
-
};
|
|
216
217
|
function createDefaultEmbeddingProvider() {
|
|
217
218
|
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
218
219
|
if (!apiKey)
|
|
@@ -264,7 +265,7 @@ async function saveMemoryFact(options) {
|
|
|
264
265
|
createdAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
265
266
|
embedding,
|
|
266
267
|
};
|
|
267
|
-
return appendFactsWithDedup(stores, [fact]);
|
|
268
|
+
return appendFactsWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
|
|
268
269
|
}
|
|
269
270
|
async function backfillEmbeddings(options) {
|
|
270
271
|
const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
|
|
@@ -365,7 +366,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
|
|
|
365
366
|
.filter((fact) => fact.embedding.length === queryEmbedding.length)
|
|
366
367
|
.map((fact) => ({
|
|
367
368
|
fact,
|
|
368
|
-
score: cosineSimilarity(queryEmbedding, fact.embedding),
|
|
369
|
+
score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
|
|
369
370
|
}))
|
|
370
371
|
.filter((entry) => entry.score > 0)
|
|
371
372
|
.sort((left, right) => right.score - left.score)
|
package/dist/mind/pending.js
CHANGED
|
@@ -33,24 +33,60 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.INNER_DIALOG_PENDING = void 0;
|
|
36
37
|
exports.getPendingDir = getPendingDir;
|
|
38
|
+
exports.getDeferredReturnDir = getDeferredReturnDir;
|
|
39
|
+
exports.getInnerDialogPendingDir = getInnerDialogPendingDir;
|
|
40
|
+
exports.hasPendingMessages = hasPendingMessages;
|
|
41
|
+
exports.queuePendingMessage = queuePendingMessage;
|
|
42
|
+
exports.enqueueDeferredReturn = enqueueDeferredReturn;
|
|
43
|
+
exports.drainDeferredReturns = drainDeferredReturns;
|
|
37
44
|
exports.drainPending = drainPending;
|
|
38
45
|
const fs = __importStar(require("fs"));
|
|
39
46
|
const path = __importStar(require("path"));
|
|
40
|
-
const
|
|
47
|
+
const identity_1 = require("../heart/identity");
|
|
41
48
|
const runtime_1 = require("../nerves/runtime");
|
|
42
49
|
function getPendingDir(agentName, friendId, channel, key) {
|
|
43
|
-
return path.join(
|
|
50
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending", friendId, channel, key);
|
|
44
51
|
}
|
|
45
|
-
function
|
|
52
|
+
function getDeferredReturnDir(agentName, friendId) {
|
|
53
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending-returns", friendId);
|
|
54
|
+
}
|
|
55
|
+
/** Canonical inner-dialog pending path segments. */
|
|
56
|
+
exports.INNER_DIALOG_PENDING = { friendId: "self", channel: "inner", key: "dialog" };
|
|
57
|
+
/** Returns the pending dir for this agent's inner dialog. */
|
|
58
|
+
function getInnerDialogPendingDir(agentName) {
|
|
59
|
+
return getPendingDir(agentName, exports.INNER_DIALOG_PENDING.friendId, exports.INNER_DIALOG_PENDING.channel, exports.INNER_DIALOG_PENDING.key);
|
|
60
|
+
}
|
|
61
|
+
function hasPendingMessages(pendingDir) {
|
|
46
62
|
if (!fs.existsSync(pendingDir))
|
|
47
|
-
return
|
|
63
|
+
return false;
|
|
64
|
+
try {
|
|
65
|
+
return fs.readdirSync(pendingDir).some((entry) => entry.endsWith(".json") || entry.endsWith(".json.processing"));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeQueueFile(queueDir, message) {
|
|
72
|
+
fs.mkdirSync(queueDir, { recursive: true });
|
|
73
|
+
const fileName = `${message.timestamp}-${Math.random().toString(36).slice(2, 10)}.json`;
|
|
74
|
+
const filePath = path.join(queueDir, fileName);
|
|
75
|
+
fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
|
|
76
|
+
return filePath;
|
|
77
|
+
}
|
|
78
|
+
function queuePendingMessage(pendingDir, message) {
|
|
79
|
+
writeQueueFile(pendingDir, message);
|
|
80
|
+
}
|
|
81
|
+
function drainQueue(queueDir) {
|
|
82
|
+
if (!fs.existsSync(queueDir))
|
|
83
|
+
return { messages: [], recovered: 0 };
|
|
48
84
|
let entries;
|
|
49
85
|
try {
|
|
50
|
-
entries = fs.readdirSync(
|
|
86
|
+
entries = fs.readdirSync(queueDir);
|
|
51
87
|
}
|
|
52
88
|
catch {
|
|
53
|
-
return [];
|
|
89
|
+
return { messages: [], recovered: 0 };
|
|
54
90
|
}
|
|
55
91
|
// Collect both .json (new) and .processing (crash recovery)
|
|
56
92
|
const jsonFiles = entries.filter(f => f.endsWith(".json") && !f.endsWith(".processing"));
|
|
@@ -62,9 +98,9 @@ function drainPending(pendingDir) {
|
|
|
62
98
|
].sort((a, b) => a.file.localeCompare(b.file));
|
|
63
99
|
const messages = [];
|
|
64
100
|
for (const { file, needsRename } of allFiles) {
|
|
65
|
-
const srcPath = path.join(
|
|
101
|
+
const srcPath = path.join(queueDir, file);
|
|
66
102
|
const processingPath = needsRename
|
|
67
|
-
? path.join(
|
|
103
|
+
? path.join(queueDir, file + ".processing")
|
|
68
104
|
: srcPath;
|
|
69
105
|
try {
|
|
70
106
|
if (needsRename) {
|
|
@@ -83,11 +119,42 @@ function drainPending(pendingDir) {
|
|
|
83
119
|
catch { /* ignore */ }
|
|
84
120
|
}
|
|
85
121
|
}
|
|
122
|
+
return {
|
|
123
|
+
messages,
|
|
124
|
+
recovered: processingFiles.length,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function enqueueDeferredReturn(agentName, friendId, message) {
|
|
128
|
+
const queueDir = getDeferredReturnDir(agentName, friendId);
|
|
129
|
+
const filePath = writeQueueFile(queueDir, message);
|
|
130
|
+
(0, runtime_1.emitNervesEvent)({
|
|
131
|
+
event: "mind.deferred_return_enqueued",
|
|
132
|
+
component: "mind",
|
|
133
|
+
message: "deferred return queued for later friend delivery",
|
|
134
|
+
meta: { friendId, queueDir },
|
|
135
|
+
});
|
|
136
|
+
return filePath;
|
|
137
|
+
}
|
|
138
|
+
function drainDeferredReturns(agentName, friendId) {
|
|
139
|
+
const queueDir = getDeferredReturnDir(agentName, friendId);
|
|
140
|
+
const { messages } = drainQueue(queueDir);
|
|
141
|
+
(0, runtime_1.emitNervesEvent)({
|
|
142
|
+
event: "mind.deferred_returns_drained",
|
|
143
|
+
component: "mind",
|
|
144
|
+
message: "deferred friend returns drained",
|
|
145
|
+
meta: { friendId, queueDir, count: messages.length },
|
|
146
|
+
});
|
|
147
|
+
return messages;
|
|
148
|
+
}
|
|
149
|
+
function drainPending(pendingDir) {
|
|
150
|
+
if (!fs.existsSync(pendingDir))
|
|
151
|
+
return [];
|
|
152
|
+
const { messages, recovered } = drainQueue(pendingDir);
|
|
86
153
|
(0, runtime_1.emitNervesEvent)({
|
|
87
154
|
event: "mind.pending_drained",
|
|
88
155
|
component: "mind",
|
|
89
156
|
message: "pending queue drained",
|
|
90
|
-
meta: { pendingDir, count: messages.length, recovered
|
|
157
|
+
meta: { pendingDir, count: messages.length, recovered },
|
|
91
158
|
});
|
|
92
159
|
return messages;
|
|
93
160
|
}
|
package/dist/mind/phrases.js
CHANGED