@ouro.bot/cli 0.1.0-alpha.669 → 0.1.0-alpha.670

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.
@@ -1,187 +0,0 @@
1
- "use strict";
2
- // FriendResolver -- resolves external identity into a FriendRecord + channel capabilities.
3
- // Created per-request (per-incoming-message), per-friend.
4
- // Replaces the old ContextResolver: no authority checker, no separate note resolution.
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.FriendResolver = void 0;
7
- exports._setMachineOwnerUsernameForTest = _setMachineOwnerUsernameForTest;
8
- exports.machineOwnerUsername = machineOwnerUsername;
9
- exports.isLocalMachineOwnerIdentity = isLocalMachineOwnerIdentity;
10
- const crypto_1 = require("crypto");
11
- const os_1 = require("os");
12
- const channel_1 = require("./channel");
13
- const runtime_1 = require("../../nerves/runtime");
14
- const CURRENT_SCHEMA_VERSION = 1;
15
- // Test seam: when set (including to null), overrides OS detection of the
16
- // machine-owner username so resolver tests are deterministic.
17
- let machineOwnerOverride;
18
- function _setMachineOwnerUsernameForTest(value) {
19
- machineOwnerOverride = value;
20
- }
21
- /**
22
- * The OS username that owns this daemon process, or null if undetectable. The
23
- * person running the daemon owns this agent + its bundle, so the local friend
24
- * that names them is the machine owner (family), not a stranger.
25
- */
26
- function machineOwnerUsername() {
27
- if (machineOwnerOverride !== undefined)
28
- return machineOwnerOverride;
29
- try {
30
- return (0, os_1.userInfo)().username;
31
- }
32
- catch {
33
- /* v8 ignore next -- defensive: userInfo() only throws when the running user has no passwd entry @preserve */
34
- return null;
35
- }
36
- }
37
- /**
38
- * True when (provider, externalId) names the local machine owner — the OS user
39
- * running the daemon. Matches the bare username or a `user@host` external id.
40
- */
41
- function isLocalMachineOwnerIdentity(provider, externalId, ownerUsername) {
42
- if (provider !== "local" || !ownerUsername)
43
- return false;
44
- return externalId === ownerUsername || externalId.startsWith(`${ownerUsername}@`);
45
- }
46
- class FriendResolver {
47
- store;
48
- params;
49
- constructor(store, params) {
50
- this.store = store;
51
- this.params = params;
52
- }
53
- async resolve() {
54
- const friend = await this.resolveOrCreate();
55
- const channel = (0, channel_1.getChannelCapabilities)(this.params.channel);
56
- return { friend, channel };
57
- }
58
- async resolveOrCreate() {
59
- // Try to find existing friend by external ID
60
- let existing = null;
61
- try {
62
- existing = await this.store.findByExternalId(this.params.provider, this.params.externalId, this.params.tenantId);
63
- }
64
- catch {
65
- // Store search failure -- fall through to create new (D16)
66
- }
67
- if (existing)
68
- return existing;
69
- // Migration: local provider previously used "${username}@${hostname}" format.
70
- // If no exact match, try finding a friend with old-format external ID.
71
- /* v8 ignore start -- migration path: only fires when legacy hostname-format friend exists @preserve */
72
- if (this.params.provider === "local" && !this.params.externalId.includes("@")) {
73
- try {
74
- const all = typeof this.store.listAll === "function" ? await this.store.listAll() : [];
75
- /* v8 ignore start -- migration path: only fires when legacy hostname-format friend exists @preserve */
76
- const migrationMatch = all.find((f) => f.externalIds.some((eid) => eid.provider === "local" && eid.externalId.startsWith(this.params.externalId + "@")));
77
- if (migrationMatch) {
78
- const now = new Date().toISOString();
79
- migrationMatch.externalIds.push({
80
- provider: this.params.provider,
81
- externalId: this.params.externalId,
82
- linkedAt: now,
83
- });
84
- migrationMatch.updatedAt = now;
85
- try {
86
- await this.store.put(migrationMatch.id, migrationMatch);
87
- }
88
- catch {
89
- // best-effort persist
90
- }
91
- (0, runtime_1.emitNervesEvent)({
92
- component: "friends",
93
- event: "friends.local_id_migrated",
94
- message: `migrated local friend identity from hostname format to username-only`,
95
- meta: { friendId: migrationMatch.id, newExternalId: this.params.externalId },
96
- });
97
- return migrationMatch;
98
- }
99
- /* v8 ignore stop */
100
- }
101
- catch {
102
- // fall through to create new
103
- }
104
- }
105
- /* v8 ignore stop */
106
- // First encounter -- create new FriendRecord
107
- const now = new Date().toISOString();
108
- const externalId = {
109
- provider: this.params.provider,
110
- externalId: this.params.externalId,
111
- linkedAt: now,
112
- ...(this.params.tenantId !== undefined ? { tenantId: this.params.tenantId } : {}),
113
- };
114
- const tenantMemberships = this.params.tenantId ? [this.params.tenantId] : [];
115
- let hasAnyFriends = false;
116
- try {
117
- if (typeof this.store.hasAnyFriends === "function") {
118
- hasAnyFriends = await this.store.hasAnyFriends();
119
- }
120
- }
121
- catch {
122
- hasAnyFriends = false;
123
- }
124
- const isFirstImprint = !hasAnyFriends;
125
- const isA2AAgent = this.params.provider === "a2a-agent";
126
- // The local friend that names the OS user running the daemon is the machine
127
- // owner (family) — they own the agent + its bundle. Usually this friend already
128
- // exists as a family/primary hatch imprint; this covers the un-imprinted boss
129
- // path (e.g. a Workbench boss check-in on a bundle that skipped imprint).
130
- const isLocalMachineOwner = isLocalMachineOwnerIdentity(this.params.provider, this.params.externalId, machineOwnerUsername());
131
- // BlueBubbles group chats route through here as `imessage-handle` with an
132
- // externalId of the form `group:any;+;<chatHash>`. When the harness auto-
133
- // creates the group friend at stranger trust, we mark the record so that
134
- // the trust gate can surface the relationship for explicit acknowledgment
135
- // later instead of letting messages accumulate silently.
136
- const isImessageGroup = this.params.provider === "imessage-handle" &&
137
- typeof this.params.externalId === "string" &&
138
- this.params.externalId.startsWith("group:");
139
- const notes = {};
140
- if (this.params.displayName !== "Unknown") {
141
- notes.name = { value: this.params.displayName, savedAt: now };
142
- }
143
- if (isImessageGroup && !isFirstImprint) {
144
- notes.autoCreatedGroup = { value: "true", savedAt: now };
145
- }
146
- const friend = {
147
- id: (0, crypto_1.randomUUID)(),
148
- name: this.params.displayName,
149
- role: isA2AAgent ? "agent-peer" : isFirstImprint ? "primary" : isLocalMachineOwner ? "family" : "stranger",
150
- trustLevel: isA2AAgent ? "stranger" : (isFirstImprint || isLocalMachineOwner) ? "family" : "stranger",
151
- connections: [],
152
- externalIds: [externalId],
153
- tenantMemberships,
154
- toolPreferences: {},
155
- notes,
156
- totalTokens: 0,
157
- createdAt: now,
158
- updatedAt: now,
159
- schemaVersion: CURRENT_SCHEMA_VERSION,
160
- kind: isA2AAgent ? "agent" : "human",
161
- ...(isA2AAgent ? {
162
- agentMeta: {
163
- bundleName: this.params.displayName,
164
- familiarity: 0,
165
- sharedMissions: [],
166
- outcomes: [],
167
- a2a: { agentId: this.params.externalId },
168
- },
169
- } : {}),
170
- };
171
- // Persist -- log and continue on failure (D16)
172
- try {
173
- await this.store.put(friend.id, friend);
174
- }
175
- catch (err) {
176
- (0, runtime_1.emitNervesEvent)({
177
- level: "error",
178
- event: "friends.persist_error",
179
- component: "friends",
180
- message: "failed to persist friend record",
181
- meta: { reason: err instanceof Error ? err.message : String(err) },
182
- });
183
- }
184
- return friend;
185
- }
186
- }
187
- exports.FriendResolver = FriendResolver;
@@ -1,247 +0,0 @@
1
- "use strict";
2
- // FileFriendStore -- filesystem adapter for FriendStore.
3
- // Stores each friend as one unified JSON file in bundle `friends/`.
4
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
- if (k2 === undefined) k2 = k;
6
- var desc = Object.getOwnPropertyDescriptor(m, k);
7
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
- desc = { enumerable: true, get: function() { return m[k]; } };
9
- }
10
- Object.defineProperty(o, k2, desc);
11
- }) : (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- o[k2] = m[k];
14
- }));
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v });
17
- }) : function(o, v) {
18
- o["default"] = v;
19
- });
20
- var __importStar = (this && this.__importStar) || (function () {
21
- var ownKeys = function(o) {
22
- ownKeys = Object.getOwnPropertyNames || function (o) {
23
- var ar = [];
24
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
- return ar;
26
- };
27
- return ownKeys(o);
28
- };
29
- return function (mod) {
30
- if (mod && mod.__esModule) return mod;
31
- var result = {};
32
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
- __setModuleDefault(result, mod);
34
- return result;
35
- };
36
- })();
37
- Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.FileFriendStore = void 0;
39
- const fs = __importStar(require("fs"));
40
- const fsPromises = __importStar(require("fs/promises"));
41
- const path = __importStar(require("path"));
42
- const session_events_1 = require("../../heart/session-events");
43
- const runtime_1 = require("../../nerves/runtime");
44
- const DEFAULT_ROLE = "friend";
45
- const DEFAULT_TRUST_LEVEL = "friend";
46
- class FileFriendStore {
47
- friendsPath;
48
- constructor(friendsPath) {
49
- this.friendsPath = friendsPath;
50
- fs.mkdirSync(friendsPath, { recursive: true });
51
- (0, runtime_1.emitNervesEvent)({
52
- component: "friends",
53
- event: "friends.store_init",
54
- message: "file friend store initialized",
55
- meta: {},
56
- });
57
- }
58
- async get(id) {
59
- // Direct UUID lookup
60
- const record = await this.readJson(path.join(this.friendsPath, `${id}.json`));
61
- if (record)
62
- return this.normalize(record);
63
- // Fallback: if id is a name (not UUID), scan for matching friend
64
- /* v8 ignore start -- name fallback: exercised by live proactive sends @preserve */
65
- try {
66
- const entries = await fsPromises.readdir(this.friendsPath);
67
- for (const entry of entries) {
68
- if (!entry.endsWith(".json"))
69
- continue;
70
- const raw = await this.readJson(path.join(this.friendsPath, entry));
71
- if (!raw)
72
- continue;
73
- const normalized = this.normalize(raw);
74
- if (normalized.name?.toLowerCase() === id.toLowerCase()) {
75
- return normalized;
76
- }
77
- }
78
- }
79
- catch { /* directory unreadable — return null */ }
80
- /* v8 ignore stop */
81
- return null;
82
- }
83
- async put(id, record) {
84
- await this.writeJson(path.join(this.friendsPath, `${id}.json`), this.normalize(record));
85
- }
86
- async delete(id) {
87
- await this.removeFile(path.join(this.friendsPath, `${id}.json`));
88
- }
89
- async findByExternalId(provider, externalId, tenantId) {
90
- let entries;
91
- try {
92
- entries = await fsPromises.readdir(this.friendsPath);
93
- }
94
- catch {
95
- return null;
96
- }
97
- for (const entry of entries) {
98
- if (!entry.endsWith(".json"))
99
- continue;
100
- const raw = await this.readJson(path.join(this.friendsPath, entry));
101
- if (!raw)
102
- continue;
103
- const record = this.normalize(raw);
104
- const match = record.externalIds.some((ext) => ext.provider === provider &&
105
- ext.externalId === externalId &&
106
- (tenantId === undefined || ext.tenantId === tenantId));
107
- if (match) {
108
- return record;
109
- }
110
- }
111
- return null;
112
- }
113
- async hasAnyFriends() {
114
- let entries;
115
- try {
116
- entries = await fsPromises.readdir(this.friendsPath);
117
- }
118
- catch {
119
- return false;
120
- }
121
- return entries.some((entry) => entry.endsWith(".json"));
122
- }
123
- async listAll() {
124
- let entries;
125
- try {
126
- entries = await fsPromises.readdir(this.friendsPath);
127
- }
128
- catch {
129
- return [];
130
- }
131
- const records = [];
132
- for (const entry of entries) {
133
- if (!entry.endsWith(".json"))
134
- continue;
135
- const raw = await this.readJson(path.join(this.friendsPath, entry));
136
- if (!raw)
137
- continue;
138
- records.push(this.normalize(raw));
139
- }
140
- return records;
141
- }
142
- normalize(raw) {
143
- const trustLevel = raw.trustLevel;
144
- const normalizedTrustLevel = trustLevel === "family" ||
145
- trustLevel === "friend" ||
146
- trustLevel === "acquaintance" ||
147
- trustLevel === "stranger"
148
- ? trustLevel
149
- : DEFAULT_TRUST_LEVEL;
150
- const kind = raw.kind === "human" || raw.kind === "agent" ? raw.kind : "human";
151
- const agentMeta = kind === "agent" ? this.normalizeAgentMeta(raw.agentMeta) : undefined;
152
- return {
153
- id: raw.id,
154
- name: raw.name,
155
- role: typeof raw.role === "string" && raw.role.trim() ? raw.role : DEFAULT_ROLE,
156
- trustLevel: normalizedTrustLevel,
157
- connections: Array.isArray(raw.connections)
158
- ? raw.connections
159
- .filter((connection) => (typeof connection === "object" &&
160
- connection !== null &&
161
- typeof connection.name === "string" &&
162
- typeof connection.relationship === "string"))
163
- .map((connection) => ({
164
- name: connection.name,
165
- relationship: connection.relationship,
166
- }))
167
- : [],
168
- externalIds: Array.isArray(raw.externalIds) ? raw.externalIds : [],
169
- tenantMemberships: Array.isArray(raw.tenantMemberships) ? raw.tenantMemberships : [],
170
- toolPreferences: raw.toolPreferences && typeof raw.toolPreferences === "object"
171
- ? raw.toolPreferences
172
- : {},
173
- notes: raw.notes && typeof raw.notes === "object" ? raw.notes : {},
174
- totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
175
- createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
176
- updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
177
- schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
178
- kind,
179
- agentMeta,
180
- };
181
- }
182
- normalizeAgentMeta(raw) {
183
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
184
- return undefined;
185
- const meta = raw;
186
- if (typeof meta.bundleName !== "string")
187
- return undefined;
188
- return {
189
- bundleName: meta.bundleName,
190
- familiarity: typeof meta.familiarity === "number" ? meta.familiarity : 0,
191
- sharedMissions: Array.isArray(meta.sharedMissions) ? meta.sharedMissions : [],
192
- outcomes: Array.isArray(meta.outcomes) ? meta.outcomes : [],
193
- ...(this.normalizeA2AMeta(meta.a2a) ? { a2a: this.normalizeA2AMeta(meta.a2a) } : {}),
194
- };
195
- }
196
- normalizeA2AMeta(raw) {
197
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
198
- return undefined;
199
- const meta = raw;
200
- const a2a = {
201
- ...(typeof meta.cardUrl === "string" ? { cardUrl: meta.cardUrl } : {}),
202
- ...(typeof meta.endpointUrl === "string" ? { endpointUrl: meta.endpointUrl } : {}),
203
- ...(typeof meta.agentId === "string" ? { agentId: meta.agentId } : {}),
204
- ...(typeof meta.protocolVersion === "string" ? { protocolVersion: meta.protocolVersion } : {}),
205
- };
206
- return Object.keys(a2a).length > 0 ? a2a : undefined;
207
- }
208
- async readJson(filePath) {
209
- try {
210
- const raw = await fsPromises.readFile(filePath, "utf-8");
211
- try {
212
- const parsed = JSON.parse(raw);
213
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
214
- return null;
215
- }
216
- return parsed;
217
- }
218
- catch {
219
- return null;
220
- }
221
- }
222
- catch {
223
- return null;
224
- }
225
- }
226
- async writeJson(filePath, data) {
227
- const notes = Object.fromEntries(Object.entries(data.notes).map(([key, note]) => [
228
- key,
229
- {
230
- ...note,
231
- value: (0, session_events_1.capStructuredRecordString)(note.value),
232
- },
233
- ]));
234
- await fsPromises.writeFile(filePath, JSON.stringify({ ...data, notes }, null, 2), "utf-8");
235
- }
236
- async removeFile(filePath) {
237
- try {
238
- await fsPromises.unlink(filePath);
239
- }
240
- catch (err) {
241
- if (err?.code === "ENOENT")
242
- return;
243
- throw err;
244
- }
245
- }
246
- }
247
- exports.FileFriendStore = FileFriendStore;
@@ -1,4 +0,0 @@
1
- "use strict";
2
- // Friend store abstraction.
3
- // All friend persistence goes through FriendStore -- no friend module imports `fs` directly.
4
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,26 +0,0 @@
1
- "use strict";
2
- // Token accumulation helper.
3
- // Tracks cumulative token usage per friend across turns.
4
- // Called from both CLI and Teams adapters after each agent turn.
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.accumulateFriendTokens = accumulateFriendTokens;
7
- const runtime_1 = require("../../nerves/runtime");
8
- async function accumulateFriendTokens(store, friendId, usage) {
9
- if (!usage?.output_tokens)
10
- return;
11
- const record = await store.get(friendId);
12
- if (!record)
13
- return;
14
- // Only count output tokens (what the model generated for this friend).
15
- // Input tokens are mostly system prompt re-sent every turn -- counting them
16
- // would inflate the total and make the onboarding threshold meaningless.
17
- record.totalTokens = (record.totalTokens ?? 0) + usage.output_tokens;
18
- record.updatedAt = new Date().toISOString();
19
- await store.put(record.id, record);
20
- (0, runtime_1.emitNervesEvent)({
21
- component: "friends",
22
- event: "friends.tokens_accumulated",
23
- message: "tokens accumulated for friend",
24
- meta: { friendId, outputTokens: usage.output_tokens },
25
- });
26
- }
@@ -1,74 +0,0 @@
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
- }
@@ -1,29 +0,0 @@
1
- "use strict";
2
- // Context kernel type definitions.
3
- // FriendRecord (merged identity + notes), channel capabilities, and resolved context.
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.TRUSTED_LEVELS = void 0;
6
- exports.isIdentityProvider = isIdentityProvider;
7
- exports.isIntegration = isIntegration;
8
- exports.isTrustedLevel = isTrustedLevel;
9
- const runtime_1 = require("../../nerves/runtime");
10
- const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle", "email-address", "a2a-agent"]);
11
- function isIdentityProvider(value) {
12
- (0, runtime_1.emitNervesEvent)({
13
- component: "friends",
14
- event: "friends.identity_provider_check",
15
- message: "identity provider validation",
16
- meta: {},
17
- });
18
- return typeof value === "string" && IDENTITY_PROVIDERS.has(value);
19
- }
20
- const INTEGRATIONS = new Set(["ado", "github", "graph"]);
21
- function isIntegration(value) {
22
- return typeof value === "string" && INTEGRATIONS.has(value);
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
- }