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

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/changelog.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.671",
6
+ "changes": [
7
+ "Consolidate friend-domain record logic into @ouro.bot/friends: re-pin the git dependency and route save_friend_note, the friend update/link/unlink CLI commands, and A2A peer onboarding through the package's applyFriendNote/setFriendTrust/linkExternalId/unlinkExternalId/upsertAgentPeer helpers, removing the duplicated in-harness record-mutation logic while keeping the MCP tool wrappers, CLI framing, and A2A HTTP card-fetch glue."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.670",
6
12
  "changes": [
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.onboardA2APeer = onboardA2APeer;
37
- const node_crypto_1 = require("node:crypto");
38
37
  const path = __importStar(require("node:path"));
39
38
  const identity_1 = require("../heart/identity");
40
39
  const runtime_1 = require("../nerves/runtime");
@@ -52,60 +51,33 @@ function agentIdFor(_card, cardUrl) {
52
51
  return parsed.toString();
53
52
  }
54
53
  async function onboardA2APeer(options) {
54
+ // HTTP/URL glue stays harness-side: fetch the agent card, resolve its JSON-RPC
55
+ // endpoint, and derive the stable agentId. The record-shaping (mint/update the
56
+ // agent-peer friend) is delegated to @ouro.bot/friends' upsertAgentPeer.
55
57
  const card = await (0, client_1.fetchA2AAgentCard)(options.cardUrl, options.fetchImpl);
56
58
  const store = storeFor(options);
57
- const now = new Date().toISOString();
58
59
  const externalId = agentIdFor(card, options.cardUrl);
59
60
  /* v8 ignore next -- fetchA2AAgentCard validates a usable endpoint or legacy url before returning a card @preserve */
60
61
  const endpointUrl = (0, client_1.endpointForCard)(card) ?? options.cardUrl;
61
62
  const protocolVersion = card.supportedInterfaces?.find((entry) => entry.url === endpointUrl)?.protocolVersion
62
63
  ?? card.protocolVersion;
63
- const existing = await store.findByExternalId("a2a-agent", externalId);
64
64
  const name = options.name ?? card.name;
65
- const trustLevel = options.trustLevel ?? existing?.trustLevel ?? "acquaintance";
66
- const baseMeta = existing?.agentMeta ?? {
67
- bundleName: name,
68
- familiarity: 0,
69
- sharedMissions: [],
70
- outcomes: [],
71
- };
72
- const record = {
73
- ...(existing ?? {
74
- id: (0, node_crypto_1.randomUUID)(),
75
- createdAt: now,
76
- externalIds: [],
77
- tenantMemberships: [],
78
- toolPreferences: {},
79
- notes: {},
80
- totalTokens: 0,
81
- schemaVersion: 1,
82
- }),
65
+ const record = await (0, friends_1.upsertAgentPeer)(store, {
83
66
  name,
84
- role: "agent-peer",
85
- trustLevel,
86
- kind: "agent",
87
- agentMeta: {
88
- ...baseMeta,
89
- bundleName: baseMeta.bundleName || name,
90
- a2a: {
91
- cardUrl: options.cardUrl,
92
- endpointUrl,
93
- agentId: externalId,
94
- protocolVersion,
95
- },
67
+ agentId: externalId,
68
+ ...(options.trustLevel ? { trustLevel: options.trustLevel } : {}),
69
+ a2a: {
70
+ cardUrl: options.cardUrl,
71
+ endpointUrl,
72
+ agentId: externalId,
73
+ protocolVersion,
96
74
  },
97
- externalIds: [
98
- ...(existing?.externalIds.filter((id) => !(id.provider === "a2a-agent" && id.externalId === externalId)) ?? []),
99
- { provider: "a2a-agent", externalId, linkedAt: now },
100
- ],
101
- updatedAt: now,
102
- };
103
- await store.put(record.id, record);
75
+ });
104
76
  (0, runtime_1.emitNervesEvent)({
105
77
  component: "friends",
106
78
  event: "friends.a2a_peer_onboarded",
107
79
  message: "onboarded A2A peer into friend model",
108
- meta: { agentName: options.agentName, friendId: record.id, peerName: record.name, trustLevel },
80
+ meta: { agentName: options.agentName, friendId: record.id, peerName: record.name, trustLevel: record.trustLevel },
109
81
  });
110
82
  return record;
111
83
  }
@@ -5419,14 +5419,6 @@ async function performSystemSetup(deps) {
5419
5419
  await registerOuroBundleTypeNonBlocking(deps);
5420
5420
  }
5421
5421
  // ── Friend command execution ──
5422
- const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
5423
- /* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
5424
- function higherTrust(a, b) {
5425
- const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
5426
- const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
5427
- return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
5428
- }
5429
- /* v8 ignore stop */
5430
5422
  async function executeFriendCommand(command, store) {
5431
5423
  if (command.kind === "friend.list") {
5432
5424
  const listAll = store.listAll;
@@ -5467,65 +5459,32 @@ async function executeFriendCommand(command, store) {
5467
5459
  return `created: ${id} (${command.name}, ${trustLevel})`;
5468
5460
  }
5469
5461
  if (command.kind === "friend.update") {
5470
- const current = await store.get(command.friendId);
5471
- if (!current)
5462
+ const result = await (0, friends_1.setFriendTrust)(store, command.friendId, command.trustLevel);
5463
+ if (result.status === "not_found")
5472
5464
  return `friend not found: ${command.friendId}`;
5473
- const now = new Date().toISOString();
5474
- await store.put(command.friendId, {
5475
- ...current,
5476
- trustLevel: command.trustLevel,
5477
- role: command.trustLevel,
5478
- updatedAt: now,
5479
- });
5480
5465
  return `updated: ${command.friendId} → trust=${command.trustLevel}`;
5481
5466
  }
5482
5467
  if (command.kind === "friend.link") {
5483
- const current = await store.get(command.friendId);
5484
- if (!current)
5468
+ const result = await (0, friends_1.linkExternalId)(store, command.friendId, {
5469
+ provider: command.provider,
5470
+ externalId: command.externalId,
5471
+ });
5472
+ if (result.status === "not_found")
5485
5473
  return `friend not found: ${command.friendId}`;
5486
- const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
5487
- if (alreadyLinked)
5474
+ if (result.status === "noop")
5488
5475
  return `identity already linked: ${command.provider}:${command.externalId}`;
5489
- const now = new Date().toISOString();
5490
- const newExternalIds = [
5491
- ...current.externalIds,
5492
- { provider: command.provider, externalId: command.externalId, linkedAt: now },
5493
- ];
5494
- // Orphan cleanup: check if another friend has this externalId
5495
- const orphan = await store.findByExternalId(command.provider, command.externalId);
5496
- let mergeMessage = "";
5497
- let mergedNotes = { ...current.notes };
5498
- let mergedTrust = current.trustLevel;
5499
- let orphanExternalIds = [];
5500
- if (orphan && orphan.id !== command.friendId) {
5501
- // Merge orphan's notes (target's notes take priority)
5502
- mergedNotes = { ...orphan.notes, ...current.notes };
5503
- // Keep higher trust level
5504
- mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
5505
- // Collect orphan's other externalIds (excluding the one being linked)
5506
- orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
5507
- await store.delete(orphan.id);
5508
- mergeMessage = ` (merged orphan ${orphan.id})`;
5509
- }
5510
- await store.put(command.friendId, {
5511
- ...current,
5512
- externalIds: [...newExternalIds, ...orphanExternalIds],
5513
- notes: mergedNotes,
5514
- trustLevel: mergedTrust,
5515
- updatedAt: now,
5516
- });
5476
+ const mergeMessage = result.status === "merged" ? " (merged orphan)" : "";
5517
5477
  return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
5518
5478
  }
5519
5479
  // command.kind === "friend.unlink"
5520
- const current = await store.get(command.friendId);
5521
- if (!current)
5480
+ const result = await (0, friends_1.unlinkExternalId)(store, command.friendId, {
5481
+ provider: command.provider,
5482
+ externalId: command.externalId,
5483
+ });
5484
+ if (result.status === "not_found")
5522
5485
  return `friend not found: ${command.friendId}`;
5523
- const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
5524
- if (idx === -1)
5486
+ if (result.status === "noop")
5525
5487
  return `identity not linked: ${command.provider}:${command.externalId}`;
5526
- const now = new Date().toISOString();
5527
- const filtered = current.externalIds.filter((_, i) => i !== idx);
5528
- await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
5529
5488
  return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
5530
5489
  }
5531
5490
  async function executeA2ACommand(command, deps) {
@@ -40,6 +40,7 @@ const skills_1 = require("./skills");
40
40
  const config_1 = require("../heart/config");
41
41
  const runtime_1 = require("../nerves/runtime");
42
42
  const identity_1 = require("../heart/identity");
43
+ const friends_1 = require("@ouro.bot/friends");
43
44
  const diary_1 = require("../mind/diary");
44
45
  const record_paths_1 = require("../mind/record-paths");
45
46
  const provenance_trust_1 = require("../mind/provenance-trust");
@@ -414,7 +415,8 @@ exports.notesToolDefinitions = [
414
415
  const friendId = ctx.context.friend?.id;
415
416
  if (!friendId)
416
417
  return "i can't save notes -- no friend identity available";
417
- // Validate parameters
418
+ // Validate parameters (input-shape guards stay harness-side so we keep the
419
+ // first-person tool wording; applyFriendNote does the record mutation).
418
420
  if (!a.content)
419
421
  return "i need a content value to save";
420
422
  const validTypes = ["name", "tool_preference", "note"];
@@ -422,44 +424,33 @@ exports.notesToolDefinitions = [
422
424
  return `i don't recognize type '${a.type}' -- use name, tool_preference, or note`;
423
425
  if ((a.type === "tool_preference" || a.type === "note") && !a.key)
424
426
  return "i need a key for tool_preference or note type";
425
- try {
426
- // Read fresh record from disk
427
- const record = await ctx.friendStore.get(friendId);
428
- if (!record)
429
- return "i can't find the friend record on disk";
430
- const isOverride = a.override === "true";
431
- if (a.type === "name") {
432
- const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
433
- await ctx.friendStore.put(friendId, updated);
434
- return `saved: name = ${a.content}`;
435
- }
436
- if (a.type === "tool_preference") {
437
- const existing = record.toolPreferences[a.key];
438
- if (existing && !isOverride) {
439
- return `i already have a preference for '${a.key}': "${existing}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
440
- }
441
- const updated = { ...record, toolPreferences: { ...record.toolPreferences, [a.key]: a.content }, updatedAt: new Date().toISOString() };
442
- await ctx.friendStore.put(friendId, updated);
443
- return `saved: toolPreference ${a.key} = ${a.content}`;
444
- }
445
- // type === "note"
446
- // Redirect "name" key to name field
447
- if (a.key === "name") {
448
- const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
449
- await ctx.friendStore.put(friendId, updated);
427
+ // Record-domain logic lives in @ouro.bot/friends; this wrapper maps the
428
+ // structured FriendOpResult back to the tool's first-person strings.
429
+ const result = await (0, friends_1.applyFriendNote)(ctx.friendStore, friendId, {
430
+ type: a.type,
431
+ ...(a.key !== undefined ? { key: a.key } : {}),
432
+ content: a.content,
433
+ override: a.override === "true",
434
+ });
435
+ switch (result.status) {
436
+ case "saved":
437
+ if (a.type === "name")
438
+ return `saved: name = ${a.content}`;
439
+ if (a.type === "tool_preference")
440
+ return `saved: toolPreference ${a.key} = ${a.content}`;
441
+ return `saved: note ${a.key} = ${a.content}`;
442
+ case "redirected_to_name":
450
443
  return `updated friend's name to '${a.content}' (stored as name, not a note)`;
451
- }
452
- const existing = record.notes[a.key];
453
- if (existing && !isOverride) {
454
- return `i already have a note for '${a.key}': "${existing.value}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
455
- }
456
- const updated = { ...record, notes: { ...record.notes, [a.key]: { value: a.content, savedAt: new Date().toISOString() } }, updatedAt: new Date().toISOString() };
457
- await ctx.friendStore.put(friendId, updated);
458
- return `saved: note ${a.key} = ${a.content}`;
459
- }
460
- catch (err) {
461
- /* v8 ignore next -- defensive: non-Error branch for String(err) @preserve */
462
- return `error saving note: ${err instanceof Error ? err.message : String(err)}`;
444
+ case "override_required":
445
+ // result.message embeds the existing value; append the harness merge instruction.
446
+ return `${result.message}. if you want to replace it, call again with override: true. or merge both values into content and override.`;
447
+ case "not_found":
448
+ return "i can't find the friend record on disk";
449
+ /* v8 ignore next 2 -- invalid is pre-empted by the harness validation above; kept for exhaustiveness @preserve */
450
+ case "invalid":
451
+ return result.message ?? "i can't save that note";
452
+ default:
453
+ return `error saving note: ${result.message}`;
463
454
  }
464
455
  },
465
456
  summaryKeys: ["type", "key", "content"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.670",
3
+ "version": "0.1.0-alpha.671",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@anthropic-ai/sdk": "^0.78.0",
53
- "@ouro.bot/friends": "git+https://github.com/ourostack/friends.git#0095316eed6876a27f36a26ddc47ad9da800a652",
53
+ "@ouro.bot/friends": "git+https://github.com/ourostack/friends.git#250d1a4bbbad5cd50687975afc463e60e3ff8366",
54
54
  "@azure/identity": "^4.13.0",
55
55
  "@azure/storage-blob": "^12.31.0",
56
56
  "@microsoft/teams.api": "2.0.11",