@ouro.bot/cli 0.1.0-alpha.47 → 0.1.0-alpha.49

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.
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.recallSession = recallSession;
37
+ const fs = __importStar(require("fs"));
38
+ const runtime_1 = require("../nerves/runtime");
39
+ function normalizeContent(content) {
40
+ if (typeof content === "string")
41
+ return content;
42
+ if (!Array.isArray(content))
43
+ return "";
44
+ return content
45
+ .map((part) => (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
46
+ ? String(part.text ?? "")
47
+ : ""))
48
+ .filter((text) => text.length > 0)
49
+ .join("");
50
+ }
51
+ function buildSummaryInstruction(friendId, channel, trustLevel) {
52
+ if (friendId === "self" && channel === "inner") {
53
+ return "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning.";
54
+ }
55
+ return `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
56
+ }
57
+ function clip(text, limit = 160) {
58
+ const compact = text.replace(/\s+/g, " ").trim();
59
+ return compact.length > limit ? compact.slice(0, limit - 1) + "…" : compact;
60
+ }
61
+ function buildSnapshot(summary, tailMessages) {
62
+ const lines = [`recent focus: ${clip(summary, 240)}`];
63
+ const latestUser = [...tailMessages].reverse().find((message) => message.role === "user")?.content;
64
+ const latestAssistant = [...tailMessages].reverse().find((message) => message.role === "assistant")?.content;
65
+ if (latestUser) {
66
+ lines.push(`latest user: ${clip(latestUser)}`);
67
+ }
68
+ if (latestAssistant) {
69
+ lines.push(`latest assistant: ${clip(latestAssistant)}`);
70
+ }
71
+ return lines.join("\n");
72
+ }
73
+ async function recallSession(options) {
74
+ (0, runtime_1.emitNervesEvent)({
75
+ component: "daemon",
76
+ event: "daemon.session_recall",
77
+ message: "recalling session transcript tail",
78
+ meta: {
79
+ friendId: options.friendId,
80
+ channel: options.channel,
81
+ key: options.key,
82
+ messageCount: options.messageCount,
83
+ },
84
+ });
85
+ let raw;
86
+ try {
87
+ raw = fs.readFileSync(options.sessionPath, "utf-8");
88
+ }
89
+ catch {
90
+ return { kind: "missing" };
91
+ }
92
+ const parsed = JSON.parse(raw);
93
+ const tailMessages = (parsed.messages ?? [])
94
+ .map((message) => ({
95
+ role: typeof message.role === "string" ? message.role : "",
96
+ content: normalizeContent(message.content),
97
+ }))
98
+ .filter((message) => message.role !== "system" && message.content.length > 0)
99
+ .slice(-options.messageCount);
100
+ if (tailMessages.length === 0) {
101
+ return { kind: "empty" };
102
+ }
103
+ const transcript = tailMessages
104
+ .map((message) => `[${message.role}] ${message.content}`)
105
+ .join("\n");
106
+ const summary = options.summarize
107
+ ? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
108
+ : transcript;
109
+ return {
110
+ kind: "ok",
111
+ transcript,
112
+ summary,
113
+ snapshot: buildSnapshot(summary, tailMessages),
114
+ tailMessages,
115
+ };
116
+ }
@@ -1,7 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createTurnCoordinator = createTurnCoordinator;
4
+ exports.withSharedTurnLock = withSharedTurnLock;
5
+ exports.tryBeginSharedTurn = tryBeginSharedTurn;
6
+ exports.endSharedTurn = endSharedTurn;
7
+ exports.isSharedTurnActive = isSharedTurnActive;
8
+ exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
9
+ exports.drainSharedFollowUps = drainSharedFollowUps;
4
10
  const runtime_1 = require("../nerves/runtime");
11
+ function scopedKey(scope, key) {
12
+ return `${scope}:${key}`;
13
+ }
5
14
  function createTurnCoordinator() {
6
15
  const turnLocks = new Map();
7
16
  const activeTurns = new Set();
@@ -60,3 +69,22 @@ function createTurnCoordinator() {
60
69
  },
61
70
  };
62
71
  }
72
+ const _sharedTurnCoordinator = createTurnCoordinator();
73
+ function withSharedTurnLock(scope, key, fn) {
74
+ return _sharedTurnCoordinator.withTurnLock(scopedKey(scope, key), fn);
75
+ }
76
+ function tryBeginSharedTurn(scope, key) {
77
+ return _sharedTurnCoordinator.tryBeginTurn(scopedKey(scope, key));
78
+ }
79
+ function endSharedTurn(scope, key) {
80
+ _sharedTurnCoordinator.endTurn(scopedKey(scope, key));
81
+ }
82
+ function isSharedTurnActive(scope, key) {
83
+ return _sharedTurnCoordinator.isTurnActive(scopedKey(scope, key));
84
+ }
85
+ function enqueueSharedFollowUp(scope, key, followUp) {
86
+ _sharedTurnCoordinator.enqueueFollowUp(scopedKey(scope, key), followUp);
87
+ }
88
+ function drainSharedFollowUps(scope, key) {
89
+ return _sharedTurnCoordinator.drainFollowUps(scopedKey(scope, key));
90
+ }
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.INNER_DIALOG_PENDING = void 0;
37
37
  exports.getPendingDir = getPendingDir;
38
38
  exports.getInnerDialogPendingDir = getInnerDialogPendingDir;
39
+ exports.hasPendingMessages = hasPendingMessages;
39
40
  exports.drainPending = drainPending;
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
@@ -50,6 +51,16 @@ exports.INNER_DIALOG_PENDING = { friendId: "self", channel: "inner", key: "dialo
50
51
  function getInnerDialogPendingDir(agentName) {
51
52
  return getPendingDir(agentName, exports.INNER_DIALOG_PENDING.friendId, exports.INNER_DIALOG_PENDING.channel, exports.INNER_DIALOG_PENDING.key);
52
53
  }
54
+ function hasPendingMessages(pendingDir) {
55
+ if (!fs.existsSync(pendingDir))
56
+ return false;
57
+ try {
58
+ return fs.readdirSync(pendingDir).some((entry) => entry.endsWith(".json") || entry.endsWith(".json.processing"));
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
53
64
  function drainPending(pendingDir) {
54
65
  if (!fs.existsSync(pendingDir))
55
66
  return [];
@@ -412,6 +412,12 @@ function memoryFriendToolContractSection() {
412
412
  - My psyche files (SOUL, IDENTITY, TACIT, LORE, ASPIRATIONS) are always loaded - I already know who I am.
413
413
  - My task board is always loaded - I already know my work.`;
414
414
  }
