@ouro.bot/cli 0.1.0-alpha.38 → 0.1.0-alpha.39

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,16 @@
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.39",
6
+ "changes": [
7
+ "All senses now route through a shared per-turn pipeline — friend resolution, trust gate, session load, pending drain, agent turn, post-turn, and token accumulation happen in one place instead of four.",
8
+ "Trust gate is now channel-aware: open senses (iMessage) enforce stranger/acquaintance rules, closed senses (Teams) trust the org, local and internal always pass through.",
9
+ "Tool access and prompt restrictions use a single shared isTrustedLevel check — no more scattered family/friend comparisons that could drift apart.",
10
+ "Pending messages now inject correctly into multimodal content (image attachments no longer silently drop pending messages).",
11
+ "ouro reminder create supports --agent flag, matching every other identity-scoped CLI command."
12
+ ]
13
+ },
4
14
  {
5
15
  "version": "0.1.0-alpha.38",
6
16
  "changes": [
@@ -39,6 +39,7 @@ exports.discoverExistingCredentials = discoverExistingCredentials;
39
39
  exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
40
40
  exports.runOuroCli = runOuroCli;
41
41
  const child_process_1 = require("child_process");
42
+ const crypto_1 = require("crypto");
42
43
  const fs = __importStar(require("fs"));
43
44
  const net = __importStar(require("net"));
44
45
  const os = __importStar(require("os"));
@@ -225,6 +226,18 @@ async function ensureDaemonRunning(deps) {
225
226
  message: `daemon started (pid ${started.pid ?? "unknown"})`,
226
227
  };
227
228
  }
229
+ /**
230
+ * Extract `--agent <name>` from an args array, returning the agent name and
231
+ * the remaining args with the flag pair removed.
232
+ */
233
+ function extractAgentFlag(args) {
234
+ const idx = args.indexOf("--agent");
235
+ if (idx === -1 || idx + 1 >= args.length)
236
+ return { rest: args };
237
+ const agent = args[idx + 1];
238
+ const rest = [...args.slice(0, idx), ...args.slice(idx + 2)];
239
+ return { agent, rest };
240
+ }
228
241
  function usage() {
229
242
  return [
230
243
  "Usage:",
@@ -235,18 +248,19 @@ function usage() {
235
248
  " ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
236
249
  " ouro poke <agent> --task <task-id>",
237
250
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
238
- " ouro task board [<status>]",
239
- " ouro task create <title> [--type <type>]",
240
- " ouro task update <id> <status>",
241
- " ouro task show <id>",
242
- " ouro task actionable|deps|sessions",
243
- " ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>]",
244
- " ouro friend list",
245
- " ouro friend show <id>",
251
+ " ouro task board [<status>] [--agent <name>]",
252
+ " ouro task create <title> [--type <type>] [--agent <name>]",
253
+ " ouro task update <id> <status> [--agent <name>]",
254
+ " ouro task show <id> [--agent <name>]",
255
+ " ouro task actionable|deps|sessions [--agent <name>]",
256
+ " ouro reminder create <title> --body <body> [--at <iso>] [--cadence <interval>] [--category <category>] [--agent <name>]",
257
+ " ouro friend list [--agent <name>]",
258
+ " ouro friend show <id> [--agent <name>]",
259
+ " ouro friend create --name <name> [--trust <level>] [--agent <name>]",
246
260
  " ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
247
261
  " ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
248
- " ouro whoami",
249
- " ouro session list",
262
+ " ouro whoami [--agent <name>]",
263
+ " ouro session list [--agent <name>]",
250
264
  ].join("\n");
251
265
  }
252
266
  function formatVersionOutput() {
@@ -446,12 +460,15 @@ function parseHatchCommand(args) {
446
460
  };
447
461
  }
448
462
  function parseTaskCommand(args) {
449
- const [sub, ...rest] = args;
463
+ const { agent, rest: cleaned } = extractAgentFlag(args);
464
+ const [sub, ...rest] = cleaned;
450
465
  if (!sub)
451
466
  throw new Error(`Usage\n${usage()}`);
452
467
  if (sub === "board") {
453
468
  const status = rest[0];
454
- return status ? { kind: "task.board", status } : { kind: "task.board" };
469
+ return status
470
+ ? { kind: "task.board", status, ...(agent ? { agent } : {}) }
471
+ : { kind: "task.board", ...(agent ? { agent } : {}) };
455
472
  }
456
473
  if (sub === "create") {
457
474
  const title = rest[0];
@@ -464,31 +481,34 @@ function parseTaskCommand(args) {
464
481
  i += 1;
465
482
  }
466
483
  }
467
- return type ? { kind: "task.create", title, type } : { kind: "task.create", title };
484
+ return type
485
+ ? { kind: "task.create", title, type, ...(agent ? { agent } : {}) }
486
+ : { kind: "task.create", title, ...(agent ? { agent } : {}) };
468
487
  }
469
488
  if (sub === "update") {
470
489
  const id = rest[0];
471
490
  const status = rest[1];
472
491
  if (!id || !status)
473
492
  throw new Error(`Usage\n${usage()}`);
474
- return { kind: "task.update", id, status };
493
+ return { kind: "task.update", id, status, ...(agent ? { agent } : {}) };
475
494
  }
476
495
  if (sub === "show") {
477
496
  const id = rest[0];
478
497
  if (!id)
479
498
  throw new Error(`Usage\n${usage()}`);
480
- return { kind: "task.show", id };
499
+ return { kind: "task.show", id, ...(agent ? { agent } : {}) };
481
500
  }
482
501
  if (sub === "actionable")
483
- return { kind: "task.actionable" };
502
+ return { kind: "task.actionable", ...(agent ? { agent } : {}) };
484
503
  if (sub === "deps")
485
- return { kind: "task.deps" };
504
+ return { kind: "task.deps", ...(agent ? { agent } : {}) };
486
505
  if (sub === "sessions")
487
- return { kind: "task.sessions" };
506
+ return { kind: "task.sessions", ...(agent ? { agent } : {}) };
488
507
  throw new Error(`Usage\n${usage()}`);
489
508
  }
490
509
  function parseReminderCommand(args) {
491
- const [sub, ...rest] = args;
510
+ const { agent, rest: cleaned } = extractAgentFlag(args);
511
+ const [sub, ...rest] = cleaned;
492
512
  if (!sub)
493
513
  throw new Error(`Usage\n${usage()}`);
494
514
  if (sub === "create") {
@@ -528,29 +548,54 @@ function parseReminderCommand(args) {
528
548
  ...(scheduledAt ? { scheduledAt } : {}),
529
549
  ...(cadence ? { cadence } : {}),
530
550
  ...(category ? { category } : {}),
551
+ ...(agent ? { agent } : {}),
531
552
  };
532
553
  }
533
554
  throw new Error(`Usage\n${usage()}`);
534
555
  }
535
556
  function parseSessionCommand(args) {
536
- const [sub] = args;
557
+ const { agent, rest: cleaned } = extractAgentFlag(args);
558
+ const [sub] = cleaned;
537
559
  if (!sub)
538
560
  throw new Error(`Usage\n${usage()}`);
539
561
  if (sub === "list")
540
- return { kind: "session.list" };
562
+ return { kind: "session.list", ...(agent ? { agent } : {}) };
541
563
  throw new Error(`Usage\n${usage()}`);
542
564
  }
543
565
  function parseFriendCommand(args) {
544
- const [sub, ...rest] = args;
566
+ const { agent, rest: cleaned } = extractAgentFlag(args);
567
+ const [sub, ...rest] = cleaned;
545
568
  if (!sub)
546
569
  throw new Error(`Usage\n${usage()}`);
547
570
  if (sub === "list")
548
- return { kind: "friend.list" };
571
+ return { kind: "friend.list", ...(agent ? { agent } : {}) };
549
572
  if (sub === "show") {
550
573
  const friendId = rest[0];
551
574
  if (!friendId)
552
575
  throw new Error(`Usage\n${usage()}`);
553
- return { kind: "friend.show", friendId };
576
+ return { kind: "friend.show", friendId, ...(agent ? { agent } : {}) };
577
+ }
578
+ if (sub === "create") {
579
+ let name;
580
+ let trustLevel;
581
+ for (let i = 0; i < rest.length; i++) {
582
+ if (rest[i] === "--name" && rest[i + 1]) {
583
+ name = rest[i + 1];
584
+ i += 1;
585
+ }
586
+ else if (rest[i] === "--trust" && rest[i + 1]) {
587
+ trustLevel = rest[i + 1];
588
+ i += 1;
589
+ }
590
+ }
591
+ if (!name)
592
+ throw new Error(`Usage\n${usage()}`);
593
+ return {
594
+ kind: "friend.create",
595
+ name,
596
+ ...(trustLevel ? { trustLevel } : {}),
597
+ ...(agent ? { agent } : {}),
598
+ };
554
599
  }
555
600
  if (sub === "link")
556
601
  return parseLinkCommand(rest, "friend.link");
@@ -578,8 +623,10 @@ function parseOuroCommand(args) {
578
623
  return parseReminderCommand(args.slice(1));
579
624
  if (head === "friend")
580
625
  return parseFriendCommand(args.slice(1));
581
- if (head === "whoami")
582
- return { kind: "whoami" };
626
+ if (head === "whoami") {
627
+ const { agent } = extractAgentFlag(args.slice(1));
628
+ return { kind: "whoami", ...(agent ? { agent } : {}) };
629
+ }
583
630
  if (head === "session")
584
631
  return parseSessionCommand(args.slice(1));
585
632
  if (head === "chat") {
@@ -1193,6 +1240,25 @@ async function executeFriendCommand(command, store) {
1193
1240
  return `friend not found: ${command.friendId}`;
1194
1241
  return JSON.stringify(record, null, 2);
1195
1242
  }
1243
+ if (command.kind === "friend.create") {
1244
+ const now = new Date().toISOString();
1245
+ const id = (0, crypto_1.randomUUID)();
1246
+ const trustLevel = (command.trustLevel ?? "acquaintance");
1247
+ await store.put(id, {
1248
+ id,
1249
+ name: command.name,
1250
+ trustLevel,
1251
+ externalIds: [],
1252
+ tenantMemberships: [],
1253
+ toolPreferences: {},
1254
+ notes: {},
1255
+ totalTokens: 0,
1256
+ createdAt: now,
1257
+ updatedAt: now,
1258
+ schemaVersion: 1,
1259
+ });
1260
+ return `created: ${id} (${command.name}, ${trustLevel})`;
1261
+ }
1196
1262
  if (command.kind === "friend.link") {
1197
1263
  const current = await store.get(command.friendId);
1198
1264
  if (!current)
@@ -1382,13 +1448,15 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1382
1448
  return message;
1383
1449
  }
1384
1450
  // ── friend subcommands (local, no daemon socket needed) ──
1385
- if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.link" || command.kind === "friend.unlink") {
1451
+ if (command.kind === "friend.list" || command.kind === "friend.show" || command.kind === "friend.create" ||
1452
+ command.kind === "friend.link" || command.kind === "friend.unlink") {
1386
1453
  /* v8 ignore start -- production default: requires full identity setup @preserve */
1387
1454
  let store = deps.friendStore;
1388
1455
  if (!store) {
1389
- // link/unlink operate on a specific agent's friend store
1390
- const friendsDir = (command.kind === "friend.link" || command.kind === "friend.unlink")
1391
- ? path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends")
1456
+ // Derive agent-scoped friends dir from --agent flag or link/unlink's agent field
1457
+ const agentName = ("agent" in command && command.agent) ? command.agent : undefined;
1458
+ const friendsDir = agentName
1459
+ ? path.join((0, identity_1.getAgentBundlesRoot)(), `${agentName}.ouro`, "friends")
1392
1460
  : path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
1393
1461
  store = new store_file_1.FileFriendStore(friendsDir);
1394
1462
  }
@@ -1399,22 +1467,39 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1399
1467
  }
1400
1468
  // ── whoami (local, no daemon socket needed) ──
1401
1469
  if (command.kind === "whoami") {
1470
+ if (command.agent) {
1471
+ const agentRoot = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
1472
+ const message = [
1473
+ `agent: ${command.agent}`,
1474
+ `home: ${agentRoot}`,
1475
+ `bones: ${(0, runtime_metadata_1.getRuntimeMetadata)().version}`,
1476
+ ].join("\n");
1477
+ deps.writeStdout(message);
1478
+ return message;
1479
+ }
1402
1480
  /* v8 ignore start -- production default: requires full identity setup @preserve */
1403
- const info = deps.whoamiInfo
1404
- ? deps.whoamiInfo()
1405
- : {
1406
- agentName: (0, identity_1.getAgentName)(),
1407
- homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
1408
- bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
1409
- };
1481
+ try {
1482
+ const info = deps.whoamiInfo
1483
+ ? deps.whoamiInfo()
1484
+ : {
1485
+ agentName: (0, identity_1.getAgentName)(),
1486
+ homePath: path.join((0, identity_1.getAgentBundlesRoot)(), `${(0, identity_1.getAgentName)()}.ouro`),
1487
+ bonesVersion: (0, runtime_metadata_1.getRuntimeMetadata)().version,
1488
+ };
1489
+ const message = [
1490
+ `agent: ${info.agentName}`,
1491
+ `home: ${info.homePath}`,
1492
+ `bones: ${info.bonesVersion}`,
1493
+ ].join("\n");
1494
+ deps.writeStdout(message);
1495
+ return message;
1496
+ }
1497
+ catch {
1498
+ const message = "error: no agent context — use --agent <name> to specify";
1499
+ deps.writeStdout(message);
1500
+ return message;
1501
+ }
1410
1502
  /* v8 ignore stop */
1411
- const message = [
1412
- `agent: ${info.agentName}`,
1413
- `home: ${info.homePath}`,
1414
- `bones: ${info.bonesVersion}`,
1415
- ].join("\n");
1416
- deps.writeStdout(message);
1417
- return message;
1418
1503
  }
1419
1504
  // ── session list (local, no daemon socket needed) ──
1420
1505
  if (command.kind === "session.list") {
@@ -46,6 +46,8 @@ const bundle_manifest_1 = require("../../mind/bundle-manifest");
46
46
  const update_checker_1 = require("./update-checker");
47
47
  const staged_restart_1 = require("./staged-restart");
48
48
  const child_process_1 = require("child_process");
49
+ const pending_1 = require("../../mind/pending");
50
+ const channel_1 = require("../../mind/friends/channel");
49
51
  function buildWorkerRows(snapshots) {
50
52
  return snapshots.map((snapshot) => ({
51
53
  agent: snapshot.name,
@@ -156,6 +158,7 @@ class OuroDaemon {
156
158
  this.scheduler.start?.();
157
159
  await this.scheduler.reconcile?.();
158
160
  await this.drainPendingBundleMessages();
161
+ await this.drainPendingSenseMessages();
159
162
  if (fs.existsSync(this.socketPath)) {
160
163
  fs.unlinkSync(this.socketPath);
161
164
  }
@@ -231,6 +234,93 @@ class OuroDaemon {
231
234
  fs.writeFileSync(pendingPath, next, "utf-8");
232
235
  }
233
236
  }
237
+ /** Drains per-sense pending dirs for always-on senses across all agents. */
238
+ static ALWAYS_ON_SENSES = new Set((0, channel_1.getAlwaysOnSenseNames)());
239
+ async drainPendingSenseMessages() {
240
+ if (!fs.existsSync(this.bundlesRoot))
241
+ return;
242
+ let bundleDirs;
243
+ try {
244
+ bundleDirs = fs.readdirSync(this.bundlesRoot, { withFileTypes: true });
245
+ }
246
+ catch {
247
+ return;
248
+ }
249
+ for (const bundleDir of bundleDirs) {
250
+ if (!bundleDir.isDirectory() || !bundleDir.name.endsWith(".ouro"))
251
+ continue;
252
+ const agentName = bundleDir.name.replace(/\.ouro$/, "");
253
+ const pendingRoot = path.join(this.bundlesRoot, bundleDir.name, "state", "pending");
254
+ if (!fs.existsSync(pendingRoot))
255
+ continue;
256
+ let friendDirs;
257
+ try {
258
+ friendDirs = fs.readdirSync(pendingRoot, { withFileTypes: true });
259
+ }
260
+ catch {
261
+ continue;
262
+ }
263
+ for (const friendDir of friendDirs) {
264
+ if (!friendDir.isDirectory())
265
+ continue;
266
+ const friendPath = path.join(pendingRoot, friendDir.name);
267
+ let channelDirs;
268
+ try {
269
+ channelDirs = fs.readdirSync(friendPath, { withFileTypes: true });
270
+ }
271
+ catch {
272
+ continue;
273
+ }
274
+ for (const channelDir of channelDirs) {
275
+ if (!channelDir.isDirectory())
276
+ continue;
277
+ if (!OuroDaemon.ALWAYS_ON_SENSES.has(channelDir.name))
278
+ continue;
279
+ const channelPath = path.join(friendPath, channelDir.name);
280
+ let keyDirs;
281
+ try {
282
+ keyDirs = fs.readdirSync(channelPath, { withFileTypes: true });
283
+ }
284
+ catch {
285
+ continue;
286
+ }
287
+ for (const keyDir of keyDirs) {
288
+ if (!keyDir.isDirectory())
289
+ continue;
290
+ const leafDir = path.join(channelPath, keyDir.name);
291
+ const messages = (0, pending_1.drainPending)(leafDir);
292
+ for (const msg of messages) {
293
+ try {
294
+ await this.router.send({
295
+ from: msg.from,
296
+ to: agentName,
297
+ content: msg.content,
298
+ priority: "normal",
299
+ });
300
+ }
301
+ catch {
302
+ // Best-effort delivery — log and continue
303
+ }
304
+ }
305
+ if (messages.length > 0) {
306
+ (0, runtime_1.emitNervesEvent)({
307
+ component: "daemon",
308
+ event: "daemon.startup_sense_drain",
309
+ message: "drained pending sense messages on startup",
310
+ meta: {
311
+ agent: agentName,
312
+ channel: channelDir.name,
313
+ friendId: friendDir.name,
314
+ key: keyDir.name,
315
+ count: messages.length,
316
+ },
317
+ });
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ }
234
324
  async stop() {
235
325
  (0, runtime_1.emitNervesEvent)({
236
326
  component: "daemon",
@@ -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,6 +27,7 @@ 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,
@@ -31,6 +36,7 @@ const CHANNEL_CAPABILITIES = {
31
36
  },
32
37
  inner: {
33
38
  channel: "inner",
39
+ senseType: "internal",
34
40
  availableIntegrations: [],
35
41
  supportsMarkdown: false,
36
42
  supportsStreaming: true,
@@ -40,6 +46,7 @@ const CHANNEL_CAPABILITIES = {
40
46
  };
41
47
  const DEFAULT_CAPABILITIES = {
42
48
  channel: "cli",
49
+ senseType: "local",
43
50
  availableIntegrations: [],
44
51
  supportsMarkdown: false,
45
52
  supportsStreaming: false,
@@ -55,3 +62,23 @@ function getChannelCapabilities(channel) {
55
62
  });
56
63
  return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
57
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
+ }
@@ -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
+ }
@@ -33,7 +33,9 @@ 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.getInnerDialogPendingDir = getInnerDialogPendingDir;
37
39
  exports.drainPending = drainPending;
38
40
  const fs = __importStar(require("fs"));
39
41
  const path = __importStar(require("path"));
@@ -42,6 +44,12 @@ const runtime_1 = require("../nerves/runtime");
42
44
  function getPendingDir(agentName, friendId, channel, key) {
43
45
  return path.join((0, identity_1.getAgentRoot)(agentName), "state", "pending", friendId, channel, key);
44
46
  }
47
+ /** Canonical inner-dialog pending path segments. */
48
+ exports.INNER_DIALOG_PENDING = { friendId: "self", channel: "inner", key: "dialog" };
49
+ /** Returns the pending dir for this agent's inner dialog. */
50
+ function getInnerDialogPendingDir(agentName) {
51
+ return getPendingDir(agentName, exports.INNER_DIALOG_PENDING.friendId, exports.INNER_DIALOG_PENDING.channel, exports.INNER_DIALOG_PENDING.key);
52
+ }
45
53
  function drainPending(pendingDir) {
46
54
  if (!fs.existsSync(pendingDir))
47
55
  return [];
@@ -41,6 +41,8 @@ exports.toolRestrictionSection = toolRestrictionSection;
41
41
  exports.contextSection = contextSection;
42
42
  exports.metacognitiveFramingSection = metacognitiveFramingSection;
43
43
  exports.loopOrientationSection = loopOrientationSection;
44
+ exports.channelNatureSection = channelNatureSection;
45
+ exports.mixedTrustGroupSection = mixedTrustGroupSection;
44
46
  exports.buildSystem = buildSystem;
45
47
  const fs = __importStar(require("fs"));
46
48
  const path = __importStar(require("path"));
@@ -48,6 +50,7 @@ const core_1 = require("../heart/core");
48
50
  const tools_1 = require("../repertoire/tools");
49
51
  const skills_1 = require("../repertoire/skills");
50
52
  const identity_1 = require("../heart/identity");
53
+ const types_1 = require("./friends/types");
51
54
  const channel_1 = require("./friends/channel");
52
55
  const runtime_1 = require("../nerves/runtime");
53
56
  const bundle_manifest_1 = require("./bundle-manifest");
@@ -368,34 +371,16 @@ function toolsSection(channel, options, context) {
368
371
  .join("\n");
369
372
  return `## my tools\n${list}`;
370
373
  }
371
- const RESTRICTED_TOOLS = ["shell", "read_file", "write_file", "edit_file", "glob", "grep"];
372
- function isRemoteChannel(channel) {
373
- return channel === "teams" || channel === "bluebubbles";
374
- }
375
- function isSharedContext(friend) {
376
- const externalIds = friend.externalIds ?? [];
377
- return externalIds.some((eid) => eid.externalId.startsWith("group:") || eid.provider === "teams-conversation");
378
- }
379
374
  function toolRestrictionSection(context) {
380
- if (!context?.friend || !isRemoteChannel(context.channel?.channel))
375
+ if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
381
376
  return "";
382
- const trustLevel = context.friend.trustLevel ?? "stranger";
383
- const lowTrust = trustLevel === "stranger" || trustLevel === "acquaintance";
384
- const shared = isSharedContext(context.friend);
385
- if (!lowTrust && !shared)
377
+ if ((0, types_1.isTrustedLevel)(context.friend.trustLevel))
386
378
  return "";
387
- const reasons = [];
388
- if (lowTrust) {
389
- reasons.push("i don't know this person well enough yet to run local operations on their behalf");
390
- }
391
- if (shared) {
392
- reasons.push("this is a shared channel — local operations could let conversations interfere with each other");
393
- }
394
- const toolList = RESTRICTED_TOOLS.join(", ");
379
+ const toolList = [...tools_1.REMOTE_BLOCKED_LOCAL_TOOLS].join(", ");
395
380
  return `## restricted tools
396
381
  some of my tools are unavailable right now: ${toolList}
397
382
 
398
- ${reasons.join(". ")}. i can suggest remote-safe alternatives or ask them to run it from CLI.`;
383
+ i don't know this person well enough yet to run local operations on their behalf. i can suggest remote-safe alternatives or ask them to run it from CLI.`;
399
384
  }
400
385
  function skillsSection() {
401
386
  const names = (0, skills_1.listSkills)() || [];
@@ -514,6 +499,23 @@ function loopOrientationSection(channel) {
514
499
 
515
500
  when something deserves more thought than the moment allows, i can note it to myself and come back later with a considered answer.`;
516
501
  }
502
+ function channelNatureSection(capabilities) {
503
+ const { senseType } = capabilities;
504
+ if (senseType === "local" || senseType === "internal")
505
+ return "";
506
+ if (senseType === "open") {
507
+ return "## channel nature\nthis is an open channel — anyone with my number can reach me here. i may hear from people i don't know.";
508
+ }
509
+ // closed
510
+ return "## channel nature\nthis is an org-gated channel — i know everyone here is already part of the organization.";
511
+ }
512
+ function mixedTrustGroupSection(context) {
513
+ if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
514
+ return "";
515
+ if (!context.isGroupChat)
516
+ return "";
517
+ return "## mixed trust group\nin this group chat, my capabilities depend on who's talking. some people here have full trust, others don't — i adjust what i can do based on who's asking.";
518
+ }
517
519
  async function buildSystem(channel = "cli", options, context) {
518
520
  (0, runtime_1.emitNervesEvent)({
519
521
  event: "mind.step_start",
@@ -533,10 +535,12 @@ async function buildSystem(channel = "cli", options, context) {
533
535
  metacognitiveFramingSection(channel),
534
536
  loopOrientationSection(channel),
535
537
  runtimeInfoSection(channel),
538
+ channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
536
539
  providerSection(),
537
540
  dateSection(),
538
541
  toolsSection(channel, options, context),
539
542
  toolRestrictionSection(context),
543
+ mixedTrustGroupSection(context),
540
544
  skillsSection(),
541
545
  taskBoardSection(),
542
546
  buildSessionSummary({
@@ -661,7 +661,7 @@ exports.baseToolDefinitions = [
661
661
  // regardless of the channel or key the agent specified.
662
662
  const isSelf = friendId === "self";
663
663
  const pendingDir = isSelf
664
- ? (0, pending_1.getPendingDir)(agentName, "self", "inner", "dialog")
664
+ ? (0, pending_1.getInnerDialogPendingDir)(agentName)
665
665
  : (0, pending_1.getPendingDir)(agentName, friendId, channel, key);
666
666
  fs.mkdirSync(pendingDir, { recursive: true });
667
667
  const fileName = `${now}-${Math.random().toString(36).slice(2, 10)}.json`;