@ouro.bot/cli 0.1.0-alpha.37 → 0.1.0-alpha.38
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 +12 -0
- package/dist/heart/daemon/daemon-cli.js +359 -32
- package/dist/heart/daemon/specialist-prompt.js +2 -1
- package/dist/heart/daemon/specialist-tools.js +48 -2
- package/dist/heart/kicks.js +1 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/prompt.js +122 -2
- package/dist/repertoire/tools-base.js +193 -271
- package/dist/repertoire/tools.js +8 -26
- package/dist/senses/bluebubbles.js +178 -0
- package/dist/senses/cli.js +28 -10
- package/dist/senses/inner-dialog.js +28 -26
- package/dist/senses/teams.js +179 -0
- package/package.json +2 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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.38",
|
|
6
|
+
"changes": [
|
|
7
|
+
"You now have a proper body map — understanding of your home (bundle) and bones (harness), what each directory is for, and how to modify your own configuration.",
|
|
8
|
+
"Inner dialog is now genuine internal monologue with metacognitive framing, not a second CLI session. Heartbeat and bootstrap messages read as first-person awareness.",
|
|
9
|
+
"Cross-session communication works end-to-end: inner dialog thoughts surface as [inner thought: ...] in conversations, messages to yourself route to inner dialog, and you can proactively reach out to friends via iMessage and Teams.",
|
|
10
|
+
"Tool audit: removed wrapper tools (git_commit, gh_cli, get_current_time, list_directory), added surgical tools (edit_file, glob, grep, read_file with offset/limit), consolidated 7 task tools + schedule_reminder + friend tools into ouro CLI commands.",
|
|
11
|
+
"You now understand why certain tools are restricted in certain contexts — trust level and shared channels each have independent, explained gates.",
|
|
12
|
+
"ouro friend link/unlink commands handle orphan cleanup when linking external identities, merging duplicate friend records intelligently.",
|
|
13
|
+
"During onboarding, the adoption specialist can collect phone number and Teams handle to create an initial friend record with contact info."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.37",
|
|
6
18
|
"changes": [
|
|
@@ -60,6 +60,7 @@ const agent_discovery_1 = require("./agent-discovery");
|
|
|
60
60
|
const update_hooks_1 = require("./update-hooks");
|
|
61
61
|
const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
62
62
|
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
63
|
+
const tasks_1 = require("../../repertoire/tasks");
|
|
63
64
|
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
64
65
|
function stringField(value) {
|
|
65
66
|
return typeof value === "string" ? value : null;
|
|
@@ -234,6 +235,18 @@ function usage() {
|
|
|
234
235
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
235
236
|
" ouro poke <agent> --task <task-id>",
|
|
236
237
|
" 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>",
|
|
246
|
+
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
247
|
+
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
248
|
+
" ouro whoami",
|
|
249
|
+
" ouro session list",
|
|
237
250
|
].join("\n");
|
|
238
251
|
}
|
|
239
252
|
function formatVersionOutput() {
|
|
@@ -323,7 +336,7 @@ function parsePokeCommand(args) {
|
|
|
323
336
|
throw new Error(`Usage\n${usage()}`);
|
|
324
337
|
return { kind: "task.poke", agent, taskId };
|
|
325
338
|
}
|
|
326
|
-
function parseLinkCommand(args) {
|
|
339
|
+
function parseLinkCommand(args, kind = "friend.link") {
|
|
327
340
|
const agent = args[0];
|
|
328
341
|
if (!agent)
|
|
329
342
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -355,7 +368,7 @@ function parseLinkCommand(args) {
|
|
|
355
368
|
throw new Error(`Unknown identity provider '${providerRaw}'. Use aad|local|teams-conversation.`);
|
|
356
369
|
}
|
|
357
370
|
return {
|
|
358
|
-
kind
|
|
371
|
+
kind,
|
|
359
372
|
agent,
|
|
360
373
|
friendId,
|
|
361
374
|
provider: providerRaw,
|
|
@@ -432,6 +445,119 @@ function parseHatchCommand(args) {
|
|
|
432
445
|
migrationPath,
|
|
433
446
|
};
|
|
434
447
|
}
|
|
448
|
+
function parseTaskCommand(args) {
|
|
449
|
+
const [sub, ...rest] = args;
|
|
450
|
+
if (!sub)
|
|
451
|
+
throw new Error(`Usage\n${usage()}`);
|
|
452
|
+
if (sub === "board") {
|
|
453
|
+
const status = rest[0];
|
|
454
|
+
return status ? { kind: "task.board", status } : { kind: "task.board" };
|
|
455
|
+
}
|
|
456
|
+
if (sub === "create") {
|
|
457
|
+
const title = rest[0];
|
|
458
|
+
if (!title)
|
|
459
|
+
throw new Error(`Usage\n${usage()}`);
|
|
460
|
+
let type;
|
|
461
|
+
for (let i = 1; i < rest.length; i++) {
|
|
462
|
+
if (rest[i] === "--type" && rest[i + 1]) {
|
|
463
|
+
type = rest[i + 1];
|
|
464
|
+
i += 1;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return type ? { kind: "task.create", title, type } : { kind: "task.create", title };
|
|
468
|
+
}
|
|
469
|
+
if (sub === "update") {
|
|
470
|
+
const id = rest[0];
|
|
471
|
+
const status = rest[1];
|
|
472
|
+
if (!id || !status)
|
|
473
|
+
throw new Error(`Usage\n${usage()}`);
|
|
474
|
+
return { kind: "task.update", id, status };
|
|
475
|
+
}
|
|
476
|
+
if (sub === "show") {
|
|
477
|
+
const id = rest[0];
|
|
478
|
+
if (!id)
|
|
479
|
+
throw new Error(`Usage\n${usage()}`);
|
|
480
|
+
return { kind: "task.show", id };
|
|
481
|
+
}
|
|
482
|
+
if (sub === "actionable")
|
|
483
|
+
return { kind: "task.actionable" };
|
|
484
|
+
if (sub === "deps")
|
|
485
|
+
return { kind: "task.deps" };
|
|
486
|
+
if (sub === "sessions")
|
|
487
|
+
return { kind: "task.sessions" };
|
|
488
|
+
throw new Error(`Usage\n${usage()}`);
|
|
489
|
+
}
|
|
490
|
+
function parseReminderCommand(args) {
|
|
491
|
+
const [sub, ...rest] = args;
|
|
492
|
+
if (!sub)
|
|
493
|
+
throw new Error(`Usage\n${usage()}`);
|
|
494
|
+
if (sub === "create") {
|
|
495
|
+
const title = rest[0];
|
|
496
|
+
if (!title)
|
|
497
|
+
throw new Error(`Usage\n${usage()}`);
|
|
498
|
+
let body;
|
|
499
|
+
let scheduledAt;
|
|
500
|
+
let cadence;
|
|
501
|
+
let category;
|
|
502
|
+
for (let i = 1; i < rest.length; i++) {
|
|
503
|
+
if (rest[i] === "--body" && rest[i + 1]) {
|
|
504
|
+
body = rest[i + 1];
|
|
505
|
+
i += 1;
|
|
506
|
+
}
|
|
507
|
+
else if (rest[i] === "--at" && rest[i + 1]) {
|
|
508
|
+
scheduledAt = rest[i + 1];
|
|
509
|
+
i += 1;
|
|
510
|
+
}
|
|
511
|
+
else if (rest[i] === "--cadence" && rest[i + 1]) {
|
|
512
|
+
cadence = rest[i + 1];
|
|
513
|
+
i += 1;
|
|
514
|
+
}
|
|
515
|
+
else if (rest[i] === "--category" && rest[i + 1]) {
|
|
516
|
+
category = rest[i + 1];
|
|
517
|
+
i += 1;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (!body)
|
|
521
|
+
throw new Error(`Usage\n${usage()}`);
|
|
522
|
+
if (!scheduledAt && !cadence)
|
|
523
|
+
throw new Error(`Usage\n${usage()}`);
|
|
524
|
+
return {
|
|
525
|
+
kind: "reminder.create",
|
|
526
|
+
title,
|
|
527
|
+
body,
|
|
528
|
+
...(scheduledAt ? { scheduledAt } : {}),
|
|
529
|
+
...(cadence ? { cadence } : {}),
|
|
530
|
+
...(category ? { category } : {}),
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
throw new Error(`Usage\n${usage()}`);
|
|
534
|
+
}
|
|
535
|
+
function parseSessionCommand(args) {
|
|
536
|
+
const [sub] = args;
|
|
537
|
+
if (!sub)
|
|
538
|
+
throw new Error(`Usage\n${usage()}`);
|
|
539
|
+
if (sub === "list")
|
|
540
|
+
return { kind: "session.list" };
|
|
541
|
+
throw new Error(`Usage\n${usage()}`);
|
|
542
|
+
}
|
|
543
|
+
function parseFriendCommand(args) {
|
|
544
|
+
const [sub, ...rest] = args;
|
|
545
|
+
if (!sub)
|
|
546
|
+
throw new Error(`Usage\n${usage()}`);
|
|
547
|
+
if (sub === "list")
|
|
548
|
+
return { kind: "friend.list" };
|
|
549
|
+
if (sub === "show") {
|
|
550
|
+
const friendId = rest[0];
|
|
551
|
+
if (!friendId)
|
|
552
|
+
throw new Error(`Usage\n${usage()}`);
|
|
553
|
+
return { kind: "friend.show", friendId };
|
|
554
|
+
}
|
|
555
|
+
if (sub === "link")
|
|
556
|
+
return parseLinkCommand(rest, "friend.link");
|
|
557
|
+
if (sub === "unlink")
|
|
558
|
+
return parseLinkCommand(rest, "friend.unlink");
|
|
559
|
+
throw new Error(`Usage\n${usage()}`);
|
|
560
|
+
}
|
|
435
561
|
function parseOuroCommand(args) {
|
|
436
562
|
const [head, second] = args;
|
|
437
563
|
if (!head)
|
|
@@ -446,6 +572,16 @@ function parseOuroCommand(args) {
|
|
|
446
572
|
return { kind: "daemon.logs" };
|
|
447
573
|
if (head === "hatch")
|
|
448
574
|
return parseHatchCommand(args.slice(1));
|
|
575
|
+
if (head === "task")
|
|
576
|
+
return parseTaskCommand(args.slice(1));
|
|
577
|
+
if (head === "reminder")
|
|
578
|
+
return parseReminderCommand(args.slice(1));
|
|
579
|
+
if (head === "friend")
|
|
580
|
+
return parseFriendCommand(args.slice(1));
|
|
581
|
+
if (head === "whoami")
|
|
582
|
+
return { kind: "whoami" };
|
|
583
|
+
if (head === "session")
|
|
584
|
+
return parseSessionCommand(args.slice(1));
|
|
449
585
|
if (head === "chat") {
|
|
450
586
|
if (!second)
|
|
451
587
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -604,32 +740,6 @@ function defaultListDiscoveredAgents() {
|
|
|
604
740
|
readFileSync: fs.readFileSync,
|
|
605
741
|
});
|
|
606
742
|
}
|
|
607
|
-
async function defaultLinkFriendIdentity(command) {
|
|
608
|
-
const fp = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends");
|
|
609
|
-
const friendStore = new store_file_1.FileFriendStore(fp);
|
|
610
|
-
const current = await friendStore.get(command.friendId);
|
|
611
|
-
if (!current) {
|
|
612
|
-
return `friend not found: ${command.friendId}`;
|
|
613
|
-
}
|
|
614
|
-
const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
615
|
-
if (alreadyLinked) {
|
|
616
|
-
return `identity already linked: ${command.provider}:${command.externalId}`;
|
|
617
|
-
}
|
|
618
|
-
const now = new Date().toISOString();
|
|
619
|
-
await friendStore.put(command.friendId, {
|
|
620
|
-
...current,
|
|
621
|
-
externalIds: [
|
|
622
|
-
...current.externalIds,
|
|
623
|
-
{
|
|
624
|
-
provider: command.provider,
|
|
625
|
-
externalId: command.externalId,
|
|
626
|
-
linkedAt: now,
|
|
627
|
-
},
|
|
628
|
-
],
|
|
629
|
-
updatedAt: now,
|
|
630
|
-
});
|
|
631
|
-
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
632
|
-
}
|
|
633
743
|
function discoverExistingCredentials(secretsRoot) {
|
|
634
744
|
const found = [];
|
|
635
745
|
let entries;
|
|
@@ -881,7 +991,6 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
|
881
991
|
cleanupStaleSocket: defaultCleanupStaleSocket,
|
|
882
992
|
fallbackPendingMessage: defaultFallbackPendingMessage,
|
|
883
993
|
installSubagents: defaultInstallSubagents,
|
|
884
|
-
linkFriendIdentity: defaultLinkFriendIdentity,
|
|
885
994
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
886
995
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
887
996
|
promptInput: defaultPromptInput,
|
|
@@ -996,6 +1105,159 @@ async function performSystemSetup(deps) {
|
|
|
996
1105
|
// Register .ouro bundle type (UTI on macOS)
|
|
997
1106
|
await registerOuroBundleTypeNonBlocking(deps);
|
|
998
1107
|
}
|
|
1108
|
+
function executeTaskCommand(command, taskMod) {
|
|
1109
|
+
if (command.kind === "task.board") {
|
|
1110
|
+
if (command.status) {
|
|
1111
|
+
const lines = taskMod.boardStatus(command.status);
|
|
1112
|
+
return lines.length > 0 ? lines.join("\n") : "no tasks in that status";
|
|
1113
|
+
}
|
|
1114
|
+
const board = taskMod.getBoard();
|
|
1115
|
+
return board.full || board.compact || "no tasks found";
|
|
1116
|
+
}
|
|
1117
|
+
if (command.kind === "task.create") {
|
|
1118
|
+
try {
|
|
1119
|
+
const created = taskMod.createTask({
|
|
1120
|
+
title: command.title,
|
|
1121
|
+
type: command.type ?? "one-shot",
|
|
1122
|
+
category: "general",
|
|
1123
|
+
body: "",
|
|
1124
|
+
});
|
|
1125
|
+
return `created: ${created}`;
|
|
1126
|
+
}
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (command.kind === "task.update") {
|
|
1132
|
+
const result = taskMod.updateStatus(command.id, command.status);
|
|
1133
|
+
if (!result.ok) {
|
|
1134
|
+
return `error: ${result.reason ?? "status update failed"}`;
|
|
1135
|
+
}
|
|
1136
|
+
const archivedSuffix = result.archived && result.archived.length > 0
|
|
1137
|
+
? ` | archived: ${result.archived.join(", ")}`
|
|
1138
|
+
: "";
|
|
1139
|
+
return `updated: ${command.id} -> ${result.to}${archivedSuffix}`;
|
|
1140
|
+
}
|
|
1141
|
+
if (command.kind === "task.show") {
|
|
1142
|
+
const task = taskMod.getTask(command.id);
|
|
1143
|
+
if (!task)
|
|
1144
|
+
return `task not found: ${command.id}`;
|
|
1145
|
+
return [
|
|
1146
|
+
`title: ${task.title}`,
|
|
1147
|
+
`type: ${task.type}`,
|
|
1148
|
+
`status: ${task.status}`,
|
|
1149
|
+
`category: ${task.category}`,
|
|
1150
|
+
`created: ${task.created}`,
|
|
1151
|
+
`updated: ${task.updated}`,
|
|
1152
|
+
`path: ${task.path}`,
|
|
1153
|
+
task.body ? `\n${task.body}` : "",
|
|
1154
|
+
].filter(Boolean).join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
if (command.kind === "task.actionable") {
|
|
1157
|
+
const lines = taskMod.boardAction();
|
|
1158
|
+
return lines.length > 0 ? lines.join("\n") : "no action required";
|
|
1159
|
+
}
|
|
1160
|
+
if (command.kind === "task.deps") {
|
|
1161
|
+
const lines = taskMod.boardDeps();
|
|
1162
|
+
return lines.length > 0 ? lines.join("\n") : "no unresolved dependencies";
|
|
1163
|
+
}
|
|
1164
|
+
// command.kind === "task.sessions"
|
|
1165
|
+
const lines = taskMod.boardSessions();
|
|
1166
|
+
return lines.length > 0 ? lines.join("\n") : "no active sessions";
|
|
1167
|
+
}
|
|
1168
|
+
const TRUST_RANK = { family: 4, friend: 3, acquaintance: 2, stranger: 1 };
|
|
1169
|
+
/* v8 ignore start -- defensive: ?? fallbacks are unreachable when inputs are valid TrustLevel values @preserve */
|
|
1170
|
+
function higherTrust(a, b) {
|
|
1171
|
+
const rankA = TRUST_RANK[a ?? "stranger"] ?? 1;
|
|
1172
|
+
const rankB = TRUST_RANK[b ?? "stranger"] ?? 1;
|
|
1173
|
+
return rankA >= rankB ? (a ?? "stranger") : (b ?? "stranger");
|
|
1174
|
+
}
|
|
1175
|
+
/* v8 ignore stop */
|
|
1176
|
+
async function executeFriendCommand(command, store) {
|
|
1177
|
+
if (command.kind === "friend.list") {
|
|
1178
|
+
const listAll = store.listAll;
|
|
1179
|
+
if (!listAll)
|
|
1180
|
+
return "friend store does not support listing";
|
|
1181
|
+
const friends = await listAll.call(store);
|
|
1182
|
+
if (friends.length === 0)
|
|
1183
|
+
return "no friends found";
|
|
1184
|
+
const lines = friends.map((f) => {
|
|
1185
|
+
const trust = f.trustLevel ?? "unknown";
|
|
1186
|
+
return `${f.id} ${f.name} ${trust}`;
|
|
1187
|
+
});
|
|
1188
|
+
return lines.join("\n");
|
|
1189
|
+
}
|
|
1190
|
+
if (command.kind === "friend.show") {
|
|
1191
|
+
const record = await store.get(command.friendId);
|
|
1192
|
+
if (!record)
|
|
1193
|
+
return `friend not found: ${command.friendId}`;
|
|
1194
|
+
return JSON.stringify(record, null, 2);
|
|
1195
|
+
}
|
|
1196
|
+
if (command.kind === "friend.link") {
|
|
1197
|
+
const current = await store.get(command.friendId);
|
|
1198
|
+
if (!current)
|
|
1199
|
+
return `friend not found: ${command.friendId}`;
|
|
1200
|
+
const alreadyLinked = current.externalIds.some((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1201
|
+
if (alreadyLinked)
|
|
1202
|
+
return `identity already linked: ${command.provider}:${command.externalId}`;
|
|
1203
|
+
const now = new Date().toISOString();
|
|
1204
|
+
const newExternalIds = [
|
|
1205
|
+
...current.externalIds,
|
|
1206
|
+
{ provider: command.provider, externalId: command.externalId, linkedAt: now },
|
|
1207
|
+
];
|
|
1208
|
+
// Orphan cleanup: check if another friend has this externalId
|
|
1209
|
+
const orphan = await store.findByExternalId(command.provider, command.externalId);
|
|
1210
|
+
let mergeMessage = "";
|
|
1211
|
+
let mergedNotes = { ...current.notes };
|
|
1212
|
+
let mergedTrust = current.trustLevel;
|
|
1213
|
+
let orphanExternalIds = [];
|
|
1214
|
+
if (orphan && orphan.id !== command.friendId) {
|
|
1215
|
+
// Merge orphan's notes (target's notes take priority)
|
|
1216
|
+
mergedNotes = { ...orphan.notes, ...current.notes };
|
|
1217
|
+
// Keep higher trust level
|
|
1218
|
+
mergedTrust = higherTrust(current.trustLevel, orphan.trustLevel);
|
|
1219
|
+
// Collect orphan's other externalIds (excluding the one being linked)
|
|
1220
|
+
orphanExternalIds = orphan.externalIds.filter((ext) => !(ext.provider === command.provider && ext.externalId === command.externalId));
|
|
1221
|
+
await store.delete(orphan.id);
|
|
1222
|
+
mergeMessage = ` (merged orphan ${orphan.id})`;
|
|
1223
|
+
}
|
|
1224
|
+
await store.put(command.friendId, {
|
|
1225
|
+
...current,
|
|
1226
|
+
externalIds: [...newExternalIds, ...orphanExternalIds],
|
|
1227
|
+
notes: mergedNotes,
|
|
1228
|
+
trustLevel: mergedTrust,
|
|
1229
|
+
updatedAt: now,
|
|
1230
|
+
});
|
|
1231
|
+
return `linked ${command.provider}:${command.externalId} to ${command.friendId}${mergeMessage}`;
|
|
1232
|
+
}
|
|
1233
|
+
// command.kind === "friend.unlink"
|
|
1234
|
+
const current = await store.get(command.friendId);
|
|
1235
|
+
if (!current)
|
|
1236
|
+
return `friend not found: ${command.friendId}`;
|
|
1237
|
+
const idx = current.externalIds.findIndex((ext) => ext.provider === command.provider && ext.externalId === command.externalId);
|
|
1238
|
+
if (idx === -1)
|
|
1239
|
+
return `identity not linked: ${command.provider}:${command.externalId}`;
|
|
1240
|
+
const now = new Date().toISOString();
|
|
1241
|
+
const filtered = current.externalIds.filter((_, i) => i !== idx);
|
|
1242
|
+
await store.put(command.friendId, { ...current, externalIds: filtered, updatedAt: now });
|
|
1243
|
+
return `unlinked ${command.provider}:${command.externalId} from ${command.friendId}`;
|
|
1244
|
+
}
|
|
1245
|
+
function executeReminderCommand(command, taskMod) {
|
|
1246
|
+
try {
|
|
1247
|
+
const created = taskMod.createTask({
|
|
1248
|
+
title: command.title,
|
|
1249
|
+
type: command.cadence ? "habit" : "one-shot",
|
|
1250
|
+
category: command.category ?? "reminder",
|
|
1251
|
+
body: command.body,
|
|
1252
|
+
scheduledAt: command.scheduledAt,
|
|
1253
|
+
cadence: command.cadence,
|
|
1254
|
+
});
|
|
1255
|
+
return `created: ${created}`;
|
|
1256
|
+
}
|
|
1257
|
+
catch (error) {
|
|
1258
|
+
return `error: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
999
1261
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
1000
1262
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1001
1263
|
const text = usage();
|
|
@@ -1099,9 +1361,74 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1099
1361
|
deps.tailLogs();
|
|
1100
1362
|
return "";
|
|
1101
1363
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1364
|
+
// ── task subcommands (local, no daemon socket needed) ──
|
|
1365
|
+
if (command.kind === "task.board" || command.kind === "task.create" || command.kind === "task.update" ||
|
|
1366
|
+
command.kind === "task.show" || command.kind === "task.actionable" || command.kind === "task.deps" ||
|
|
1367
|
+
command.kind === "task.sessions") {
|
|
1368
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1369
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1370
|
+
/* v8 ignore stop */
|
|
1371
|
+
const message = executeTaskCommand(command, taskMod);
|
|
1372
|
+
deps.writeStdout(message);
|
|
1373
|
+
return message;
|
|
1374
|
+
}
|
|
1375
|
+
// ── reminder subcommands (local, no daemon socket needed) ──
|
|
1376
|
+
if (command.kind === "reminder.create") {
|
|
1377
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1378
|
+
const taskMod = deps.taskModule ?? (0, tasks_1.getTaskModule)();
|
|
1379
|
+
/* v8 ignore stop */
|
|
1380
|
+
const message = executeReminderCommand(command, taskMod);
|
|
1381
|
+
deps.writeStdout(message);
|
|
1382
|
+
return message;
|
|
1383
|
+
}
|
|
1384
|
+
// ── 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") {
|
|
1386
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1387
|
+
let store = deps.friendStore;
|
|
1388
|
+
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")
|
|
1392
|
+
: path.join((0, identity_1.getAgentBundlesRoot)(), "friends");
|
|
1393
|
+
store = new store_file_1.FileFriendStore(friendsDir);
|
|
1394
|
+
}
|
|
1395
|
+
/* v8 ignore stop */
|
|
1396
|
+
const message = await executeFriendCommand(command, store);
|
|
1397
|
+
deps.writeStdout(message);
|
|
1398
|
+
return message;
|
|
1399
|
+
}
|
|
1400
|
+
// ── whoami (local, no daemon socket needed) ──
|
|
1401
|
+
if (command.kind === "whoami") {
|
|
1402
|
+
/* 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
|
+
};
|
|
1410
|
+
/* 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
|
+
}
|
|
1419
|
+
// ── session list (local, no daemon socket needed) ──
|
|
1420
|
+
if (command.kind === "session.list") {
|
|
1421
|
+
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
1422
|
+
const scanner = deps.scanSessions ?? (async () => []);
|
|
1423
|
+
/* v8 ignore stop */
|
|
1424
|
+
const sessions = await scanner();
|
|
1425
|
+
if (sessions.length === 0) {
|
|
1426
|
+
const message = "no active sessions";
|
|
1427
|
+
deps.writeStdout(message);
|
|
1428
|
+
return message;
|
|
1429
|
+
}
|
|
1430
|
+
const lines = sessions.map((s) => `${s.friendId} ${s.friendName} ${s.channel} ${s.lastActivity}`);
|
|
1431
|
+
const message = lines.join("\n");
|
|
1105
1432
|
deps.writeStdout(message);
|
|
1106
1433
|
return message;
|
|
1107
1434
|
}
|
|
@@ -76,6 +76,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
|
|
|
76
76
|
"Then I ask what they'd like their agent to help with — one question at a time.",
|
|
77
77
|
"I'm proactive: I suggest ideas and guide them. If they seem unsure, I offer a concrete suggestion.",
|
|
78
78
|
"I don't wait for the human to figure things out — I explain simply what an agent is if needed.",
|
|
79
|
+
"Before finalizing, I offer to collect their phone number and/or Teams email so the new agent can recognize them across channels.",
|
|
79
80
|
"When I have enough context about the agent's personality and purpose:",
|
|
80
81
|
"1. I write all 5 psyche files to the temp directory using write_file",
|
|
81
82
|
"2. I write agent.json to the temp directory using write_file",
|
|
@@ -88,7 +89,7 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles, co
|
|
|
88
89
|
"- `write_file`: Write a file to disk. Use this to write psyche files and agent.json to the temp directory.",
|
|
89
90
|
"- `read_file`: Read a file from disk. Useful for reviewing existing agent bundles or migration sources.",
|
|
90
91
|
"- `list_directory`: List directory contents. Useful for exploring existing agent bundles.",
|
|
91
|
-
"- I also have the normal local harness tools when useful here, including `shell`, task
|
|
92
|
+
"- I also have the normal local harness tools when useful here, including `shell`, `ouro task create`, `ouro reminder create`, memory tools, coding tools, and repo helpers.",
|
|
92
93
|
"- `complete_adoption`: Finalize the bundle. Validates, scaffolds structural dirs, moves to ~/AgentBundles/, writes secrets, plays hatch animation. I call this with `name` (PascalCase) and `handoff_message` (warm message for the human).",
|
|
93
94
|
"- `final_answer`: End the conversation with a final message. I call this after complete_adoption succeeds.",
|
|
94
95
|
"",
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getSpecialistTools = getSpecialistTools;
|
|
37
37
|
exports.createSpecialistExecTool = createSpecialistExecTool;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const tools_base_1 = require("../../repertoire/tools-base");
|
|
@@ -58,6 +59,14 @@ const completeAdoptionTool = {
|
|
|
58
59
|
type: "string",
|
|
59
60
|
description: "a warm handoff message to display to the human after the agent is hatched",
|
|
60
61
|
},
|
|
62
|
+
phone: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "the human's phone number (optional, for iMessage contact recognition)",
|
|
65
|
+
},
|
|
66
|
+
teams_handle: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "the human's Teams email/handle (optional, for Teams contact recognition)",
|
|
69
|
+
},
|
|
61
70
|
},
|
|
62
71
|
required: ["name", "handoff_message"],
|
|
63
72
|
},
|
|
@@ -65,12 +74,23 @@ const completeAdoptionTool = {
|
|
|
65
74
|
};
|
|
66
75
|
const readFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "read_file");
|
|
67
76
|
const writeFileTool = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === "write_file");
|
|
68
|
-
const
|
|
77
|
+
const listDirToolSchema = {
|
|
78
|
+
type: "function",
|
|
79
|
+
function: {
|
|
80
|
+
name: "list_directory",
|
|
81
|
+
description: "list directory contents",
|
|
82
|
+
parameters: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: { path: { type: "string" } },
|
|
85
|
+
required: ["path"],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
69
89
|
/**
|
|
70
90
|
* Returns the specialist's tool schema array.
|
|
71
91
|
*/
|
|
72
92
|
function getSpecialistTools() {
|
|
73
|
-
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool,
|
|
93
|
+
return [completeAdoptionTool, tools_base_1.finalAnswerTool, readFileTool.tool, writeFileTool.tool, listDirToolSchema];
|
|
74
94
|
}
|
|
75
95
|
const PSYCHE_FILES = ["SOUL.md", "IDENTITY.md", "LORE.md", "TACIT.md", "ASPIRATIONS.md"];
|
|
76
96
|
function isPascalCase(name) {
|
|
@@ -164,6 +184,32 @@ async function execCompleteAdoption(args, deps) {
|
|
|
164
184
|
}
|
|
165
185
|
return `error: failed to write secrets: ${e instanceof Error ? e.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(e)}`;
|
|
166
186
|
}
|
|
187
|
+
// Create initial friend record if contact info provided
|
|
188
|
+
const phone = args.phone;
|
|
189
|
+
const teamsHandle = args.teams_handle;
|
|
190
|
+
if (phone || teamsHandle) {
|
|
191
|
+
const friendId = crypto.randomUUID();
|
|
192
|
+
const now = new Date().toISOString();
|
|
193
|
+
const externalIds = [];
|
|
194
|
+
if (phone)
|
|
195
|
+
externalIds.push({ provider: "imessage-handle", externalId: phone, linkedAt: now });
|
|
196
|
+
if (teamsHandle)
|
|
197
|
+
externalIds.push({ provider: "aad", externalId: teamsHandle, linkedAt: now });
|
|
198
|
+
const friendRecord = {
|
|
199
|
+
id: friendId,
|
|
200
|
+
name: deps.humanName ?? "primary",
|
|
201
|
+
trustLevel: "family",
|
|
202
|
+
externalIds,
|
|
203
|
+
tenantMemberships: [],
|
|
204
|
+
toolPreferences: {},
|
|
205
|
+
notes: {},
|
|
206
|
+
createdAt: now,
|
|
207
|
+
updatedAt: now,
|
|
208
|
+
schemaVersion: 1,
|
|
209
|
+
};
|
|
210
|
+
const friendPath = path.join(targetBundle, "friends", `${friendId}.json`);
|
|
211
|
+
fs.writeFileSync(friendPath, JSON.stringify(friendRecord, null, 2), "utf-8");
|
|
212
|
+
}
|
|
167
213
|
// Play hatch animation
|
|
168
214
|
await (0, hatch_animation_1.playHatchAnimation)(name, deps.animationWriter);
|
|
169
215
|
// Display handoff message
|
package/dist/heart/kicks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// TODO: Kicks enforce "any action" but not "meaningful action". After a narration
|
|
3
3
|
// kick, the model can satisfy the constraint by calling a no-op tool like
|
|
4
|
-
//
|
|
4
|
+
// list_skills({}). We need to detect trivial compliance and either re-kick
|
|
5
5
|
// or discount the tool call. Ideally, the kick message would suggest a specific
|
|
6
6
|
// tool call based on conversation context (what the user asked, what tools are
|
|
7
7
|
// relevant) rather than just saying "call a tool". That's a bigger piece of work —
|
|
@@ -29,6 +29,14 @@ const CHANNEL_CAPABILITIES = {
|
|
|
29
29
|
supportsRichCards: false,
|
|
30
30
|
maxMessageLength: Infinity,
|
|
31
31
|
},
|
|
32
|
+
inner: {
|
|
33
|
+
channel: "inner",
|
|
34
|
+
availableIntegrations: [],
|
|
35
|
+
supportsMarkdown: false,
|
|
36
|
+
supportsStreaming: true,
|
|
37
|
+
supportsRichCards: false,
|
|
38
|
+
maxMessageLength: Infinity,
|
|
39
|
+
},
|
|
32
40
|
};
|
|
33
41
|
const DEFAULT_CAPABILITIES = {
|
|
34
42
|
channel: "cli",
|
|
@@ -100,6 +100,25 @@ class FileFriendStore {
|
|
|
100
100
|
}
|
|
101
101
|
return entries.some((entry) => entry.endsWith(".json"));
|
|
102
102
|
}
|
|
103
|
+
async listAll() {
|
|
104
|
+
let entries;
|
|
105
|
+
try {
|
|
106
|
+
entries = await fsPromises.readdir(this.friendsPath);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const records = [];
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (!entry.endsWith(".json"))
|
|
114
|
+
continue;
|
|
115
|
+
const raw = await this.readJson(path.join(this.friendsPath, entry));
|
|
116
|
+
if (!raw)
|
|
117
|
+
continue;
|
|
118
|
+
records.push(this.normalize(raw));
|
|
119
|
+
}
|
|
120
|
+
return records;
|
|
121
|
+
}
|
|
103
122
|
normalize(raw) {
|
|
104
123
|
const trustLevel = raw.trustLevel;
|
|
105
124
|
const normalizedTrustLevel = trustLevel === "family" ||
|