415
+ function bridgeContextSection(options) {
416
+ const bridgeContext = options?.bridgeContext?.trim() ?? "";
417
+ if (!bridgeContext)
418
+ return "";
419
+ return bridgeContext.startsWith("## ") ? bridgeContext : `## active bridge work\n${bridgeContext}`;
420
+ }
415
421
  function toolBehaviorSection(options) {
416
422
  if (!(options?.toolChoiceRequired ?? true))
417
423
  return "";
@@ -543,13 +549,14 @@ async function buildSystem(channel = "cli", options, context) {
543
549
  mixedTrustGroupSection(context),
544
550
  skillsSection(),
545
551
  taskBoardSection(),
552
+ bridgeContextSection(options),
546
553
  buildSessionSummary({
547
554
  sessionsDir: path.join((0, identity_1.getAgentRoot)(), "state", "sessions"),
548
555
  friendsDir: path.join((0, identity_1.getAgentRoot)(), "friends"),
549
556
  agentName: (0, identity_1.getAgentName)(),
550
557
  currentFriendId: context?.friend?.id,
551
558
  currentChannel: channel,
552
- currentKey: "session",
559
+ currentKey: options?.currentSessionKey ?? "session",
553
560
  }),
554
561
  memoryFriendToolContractSection(),
555
562
  toolBehaviorSection(options),
@@ -59,6 +59,12 @@ function activeSessionLines(tasks) {
59
59
  });
60
60
  return active.map((task) => task.stem).sort();
61
61
  }
