@ouro.bot/friends 0.1.0-alpha.4
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/LICENSE +201 -0
- package/README.md +514 -0
- package/changelog.json +34 -0
- package/dist/a2a/index.d.ts +102 -0
- package/dist/a2a/index.js +198 -0
- package/dist/agent-peer.d.ts +17 -0
- package/dist/agent-peer.js +57 -0
- package/dist/channel.d.ts +11 -0
- package/dist/channel.js +132 -0
- package/dist/consent.d.ts +34 -0
- package/dist/consent.js +62 -0
- package/dist/coordination.d.ts +100 -0
- package/dist/coordination.js +255 -0
- package/dist/file-bundle.d.ts +12 -0
- package/dist/file-bundle.js +23 -0
- package/dist/grant-store-file.d.ts +16 -0
- package/dist/grant-store-file.js +136 -0
- package/dist/grant-store.d.ts +7 -0
- package/dist/grant-store.js +8 -0
- package/dist/grants.d.ts +39 -0
- package/dist/grants.js +84 -0
- package/dist/group-context.d.ts +21 -0
- package/dist/group-context.js +144 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +105 -0
- package/dist/link-identity.d.ts +14 -0
- package/dist/link-identity.js +88 -0
- package/dist/mcp/bin.d.ts +2 -0
- package/dist/mcp/bin.js +16 -0
- package/dist/mcp/dispatch.d.ts +14 -0
- package/dist/mcp/dispatch.js +432 -0
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.js +14 -0
- package/dist/mcp/run-main.d.ts +7 -0
- package/dist/mcp/run-main.js +45 -0
- package/dist/mcp/schemas.d.ts +10 -0
- package/dist/mcp/schemas.js +398 -0
- package/dist/mcp/server.d.ts +21 -0
- package/dist/mcp/server.js +194 -0
- package/dist/mission-share.d.ts +94 -0
- package/dist/mission-share.js +232 -0
- package/dist/mission-store-file.d.ts +18 -0
- package/dist/mission-store-file.js +153 -0
- package/dist/mission-store.d.ts +10 -0
- package/dist/mission-store.js +9 -0
- package/dist/missions.d.ts +31 -0
- package/dist/missions.js +98 -0
- package/dist/notes.d.ts +11 -0
- package/dist/notes.js +90 -0
- package/dist/observability.d.ts +27 -0
- package/dist/observability.js +31 -0
- package/dist/outcomes.d.ts +9 -0
- package/dist/outcomes.js +51 -0
- package/dist/resolver.d.ts +28 -0
- package/dist/resolver.js +187 -0
- package/dist/results.d.ts +8 -0
- package/dist/results.js +2 -0
- package/dist/room.d.ts +22 -0
- package/dist/room.js +40 -0
- package/dist/share.d.ts +106 -0
- package/dist/share.js +223 -0
- package/dist/standing.d.ts +83 -0
- package/dist/standing.js +111 -0
- package/dist/store-file.d.ts +21 -0
- package/dist/store-file.js +264 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.js +4 -0
- package/dist/tokens.d.ts +8 -0
- package/dist/tokens.js +26 -0
- package/dist/trust-explanation.d.ts +16 -0
- package/dist/trust-explanation.js +74 -0
- package/dist/trust-mutation.d.ts +4 -0
- package/dist/trust-mutation.js +29 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.js +51 -0
- package/dist/util/cap-string.d.ts +7 -0
- package/dist/util/cap-string.js +35 -0
- package/dist/verifier.d.ts +11 -0
- package/dist/verifier.js +29 -0
- package/dist/whoami.d.ts +7 -0
- package/dist/whoami.js +39 -0
- package/package.json +68 -0
|
@@ -0,0 +1,264 @@
|
|
|
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 cap_string_1 = require("./util/cap-string");
|
|
43
|
+
const observability_1 = require("./observability");
|
|
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, observability_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
|
+
// Imported facts (the cross-agent share namespace) are preserved verbatim
|
|
175
|
+
// when present. Absent on records that have never imported anything.
|
|
176
|
+
...(raw.importedNotes && typeof raw.importedNotes === "object" && !Array.isArray(raw.importedNotes)
|
|
177
|
+
? { importedNotes: raw.importedNotes }
|
|
178
|
+
: {}),
|
|
179
|
+
totalTokens: typeof raw.totalTokens === "number" ? raw.totalTokens : 0,
|
|
180
|
+
createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
|
|
181
|
+
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
|
|
182
|
+
schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
|
|
183
|
+
kind,
|
|
184
|
+
agentMeta,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
normalizeAgentMeta(raw) {
|
|
188
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
189
|
+
return undefined;
|
|
190
|
+
const meta = raw;
|
|
191
|
+
if (typeof meta.bundleName !== "string")
|
|
192
|
+
return undefined;
|
|
193
|
+
return {
|
|
194
|
+
bundleName: meta.bundleName,
|
|
195
|
+
familiarity: typeof meta.familiarity === "number" ? meta.familiarity : 0,
|
|
196
|
+
sharedMissions: Array.isArray(meta.sharedMissions) ? meta.sharedMissions : [],
|
|
197
|
+
outcomes: Array.isArray(meta.outcomes) ? meta.outcomes : [],
|
|
198
|
+
...(this.normalizeA2AMeta(meta.a2a) ? { a2a: this.normalizeA2AMeta(meta.a2a) } : {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
normalizeA2AMeta(raw) {
|
|
202
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
203
|
+
return undefined;
|
|
204
|
+
const meta = raw;
|
|
205
|
+
const mailbox = this.normalizeMailbox(meta.mailbox);
|
|
206
|
+
const a2a = {
|
|
207
|
+
...(typeof meta.cardUrl === "string" ? { cardUrl: meta.cardUrl } : {}),
|
|
208
|
+
...(typeof meta.endpointUrl === "string" ? { endpointUrl: meta.endpointUrl } : {}),
|
|
209
|
+
...(typeof meta.agentId === "string" ? { agentId: meta.agentId } : {}),
|
|
210
|
+
...(typeof meta.protocolVersion === "string" ? { protocolVersion: meta.protocolVersion } : {}),
|
|
211
|
+
...(mailbox ? { mailbox } : {}),
|
|
212
|
+
};
|
|
213
|
+
return Object.keys(a2a).length > 0 ? a2a : undefined;
|
|
214
|
+
}
|
|
215
|
+
/** Preserve an additive a2a.mailbox coord only when both fields are strings;
|
|
216
|
+
* otherwise drop it (absent ⇒ unchanged — the additive guarantee). */
|
|
217
|
+
normalizeMailbox(raw) {
|
|
218
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
219
|
+
return undefined;
|
|
220
|
+
const m = raw;
|
|
221
|
+
if (typeof m.repo !== "string" || typeof m.selfOutboxAgentId !== "string")
|
|
222
|
+
return undefined;
|
|
223
|
+
return { repo: m.repo, selfOutboxAgentId: m.selfOutboxAgentId };
|
|
224
|
+
}
|
|
225
|
+
async readJson(filePath) {
|
|
226
|
+
try {
|
|
227
|
+
const raw = await fsPromises.readFile(filePath, "utf-8");
|
|
228
|
+
try {
|
|
229
|
+
const parsed = JSON.parse(raw);
|
|
230
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return parsed;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async writeJson(filePath, data) {
|
|
244
|
+
const notes = Object.fromEntries(Object.entries(data.notes).map(([key, note]) => [
|
|
245
|
+
key,
|
|
246
|
+
{
|
|
247
|
+
...note,
|
|
248
|
+
value: (0, cap_string_1.capStructuredRecordString)(note.value),
|
|
249
|
+
},
|
|
250
|
+
]));
|
|
251
|
+
await fsPromises.writeFile(filePath, JSON.stringify({ ...data, notes }, null, 2), "utf-8");
|
|
252
|
+
}
|
|
253
|
+
async removeFile(filePath) {
|
|
254
|
+
try {
|
|
255
|
+
await fsPromises.unlink(filePath);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
if (err?.code === "ENOENT")
|
|
259
|
+
return;
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.FileFriendStore = FileFriendStore;
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FriendRecord } from "./types";
|
|
2
|
+
export interface FriendStore {
|
|
3
|
+
get(id: string): Promise<FriendRecord | null>;
|
|
4
|
+
put(id: string, record: FriendRecord): Promise<void>;
|
|
5
|
+
delete(id: string): Promise<void>;
|
|
6
|
+
findByExternalId(provider: string, externalId: string, tenantId?: string): Promise<FriendRecord | null>;
|
|
7
|
+
hasAnyFriends?(): Promise<boolean>;
|
|
8
|
+
listAll?(): Promise<FriendRecord[]>;
|
|
9
|
+
}
|
package/dist/store.js
ADDED
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FriendStore } from "./store";
|
|
2
|
+
export interface UsageData {
|
|
3
|
+
input_tokens: number;
|
|
4
|
+
output_tokens: number;
|
|
5
|
+
reasoning_tokens: number;
|
|
6
|
+
total_tokens: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function accumulateFriendTokens(store: FriendStore, friendId: string, usage?: UsageData): Promise<void>;
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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 observability_1 = require("./observability");
|
|
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, observability_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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Channel, FriendRecord, TrustLevel } from "./types";
|
|
2
|
+
export type TrustBasis = "direct" | "shared_group" | "unknown";
|
|
3
|
+
export interface TrustExplanation {
|
|
4
|
+
level: TrustLevel;
|
|
5
|
+
basis: TrustBasis;
|
|
6
|
+
summary: string;
|
|
7
|
+
why: string;
|
|
8
|
+
permits: string[];
|
|
9
|
+
constraints: string[];
|
|
10
|
+
relatedGroupId?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function describeTrustContext(input: {
|
|
13
|
+
friend: FriendRecord;
|
|
14
|
+
channel: Channel;
|
|
15
|
+
isGroupChat?: boolean;
|
|
16
|
+
}): TrustExplanation;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeTrustContext = describeTrustContext;
|
|
4
|
+
const observability_1 = require("./observability");
|
|
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, observability_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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setFriendTrust = setFriendTrust;
|
|
4
|
+
// setFriendTrust — structured-result port of the harness's `friend.update`.
|
|
5
|
+
//
|
|
6
|
+
// Sets a friend's trust level. Mirrors the harness behavior of writing BOTH
|
|
7
|
+
// `trustLevel` and `role` to the same level (so the record's coarse role tracks
|
|
8
|
+
// its trust). A missing friend is a normal `not_found` result, never a throw.
|
|
9
|
+
const observability_1 = require("./observability");
|
|
10
|
+
async function setFriendTrust(store, friendId, level) {
|
|
11
|
+
(0, observability_1.emitNervesEvent)({
|
|
12
|
+
component: "friends",
|
|
13
|
+
event: "friends.trust_set",
|
|
14
|
+
message: "set friend trust",
|
|
15
|
+
meta: { level },
|
|
16
|
+
});
|
|
17
|
+
const current = await store.get(friendId);
|
|
18
|
+
if (!current) {
|
|
19
|
+
return { ok: false, status: "not_found", message: "friend record not found" };
|
|
20
|
+
}
|
|
21
|
+
const updated = {
|
|
22
|
+
...current,
|
|
23
|
+
trustLevel: level,
|
|
24
|
+
role: level,
|
|
25
|
+
updatedAt: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
await store.put(friendId, updated);
|
|
28
|
+
return { ok: true, status: "updated", record: updated };
|
|
29
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type IdentityProvider = "aad" | "local" | "teams-conversation" | "imessage-handle" | "email-address" | "a2a-agent";
|
|
2
|
+
export declare function isIdentityProvider(value: unknown): value is IdentityProvider;
|
|
3
|
+
export type Channel = "cli" | "teams" | "bluebubbles" | "mail" | "voice" | "a2a" | "inner" | "mcp";
|
|
4
|
+
export type Integration = "ado" | "github" | "graph";
|
|
5
|
+
export declare function isIntegration(value: unknown): value is Integration;
|
|
6
|
+
export interface ExternalId {
|
|
7
|
+
provider: IdentityProvider;
|
|
8
|
+
externalId: string;
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
linkedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export type TrustLevel = "family" | "friend" | "acquaintance" | "stranger";
|
|
13
|
+
/** Trust levels that grant full tool access and proactive send capability. */
|
|
14
|
+
export declare const TRUSTED_LEVELS: ReadonlySet<TrustLevel>;
|
|
15
|
+
/** Whether a trust level grants full access (family or friend). Defaults to "friend" for legacy records. */
|
|
16
|
+
export declare function isTrustedLevel(trustLevel?: TrustLevel): boolean;
|
|
17
|
+
export interface FriendConnection {
|
|
18
|
+
name: string;
|
|
19
|
+
relationship: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentAttribution {
|
|
22
|
+
agentId?: string;
|
|
23
|
+
agentName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface NoteProvenance {
|
|
26
|
+
assertedBy?: AgentAttribution;
|
|
27
|
+
/** Provenance origin. Absent ⇒ treat as "first_party". An "imported" fact came
|
|
28
|
+
* from another agent's share and must never be laundered into first-party. */
|
|
29
|
+
origin?: "first_party" | "imported";
|
|
30
|
+
/** ISO timestamp at which an imported fact was accepted. Set only on imports. */
|
|
31
|
+
importedAt?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ImportedNote {
|
|
34
|
+
value: string;
|
|
35
|
+
importedAt: string;
|
|
36
|
+
assertedBy?: AgentAttribution;
|
|
37
|
+
originallyAssertedBy?: AgentAttribution;
|
|
38
|
+
}
|
|
39
|
+
export type ShareScope = "name" | "identity" | "notes:safe" | "notes:all" | "outcomes" | "mission" | "coordinate";
|
|
40
|
+
export declare function isShareScope(value: unknown): value is ShareScope;
|
|
41
|
+
/** Identity-only scopes expose the join key (externalIds + name), never note
|
|
42
|
+
* content. The tiered consent policy gates these on trust alone. `coordinate`
|
|
43
|
+
* (brick 5) joins them: a coordination message names a mission by its join key
|
|
44
|
+
* and carries no note content, so trust ≥ friend consents to it — a friend peer
|
|
45
|
+
* may be asked to take a mission without a per-mission content grant. */
|
|
46
|
+
export declare const IDENTITY_SCOPES: ReadonlySet<ShareScope>;
|
|
47
|
+
export interface ShareGrant {
|
|
48
|
+
id: string;
|
|
49
|
+
subjectKey: string;
|
|
50
|
+
recipientAgentId: string;
|
|
51
|
+
scope: ShareScope;
|
|
52
|
+
grantedAt: string;
|
|
53
|
+
/** Optional ISO expiry. A grant past its expiry no longer consents. */
|
|
54
|
+
expiresAt?: string;
|
|
55
|
+
/** Set when the grant has been revoked. A revoked grant no longer consents. */
|
|
56
|
+
revokedAt?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface RelationshipOutcome {
|
|
59
|
+
missionId: string;
|
|
60
|
+
result: "success" | "partial" | "failed";
|
|
61
|
+
timestamp: string;
|
|
62
|
+
note?: string;
|
|
63
|
+
provenance?: NoteProvenance;
|
|
64
|
+
}
|
|
65
|
+
export type MissionKey = string;
|
|
66
|
+
export interface MissionLearning {
|
|
67
|
+
value: string;
|
|
68
|
+
savedAt: string;
|
|
69
|
+
provenance?: NoteProvenance;
|
|
70
|
+
shareable?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface ImportedLearning {
|
|
73
|
+
value: string;
|
|
74
|
+
importedAt: string;
|
|
75
|
+
assertedBy?: AgentAttribution;
|
|
76
|
+
originallyAssertedBy?: AgentAttribution;
|
|
77
|
+
}
|
|
78
|
+
export type CoordinationIntent = "request" | "offer" | "accept" | "decline" | "handoff";
|
|
79
|
+
export declare function isCoordinationIntent(value: unknown): value is CoordinationIntent;
|
|
80
|
+
export interface CoordinationLogEntry {
|
|
81
|
+
intent: CoordinationIntent;
|
|
82
|
+
fromAgentId: string;
|
|
83
|
+
note?: string;
|
|
84
|
+
at: string;
|
|
85
|
+
provenance?: NoteProvenance;
|
|
86
|
+
}
|
|
87
|
+
export interface MissionCoordination {
|
|
88
|
+
assignee?: AgentAttribution;
|
|
89
|
+
assignedAt?: string;
|
|
90
|
+
log: CoordinationLogEntry[];
|
|
91
|
+
}
|
|
92
|
+
export interface MissionRecord {
|
|
93
|
+
id: string;
|
|
94
|
+
missionKey: MissionKey;
|
|
95
|
+
title: string;
|
|
96
|
+
status: "active" | "succeeded" | "partial" | "failed" | "abandoned";
|
|
97
|
+
participants: AgentAttribution[];
|
|
98
|
+
outcomes: RelationshipOutcome[];
|
|
99
|
+
learnings: Record<string, MissionLearning>;
|
|
100
|
+
importedLearnings?: Record<string, Record<string, ImportedLearning>>;
|
|
101
|
+
coordination?: MissionCoordination;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
updatedAt: string;
|
|
104
|
+
schemaVersion: number;
|
|
105
|
+
}
|
|
106
|
+
export interface AgentMeta {
|
|
107
|
+
bundleName: string;
|
|
108
|
+
familiarity: number;
|
|
109
|
+
sharedMissions: string[];
|
|
110
|
+
outcomes: RelationshipOutcome[];
|
|
111
|
+
a2a?: {
|
|
112
|
+
cardUrl?: string;
|
|
113
|
+
endpointUrl?: string;
|
|
114
|
+
agentId?: string;
|
|
115
|
+
protocolVersion?: string;
|
|
116
|
+
/** Optional A2A git-mailbox coordinates (brick two). The mailbox is a
|
|
117
|
+
* dedicated PRIVATE repo holding only in-flight envelopes; `selfOutboxAgentId`
|
|
118
|
+
* is the dir THIS peer writes its outbox under. Absent on records that don't
|
|
119
|
+
* use the git-mailbox transport. */
|
|
120
|
+
mailbox?: {
|
|
121
|
+
repo: string;
|
|
122
|
+
selfOutboxAgentId: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export interface FriendRecord {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
role?: string;
|
|
130
|
+
trustLevel?: TrustLevel;
|
|
131
|
+
connections?: FriendConnection[];
|
|
132
|
+
externalIds: ExternalId[];
|
|
133
|
+
tenantMemberships: string[];
|
|
134
|
+
toolPreferences: Record<string, string>;
|
|
135
|
+
notes: Record<string, {
|
|
136
|
+
value: string;
|
|
137
|
+
savedAt: string;
|
|
138
|
+
provenance?: NoteProvenance;
|
|
139
|
+
shareable?: boolean;
|
|
140
|
+
}>;
|
|
141
|
+
importedNotes?: Record<string, Record<string, ImportedNote>>;
|
|
142
|
+
totalTokens: number;
|
|
143
|
+
createdAt: string;
|
|
144
|
+
updatedAt: string;
|
|
145
|
+
schemaVersion: number;
|
|
146
|
+
kind?: "human" | "agent";
|
|
147
|
+
agentMeta?: AgentMeta;
|
|
148
|
+
}
|
|
149
|
+
export type SenseType = "open" | "closed" | "local" | "internal";
|
|
150
|
+
export interface ChannelCapabilities {
|
|
151
|
+
channel: Channel;
|
|
152
|
+
senseType: SenseType;
|
|
153
|
+
availableIntegrations: Integration[];
|
|
154
|
+
supportsMarkdown: boolean;
|
|
155
|
+
supportsStreaming: boolean;
|
|
156
|
+
supportsRichCards: boolean;
|
|
157
|
+
maxMessageLength: number;
|
|
158
|
+
}
|
|
159
|
+
export interface ResolvedContext {
|
|
160
|
+
readonly friend: FriendRecord;
|
|
161
|
+
readonly channel: ChannelCapabilities;
|
|
162
|
+
/** Whether the current conversation is a group chat (vs 1:1). Default false. */
|
|
163
|
+
readonly isGroupChat?: boolean;
|
|
164
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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.IDENTITY_SCOPES = exports.TRUSTED_LEVELS = void 0;
|
|
6
|
+
exports.isIdentityProvider = isIdentityProvider;
|
|
7
|
+
exports.isIntegration = isIntegration;
|
|
8
|
+
exports.isTrustedLevel = isTrustedLevel;
|
|
9
|
+
exports.isShareScope = isShareScope;
|
|
10
|
+
exports.isCoordinationIntent = isCoordinationIntent;
|
|
11
|
+
const observability_1 = require("./observability");
|
|
12
|
+
const IDENTITY_PROVIDERS = new Set(["aad", "local", "teams-conversation", "imessage-handle", "email-address", "a2a-agent"]);
|
|
13
|
+
function isIdentityProvider(value) {
|
|
14
|
+
(0, observability_1.emitNervesEvent)({
|
|
15
|
+
component: "friends",
|
|
16
|
+
event: "friends.identity_provider_check",
|
|
17
|
+
message: "identity provider validation",
|
|
18
|
+
meta: {},
|
|
19
|
+
});
|
|
20
|
+
return typeof value === "string" && IDENTITY_PROVIDERS.has(value);
|
|
21
|
+
}
|
|
22
|
+
const INTEGRATIONS = new Set(["ado", "github", "graph"]);
|
|
23
|
+
function isIntegration(value) {
|
|
24
|
+
return typeof value === "string" && INTEGRATIONS.has(value);
|
|
25
|
+
}
|
|
26
|
+
/** Trust levels that grant full tool access and proactive send capability. */
|
|
27
|
+
exports.TRUSTED_LEVELS = new Set(["family", "friend"]);
|
|
28
|
+
/** Whether a trust level grants full access (family or friend). Defaults to "friend" for legacy records. */
|
|
29
|
+
function isTrustedLevel(trustLevel) {
|
|
30
|
+
return exports.TRUSTED_LEVELS.has(trustLevel ?? "friend");
|
|
31
|
+
}
|
|
32
|
+
const SHARE_SCOPES = new Set(["name", "identity", "notes:safe", "notes:all", "outcomes", "mission", "coordinate"]);
|
|
33
|
+
function isShareScope(value) {
|
|
34
|
+
return typeof value === "string" && SHARE_SCOPES.has(value);
|
|
35
|
+
}
|
|
36
|
+
/** Identity-only scopes expose the join key (externalIds + name), never note
|
|
37
|
+
* content. The tiered consent policy gates these on trust alone. `coordinate`
|
|
38
|
+
* (brick 5) joins them: a coordination message names a mission by its join key
|
|
39
|
+
* and carries no note content, so trust ≥ friend consents to it — a friend peer
|
|
40
|
+
* may be asked to take a mission without a per-mission content grant. */
|
|
41
|
+
exports.IDENTITY_SCOPES = new Set(["name", "identity", "coordinate"]);
|
|
42
|
+
const COORDINATION_INTENTS = new Set([
|
|
43
|
+
"request",
|
|
44
|
+
"offer",
|
|
45
|
+
"accept",
|
|
46
|
+
"decline",
|
|
47
|
+
"handoff",
|
|
48
|
+
]);
|
|
49
|
+
function isCoordinationIntent(value) {
|
|
50
|
+
return typeof value === "string" && COORDINATION_INTENTS.has(value);
|
|
51
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const EVENT_CONTENT_MAX_CHARS: number;
|
|
2
|
+
export declare function truncateLargeEventContent(content: unknown, maxChars: number): {
|
|
3
|
+
content: unknown;
|
|
4
|
+
truncated: boolean;
|
|
5
|
+
originalLength: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function capStructuredRecordString(value: string): string;
|