@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 +10 -0
- package/dist/heart/daemon/daemon-cli.js +129 -44
- package/dist/heart/daemon/daemon.js +90 -0
- package/dist/mind/friends/channel.js +27 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/pending.js +8 -0
- package/dist/mind/prompt.js +26 -22
- package/dist/repertoire/tools-base.js +1 -1
- package/dist/repertoire/tools.js +11 -16
- package/dist/senses/bluebubbles-model.js +10 -0
- package/dist/senses/bluebubbles.js +125 -29
- package/dist/senses/cli.js +59 -54
- package/dist/senses/inner-dialog.js +88 -45
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +87 -65
- package/dist/senses/trust-gate.js +113 -2
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
1390
|
-
const
|
|
1391
|
-
|
|
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
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
+
}
|
package/dist/mind/pending.js
CHANGED
|
@@ -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 [];
|
package/dist/mind/prompt.js
CHANGED
|
@@ -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
|
|
375
|
+
if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
|
|
381
376
|
return "";
|
|
382
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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`;
|