62
+ function activeBridgeLines(tasks) {
63
+ return tasks
64
+ .filter((task) => typeof task.frontmatter.active_bridge === "string" && String(task.frontmatter.active_bridge).trim())
65
+ .map((task) => `${task.stem} -> ${String(task.frontmatter.active_bridge).trim()}`)
66
+ .sort();
67
+ }
62
68
  function actionRequired(index, byStatus) {
63
69
  const actions = [...index.parseErrors, ...index.invalidFilenames.map((filePath) => `bad filename: ${filePath}`)];
64
70
  if (byStatus.blocked.length > 0) {
@@ -99,6 +105,11 @@ function buildTaskBoard(index) {
99
105
  fullLines.push("## active sessions");
100
106
  fullLines.push(active.map((line) => `- ${line}`).join("\n"));
101
107
  }
108
+ const activeBridges = activeBridgeLines(index.tasks);
109
+ if (activeBridges.length > 0) {
110
+ fullLines.push("## active bridges");
111
+ fullLines.push(activeBridges.map((line) => `- ${line}`).join("\n"));
112
+ }
102
113
  return {
103
114
  compact,
104
115
  full: fullLines.join("\n\n"),
@@ -106,6 +117,7 @@ function buildTaskBoard(index) {
106
117
  actionRequired: actionRequired(index, byStatus),
107
118
  unresolvedDependencies: unresolved,
108
119
  activeSessions: active,
120
+ activeBridges,
109
121
  };
110
122
  }
111
123
  function boardStatus(board, status) {
@@ -115,6 +115,12 @@ class FileTaskModule {
115
115
  frontmatter.parent_task = null;
116
116
  frontmatter.depends_on = [];
117
117
  }
118
+ if (input.activeBridge && input.activeBridge.trim()) {
119
+ frontmatter.active_bridge = input.activeBridge.trim();
120
+ }
121
+ if (Array.isArray(input.bridgeSessions) && input.bridgeSessions.length > 0) {
122
+ frontmatter.bridge_sessions = input.bridgeSessions.filter((value) => typeof value === "string" && value.trim());
123
+ }
118
124
  const content = (0, parser_1.renderTaskFile)(frontmatter, input.body);
119
125
  const validation = (0, middleware_1.validateWrite)(filePath, content);
120
126
  if (!validation.ok) {
@@ -125,6 +131,21 @@ class FileTaskModule {
125
131
  (0, scanner_1.clearTaskScanCache)();
126
132
  return filePath;
127
133
  }
134
+ bindBridge(name, input) {
135
+ const task = this.getTask(name);
136
+ if (!task) {
137
+ return { ok: false, reason: `task not found: ${name}` };
138
+ }
139
+ const content = fs.readFileSync(task.path, "utf-8");
140
+ const parsed = (0, parser_1.parseTaskFile)(content, task.path);
141
+ const frontmatter = removeRuntimeFrontmatter(parsed.frontmatter);
142
+ frontmatter.active_bridge = input.bridgeId.trim();
143
+ frontmatter.bridge_sessions = input.sessionRefs.filter((value) => value.trim().length > 0);
144
+ frontmatter.updated = formatDate();
145
+ fs.writeFileSync(task.path, (0, parser_1.renderTaskFile)(frontmatter, parsed.body), "utf-8");
146
+ (0, scanner_1.clearTaskScanCache)();
147
+ return { ok: true, path: task.path };
148
+ }
128
149
  updateStatus(name, toStatus) {
129
150
  const normalized = (0, transitions_1.normalizeTaskStatus)(toStatus);
130
151
  if (!normalized) {
@@ -44,6 +44,8 @@ const runtime_1 = require("../nerves/runtime");
44
44
  const identity_1 = require("../heart/identity");
45
45
  const socket_client_1 = require("../heart/daemon/socket-client");
46
46
  const thoughts_1 = require("../heart/daemon/thoughts");
47
+ const manager_1 = require("../heart/bridges/manager");
48
+ const session_recall_1 = require("../heart/session-recall");
47
49
  const tools_1 = require("./coding/tools");
48
50
  const memory_1 = require("../mind/memory");
49
51
  const pending_1 = require("../mind/pending");
@@ -61,6 +63,16 @@ function buildContextDiff(lines, changeStart, changeEnd, contextSize = 3) {
61
63
  }
62
64
  return result.join("\n");
63
65
  }
66
+ const NO_SESSION_FOUND_MESSAGE = "no session found for that friend/channel/key combination.";
67
+ const EMPTY_SESSION_MESSAGE = "session exists but has no non-system messages.";
68
+ async function recallSessionSafely(options) {
69
+ try {
70
+ return await (0, session_recall_1.recallSession)(options);
71
+ }
72
+ catch {
73
+ return { kind: "missing" };
74
+ }
75
+ }
64
76
  exports.baseToolDefinitions = [
65
77
  {
66
78
  tool: {
@@ -585,6 +597,103 @@ exports.baseToolDefinitions = [
585
597
  },
586
598
  },
587
599
  // -- cross-session awareness --
600
+ {
601
+ tool: {
602
+ type: "function",
603
+ function: {
604
+ name: "bridge_manage",
605
+ description: "create and manage shared live-work bridges across already-active sessions.",
606
+ parameters: {
607
+ type: "object",
608
+ properties: {
609
+ action: {
610
+ type: "string",
611
+ enum: ["begin", "attach", "status", "promote_task", "complete", "cancel"],
612
+ },
613
+ bridgeId: { type: "string", description: "bridge id for all actions except begin" },
614
+ objective: { type: "string", description: "objective for begin" },
615
+ summary: { type: "string", description: "optional concise shared-work summary" },
616
+ friendId: { type: "string", description: "target friend id for attach" },
617
+ channel: { type: "string", description: "target channel for attach" },
618
+ key: { type: "string", description: "target session key for attach (defaults to 'session')" },
619
+ title: { type: "string", description: "task title override for promote_task" },
620
+ category: { type: "string", description: "task category override for promote_task" },
621
+ body: { type: "string", description: "task body override for promote_task" },
622
+ },
623
+ required: ["action"],
624
+ },
625
+ },
626
+ },
627
+ handler: async (args, ctx) => {
628
+ const manager = (0, manager_1.createBridgeManager)();
629
+ const action = (args.action || "").trim();
630
+ if (action === "begin") {
631
+ if (!ctx?.currentSession) {
632
+ return "bridge_manage begin requires an active session context.";
633
+ }
634
+ const objective = (args.objective || "").trim();
635
+ if (!objective)
636
+ return "objective is required for bridge begin.";
637
+ return (0, manager_1.formatBridgeStatus)(manager.beginBridge({
638
+ objective,
639
+ summary: (args.summary || objective).trim(),
640
+ session: ctx.currentSession,
641
+ }));
642
+ }
643
+ const bridgeId = (args.bridgeId || "").trim();
644
+ if (!bridgeId) {
645
+ return "bridgeId is required for this bridge action.";
646
+ }
647
+ if (action === "attach") {
648
+ const friendId = (args.friendId || "").trim();
649
+ const channel = (args.channel || "").trim();
650
+ const key = (args.key || "session").trim();
651
+ if (!friendId || !channel) {
652
+ return "friendId and channel are required for bridge attach.";
653
+ }
654
+ const sessionPath = (0, config_1.resolveSessionPath)(friendId, channel, key);
655
+ const recall = await recallSessionSafely({
656
+ sessionPath,
657
+ friendId,
658
+ channel,
659
+ key,
660
+ messageCount: 20,
661
+ trustLevel: ctx?.context?.friend?.trustLevel,
662
+ summarize: ctx?.summarize,
663
+ });
664
+ if (recall.kind === "missing") {
665
+ return NO_SESSION_FOUND_MESSAGE;
666
+ }
667
+ return (0, manager_1.formatBridgeStatus)(manager.attachSession(bridgeId, {
668
+ friendId,
669
+ channel,
670
+ key,
671
+ sessionPath,
672
+ snapshot: recall.kind === "ok" ? recall.snapshot : EMPTY_SESSION_MESSAGE,
673
+ }));
674
+ }
675
+ if (action === "status") {
676
+ const bridge = manager.getBridge(bridgeId);
677
+ if (!bridge)
678
+ return `bridge not found: ${bridgeId}`;
679
+ return (0, manager_1.formatBridgeStatus)(bridge);
680
+ }
681
+ if (action === "promote_task") {
682
+ return (0, manager_1.formatBridgeStatus)(manager.promoteBridgeToTask(bridgeId, {
683
+ title: args.title,
684
+ category: args.category,
685
+ body: args.body,
686
+ }));
687
+ }
688
+ if (action === "complete") {
689
+ return (0, manager_1.formatBridgeStatus)(manager.completeBridge(bridgeId));
690
+ }
691
+ if (action === "cancel") {
692
+ return (0, manager_1.formatBridgeStatus)(manager.cancelBridge(bridgeId));
693
+ }
694
+ return `unknown bridge action: ${action}`;
695
+ },
696
+ },
588
697
  {
589
698
  tool: {
590
699
  type: "function",
@@ -605,43 +714,36 @@ exports.baseToolDefinitions = [
605
714
  },
606
715
  },
607
716
  handler: async (args, ctx) => {
608
- try {
609
- const friendId = args.friendId;
610
- const channel = args.channel;
611
- const key = args.key || "session";
612
- const count = parseInt(args.messageCount || "20", 10);
613
- const mode = args.mode || "transcript";
614
- if (mode === "status") {
615
- if (friendId !== "self" || channel !== "inner") {
616
- return "status mode is only available for self/inner dialog.";
617
- }
618
- const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
619
- const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
620
- return (0, thoughts_1.formatInnerDialogStatus)((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
621
- }
622
- const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
623
- const raw = fs.readFileSync(sessFile, "utf-8");
624
- const data = JSON.parse(raw);
625
- const messages = (data.messages || [])
626
- .filter((m) => m.role !== "system");
627
- const tail = messages.slice(-count);
628
- if (tail.length === 0)
629
- return "session exists but has no non-system messages.";
630
- const transcript = tail.map((m) => `[${m.role}] ${m.content}`).join("\n");
631
- // LLM summarization when summarize function is available
632
- if (ctx?.summarize) {
633
- const trustLevel = ctx.context?.friend?.trustLevel ?? "family";
634
- const isSelfQuery = friendId === "self";
635
- const instruction = isSelfQuery
636
- ? "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning."
637
- : `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
638
- return await ctx.summarize(transcript, instruction);
717
+ const friendId = args.friendId;
718
+ const channel = args.channel;
719
+ const key = args.key || "session";
720
+ const count = parseInt(args.messageCount || "20", 10);
721
+ const mode = args.mode || "transcript";
722
+ if (mode === "status") {
723
+ if (friendId !== "self" || channel !== "inner") {
724
+ return "status mode is only available for self/inner dialog.";
639
725
  }
640
- return transcript;
726
+ const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)((0, identity_1.getAgentRoot)());
727
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
728
+ return (0, thoughts_1.formatInnerDialogStatus)((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
729
+ }
730
+ const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
731
+ const recall = await recallSessionSafely({
732
+ sessionPath: sessFile,
733
+ friendId,
734
+ channel,
735
+ key,
736
+ messageCount: count,
737
+ trustLevel: ctx?.context?.friend?.trustLevel,
738
+ summarize: ctx?.summarize,
739
+ });
740
+ if (recall.kind === "missing") {
741
+ return NO_SESSION_FOUND_MESSAGE;
641
742
  }
642
- catch {
643
- return "no session found for that friend/channel/key combination.";
743
+ if (recall.kind === "empty") {
744
+ return EMPTY_SESSION_MESSAGE;
644
745
  }
746
+ return recall.summary;
645
747
  },
646
748
  },
647
749
  {
@@ -205,6 +205,8 @@ function summarizeArgs(name, args) {
205
205
  if (name === "save_friend_note") {
206
206
  return summarizeKeyValues(args, ["type", "key", "content"]);
207
207
  }
208
+ if (name === "bridge_manage")
209
+ return summarizeKeyValues(args, ["action", "bridgeId", "objective", "friendId", "channel", "key"]);
208
210
  if (name === "ado_backlog_list")
209
211
  return summarizeKeyValues(args, ["organization", "project"]);
210
212
  if (name === "ado_batch_update")