@ouro.bot/cli 0.1.0-alpha.74 → 0.1.0-alpha.75

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
@@ -2,9 +2,16 @@
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
4
  {
5
- "version": "0.1.0-alpha.74",
5
+ "version": "0.1.0-alpha.75",
6
6
  "changes": [
7
- "Fix: `ouro config model` now pings the model with a tiny API call before switching, rejecting the change if the model returns an error (403, not supported, etc.)."
7
+ "Inner dialog is now experienced as private thinking, not system dispatch. go_inward tool lets the agent take a thread inward atomically, with a pipeline-generated handoff packet framed as self-directed thought.",
8
+ "Return obligations are enforced: the agent cannot move on from a conversation without addressing what it promised. Rejection messages orient rather than punish.",
9
+ "Center-of-gravity steering: the system prompt tells the agent where its attention is in selfhood language, not dashboard labels.",
10
+ "All status rendering rewritten from telemetry to self-awareness: \"what i'm holding\" replaces \"active work\", \"where my attention is\" replaces \"center of gravity\", \"what i'm sensing\" replaces \"delegation hint\".",
11
+ "New query_commitments tool: the agent can ask itself \"what am I holding right now?\" and get genuine self-reflection.",
12
+ "Bridge suggestions trigger earlier when multi-session pressure is detected. Inward completions automatically inherit bridge context for return routing.",
13
+ "Inner dialog modes (reflect/plan/relay) give the agent awareness of what kind of thinking it's doing.",
14
+ "Canonical InnerJob type with lifecycle (idle/queued/running/surfaced) replaces reconstructed status."
8
15
  ]
9
16
  },
10
17
  {
@@ -43,7 +50,7 @@
43
50
  {
44
51
  "version": "0.1.0-alpha.69",
45
52
  "changes": [
46
- "Generic MCP client: ouroboros agents can now connect to any MCP server (e.g., agency mcp ado, agency mcp mail) configured in agent.json. Zero new dependencies pure JSON-RPC over stdio.",
53
+ "Generic MCP client: ouroboros agents can now connect to any MCP server (e.g., agency mcp ado, agency mcp mail) configured in agent.json. Zero new dependencies \u2014 pure JSON-RPC over stdio.",
47
54
  "New `ouro mcp list` and `ouro mcp call` CLI commands route through the daemon socket to persistent MCP connections, so agents use shared server instances instead of spawning fresh ones per call.",
48
55
  "MCP tools are injected into the agent's system prompt on startup, so agents know what external capabilities are available without a discovery step.",
49
56
  "Trust manifest: `mcp list` requires acquaintance trust, `mcp call` requires friend trust."
@@ -52,7 +59,7 @@
52
59
  {
53
60
  "version": "0.1.0-alpha.68",
54
61
  "changes": [
55
- "New no_response tool lets agents stay silent in group chats when the moment doesn't call for a reply reactions, side conversations, and tapbacks no longer trigger unwanted responses.",
62
+ "New no_response tool lets agents stay silent in group chats when the moment doesn't call for a reply \u2014 reactions, side conversations, and tapbacks no longer trigger unwanted responses.",
56
63
  "Group chat participation prompt teaches agents to be intentional participants, comfortable with silence, and to prefer reactions over full text replies when appropriate.",
57
64
  "System prompt includes --agent flag in all ouro CLI examples for non-daemon deployments. Azure startup symlinks ouro CLI into /usr/local/bin."
58
65
  ]
@@ -66,7 +73,7 @@
66
73
  {
67
74
  "version": "0.1.0-alpha.65",
68
75
  "changes": [
69
- "Tool permissions overhauled: channel-level blocking removed, all tools now visible on all channels. Guardrails are invocation-level with two layers structural (edit-requires-read, destructive pattern blocking, protected paths) always on for everyone, and trust-level (ouro CLI per-subcommand trust manifest, general CLI allowlists, bundle-scoped writes) for untrusted contexts.",
76
+ "Tool permissions overhauled: channel-level blocking removed, all tools now visible on all channels. Guardrails are invocation-level with two layers \u2014 structural (edit-requires-read, destructive pattern blocking, protected paths) always on for everyone, and trust-level (ouro CLI per-subcommand trust manifest, general CLI allowlists, bundle-scoped writes) for untrusted contexts.",
70
77
  "New `ouro changelog` CLI subcommand reads changelog.json and supports `--from <version>` for delta filtering, so agents can introspect their own update history on any channel.",
71
78
  "Compound shell commands (&&, ;, |, $()) are blocked for untrusted users to prevent smuggling dangerous operations behind safe prefixes.",
72
79
  "Azure App Service deployment migrated from zip-deploy to npm-based harness install with persistent agent bundle and managed identity auth."
@@ -50,7 +50,7 @@ function suggestBridgeForActiveWork(input) {
50
50
  .sort((a, b) => {
51
51
  return b.lastActivityMs - a.lastActivityMs;
52
52
  });
53
- if (!hasSharedObligationPressure(input) || targetCandidates.length !== 1) {
53
+ if (!hasSharedObligationPressure(input) || targetCandidates.length === 0) {
54
54
  return null;
55
55
  }
56
56
  const targetSession = targetCandidates[0];
@@ -134,30 +134,68 @@ function buildActiveWorkFrame(input) {
134
134
  return frame;
135
135
  }
136
136
  function formatActiveWorkFrame(frame) {
137
- const lines = ["## active work"];
137
+ const lines = ["## what i'm holding"];
138
+ // Session line
138
139
  if (frame.currentSession) {
139
- lines.push(`current session: ${formatSessionLabel(frame.currentSession)}`);
140
+ let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
141
+ if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
142
+ sessionLine += ` i told them i'd ${frame.currentObligation.trim()}.`;
143
+ }
144
+ else if (frame.mustResolveBeforeHandoff) {
145
+ sessionLine += " i need to finish what i started here before moving on.";
146
+ }
147
+ lines.push("");
148
+ lines.push(sessionLine);
140
149
  }
141
- lines.push(`center: ${frame.centerOfGravity}`);
142
- if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
143
- lines.push(`obligation: ${frame.currentObligation.trim()}`);
150
+ else {
151
+ lines.push("");
152
+ lines.push("i'm not in a conversation right now.");
144
153
  }
145
- if (frame.mustResolveBeforeHandoff) {
146
- lines.push("handoff pressure: must resolve before handoff");
154
+ // Inner status block
155
+ const job = frame.inner?.job;
156
+ if (job) {
157
+ if (job.status === "queued") {
158
+ let queuedLine = "i have a thought queued up for private attention.";
159
+ if (frame.inner?.contentSnippet) {
160
+ queuedLine += `\nit's about: "${frame.inner.contentSnippet}"`;
161
+ }
162
+ lines.push("");
163
+ lines.push(queuedLine);
164
+ }
165
+ else if (job.status === "running") {
166
+ const originName = job.origin?.friendName ?? job.origin?.friendId;
167
+ let runningLine = originName
168
+ ? `i'm thinking through something privately right now. ${originName} asked about something and i wanted to give it real thought.`
169
+ : "i'm thinking through something privately right now.";
170
+ if (frame.inner?.obligationPending) {
171
+ runningLine += "\ni still owe them an answer.";
172
+ }
173
+ lines.push("");
174
+ lines.push(runningLine);
175
+ }
176
+ else if (job.status === "surfaced") {
177
+ let surfacedLine = "i finished thinking about something privately. i should bring my answer back.";
178
+ if (job.surfacedResult) {
179
+ const truncated = job.surfacedResult.length > 120 ? job.surfacedResult.slice(0, 117) + "..." : job.surfacedResult;
180
+ surfacedLine += `\nwhat i came to: ${truncated}`;
181
+ }
182
+ lines.push("");
183
+ lines.push(surfacedLine);
184
+ }
185
+ // idle, returned, abandoned: omitted
147
186
  }
148
- const innerStatus = frame.inner?.status ?? "idle";
149
- const innerHasPending = frame.inner?.hasPending === true;
150
- lines.push(`inner status: ${innerStatus}${innerHasPending ? " (pending queued)" : ""}`);
187
+ // Task pressure
151
188
  if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
152
- lines.push(`live tasks: ${frame.taskPressure.liveTaskNames.join(", ")}`);
189
+ lines.push("");
190
+ lines.push(`i'm also tracking: ${frame.taskPressure.liveTaskNames.join(", ")}.`);
153
191
  }
192
+ // Bridges
154
193
  if ((frame.bridges ?? []).length > 0) {
155
194
  const bridgeLabels = frame.bridges.map((bridge) => `${bridge.id} [${(0, state_machine_1.bridgeStateLabel)(bridge)}]`);
156
- lines.push(`bridges: ${bridgeLabels.join(", ")}`);
157
- }
158
- if (frame.friendActivity?.freshestForCurrentFriend) {
159
- lines.push(`freshest friend-facing session: ${formatSessionLabel(frame.friendActivity.freshestForCurrentFriend)}`);
195
+ lines.push("");
196
+ lines.push(`i have shared work spanning sessions: ${bridgeLabels.join(", ")}.`);
160
197
  }
198
+ // Target candidates (keep factual format)
161
199
  const targetCandidatesBlock = frame.targetCandidates && frame.targetCandidates.length > 0
162
200
  ? (0, target_resolution_1.formatTargetSessionCandidates)(frame.targetCandidates)
163
201
  : "";
@@ -165,13 +203,14 @@ function formatActiveWorkFrame(frame) {
165
203
  lines.push("");
166
204
  lines.push(targetCandidatesBlock);
167
205
  }
206
+ // Bridge suggestion
168
207
  if (frame.bridgeSuggestion) {
169
- if (frame.bridgeSuggestion.kind === "attach-existing") {
170
- lines.push(`suggested bridge: attach ${frame.bridgeSuggestion.bridgeId} -> ${formatSessionLabel(frame.bridgeSuggestion.targetSession)}`);
208
+ lines.push("");
209
+ if (frame.bridgeSuggestion.kind === "begin-new") {
210
+ lines.push(`this work touches my conversation on ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} too -- i should connect these threads.`);
171
211
  }
172
212
  else {
173
- lines.push(`suggested bridge: begin -> ${formatSessionLabel(frame.bridgeSuggestion.targetSession)}`);
174
- lines.push(`bridge objective hint: ${frame.bridgeSuggestion.objectiveHint}`);
213
+ lines.push(`this work relates to bridge ${frame.bridgeSuggestion.bridgeId} -- i should connect ${formatSessionLabel(frame.bridgeSuggestion.targetSession)} to it.`);
175
214
  }
176
215
  }
177
216
  return lines.join("\n");
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveCommitments = deriveCommitments;
4
+ exports.formatCommitments = formatCommitments;
5
+ const runtime_1 = require("../nerves/runtime");
6
+ function deriveCommitments(activeWorkFrame, innerJob) {
7
+ const committedTo = [];
8
+ const completionCriteria = [];
9
+ const safeToIgnore = [];
10
+ // Obligation
11
+ if (typeof activeWorkFrame.currentObligation === "string" && activeWorkFrame.currentObligation.trim().length > 0) {
12
+ committedTo.push(`i told them i'd ${activeWorkFrame.currentObligation.trim()}`);
13
+ }
14
+ // Inner job
15
+ if (innerJob.status === "queued" || innerJob.status === "running") {
16
+ const contentSuffix = innerJob.content ? ` -- ${innerJob.content.slice(0, 60)}` : "";
17
+ committedTo.push(`i'm thinking through something privately${contentSuffix}`);
18
+ }
19
+ else if (innerJob.status === "surfaced") {
20
+ committedTo.push("i finished thinking about something and need to bring it back");
21
+ }
22
+ // mustResolveBeforeHandoff
23
+ if (activeWorkFrame.mustResolveBeforeHandoff) {
24
+ committedTo.push("i need to finish what i started before moving on");
25
+ completionCriteria.push("resolve the current thread before moving on");
26
+ }
27
+ // Bridges
28
+ for (const bridge of activeWorkFrame.bridges) {
29
+ committedTo.push(`i have shared work: ${bridge.summary || bridge.objective}`);
30
+ }
31
+ if (activeWorkFrame.bridges.length > 0) {
32
+ completionCriteria.push("keep shared work aligned across sessions");
33
+ }
34
+ // Tasks
35
+ for (const taskName of activeWorkFrame.taskPressure?.liveTaskNames ?? []) {
36
+ committedTo.push(`i'm tracking: ${taskName}`);
37
+ }
38
+ // Obligation completion criteria
39
+ if (innerJob.obligationStatus === "pending") {
40
+ const name = innerJob.origin?.friendName ?? innerJob.origin?.friendId ?? "them";
41
+ completionCriteria.push(`bring my answer back to ${name}`);
42
+ }
43
+ // Default completion criteria
44
+ if (completionCriteria.length === 0) {
45
+ completionCriteria.push("just be present in this conversation");
46
+ }
47
+ // Safe to ignore
48
+ if (innerJob.status === "idle" && !(activeWorkFrame.inner?.hasPending)) {
49
+ safeToIgnore.push("no private thinking in progress");
50
+ }
51
+ if (activeWorkFrame.bridges.length === 0) {
52
+ safeToIgnore.push("no shared work to coordinate");
53
+ }
54
+ if ((activeWorkFrame.taskPressure?.liveTaskNames ?? []).length === 0) {
55
+ safeToIgnore.push("no active tasks to track");
56
+ }
57
+ (0, runtime_1.emitNervesEvent)({
58
+ component: "engine",
59
+ event: "engine.commitments_derive",
60
+ message: "derived commitments frame",
61
+ meta: { committedCount: committedTo.length, criteriaCount: completionCriteria.length },
62
+ });
63
+ return { committedTo, completionCriteria, safeToIgnore };
64
+ }
65
+ function formatCommitments(commitments) {
66
+ const sections = [];
67
+ if (commitments.committedTo.length === 0) {
68
+ sections.push("i'm not holding anything specific right now. i'm free to be present.");
69
+ }
70
+ else {
71
+ sections.push("## what i'm holding right now");
72
+ sections.push("");
73
+ sections.push(commitments.committedTo.map((c) => `- ${c}`).join("\n"));
74
+ }
75
+ sections.push("");
76
+ sections.push("## what \"done\" looks like");
77
+ sections.push(commitments.completionCriteria.map((c) => `- ${c}`).join("\n"));
78
+ sections.push("");
79
+ sections.push("## what i can let go of");
80
+ sections.push(commitments.safeToIgnore.map((c) => `- ${c}`).join("\n"));
81
+ return sections.join("\n");
82
+ }
@@ -7,6 +7,7 @@ exports.getModel = getModel;
7
7
  exports.getProvider = getProvider;
8
8
  exports.createSummarize = createSummarize;
9
9
  exports.getProviderDisplayLabel = getProviderDisplayLabel;
10
+ exports.getFinalAnswerRetryError = getFinalAnswerRetryError;
10
11
  exports.stripLastToolCalls = stripLastToolCalls;
11
12
  exports.repairOrphanedToolCalls = repairOrphanedToolCalls;
12
13
  exports.isTransientError = isTransientError;
@@ -25,6 +26,9 @@ const azure_1 = require("./providers/azure");
25
26
  const minimax_1 = require("./providers/minimax");
26
27
  const openai_codex_1 = require("./providers/openai-codex");
27
28
  const github_copilot_1 = require("./providers/github-copilot");
29
+ const pending_1 = require("../mind/pending");
30
+ const identity_2 = require("./identity");
31
+ const socket_client_1 = require("./daemon/socket-client");
28
32
  let _providerRuntime = null;
29
33
  function getProviderRuntimeFingerprint() {
30
34
  const provider = (0, identity_1.loadAgentConfig)().provider;
@@ -163,6 +167,46 @@ Object.defineProperty(exports, "toResponsesTools", { enumerable: true, get: func
163
167
  // Re-export prompt functions for backward compat
164
168
  var prompt_2 = require("../mind/prompt");
165
169
  Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function () { return prompt_2.buildSystem; } });
170
+ const DELEGATION_REASON_PROSE_HANDOFF = {
171
+ explicit_reflection: "something in the conversation called for reflection",
172
+ cross_session: "this touches other conversations",
173
+ bridge_state: "there's shared work spanning sessions",
174
+ task_state: "there are active tasks that relate to this",
175
+ non_fast_path_tool: "this needs tools beyond a simple reply",
176
+ unresolved_obligation: "there's an unresolved commitment from an earlier conversation",
177
+ };
178
+ function buildGoInwardHandoffPacket(params) {
179
+ const reasons = params.delegationDecision?.reasons ?? [];
180
+ const reasonProse = reasons.length > 0
181
+ ? reasons.map((r) => DELEGATION_REASON_PROSE_HANDOFF[r]).join("; ")
182
+ : "this felt like it needed more thought";
183
+ const returnAddress = params.currentSession
184
+ ? `${params.currentSession.friendId}/${params.currentSession.channel}/${params.currentSession.key}`
185
+ : "no specific return -- just thinking";
186
+ let obligationLine;
187
+ if (params.outwardClosureRequired && params.currentSession) {
188
+ obligationLine = `i need to come back to ${params.currentSession.friendId} with something`;
189
+ }
190
+ else {
191
+ obligationLine = "no obligation -- just thinking";
192
+ }
193
+ return [
194
+ "## what i need to think about",
195
+ params.content,
196
+ "",
197
+ "## why this came up",
198
+ reasonProse,
199
+ "",
200
+ "## where to bring it back",
201
+ returnAddress,
202
+ "",
203
+ "## what i owe",
204
+ obligationLine,
205
+ "",
206
+ "## thinking mode",
207
+ params.mode,
208
+ ].join("\n");
209
+ }
166
210
  function parseFinalAnswerPayload(argumentsText) {
167
211
  try {
168
212
  const parsed = JSON.parse(argumentsText);
@@ -183,13 +227,33 @@ function parseFinalAnswerPayload(argumentsText) {
183
227
  return {};
184
228
  }
185
229
  }
186
- function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp) {
230
+ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, innerJob) {
231
+ // 1. Delegation adherence: delegate-inward without evidence of inward action
232
+ if (delegationDecision?.target === "delegate-inward" && !sawSendMessageSelf && !sawGoInward && !sawQuerySession) {
233
+ (0, runtime_1.emitNervesEvent)({
234
+ event: "engine.delegation_adherence_rejected",
235
+ component: "engine",
236
+ message: "delegation adherence check rejected final_answer",
237
+ meta: {
238
+ target: delegationDecision.target,
239
+ reasons: delegationDecision.reasons,
240
+ },
241
+ });
242
+ return "you're reaching for a final answer, but part of you knows this needs more thought. take it inward -- go_inward will let you think privately, or send_message(self) if you just want to leave yourself a note.";
243
+ }
244
+ // 2. Pending obligation not addressed
245
+ if (innerJob?.obligationStatus === "pending" && !sawSendMessageSelf && !sawGoInward) {
246
+ return "you're still holding something from an earlier conversation -- someone is waiting for your answer. finish the thought first, or go_inward to keep working on it privately.";
247
+ }
248
+ // 3. mustResolveBeforeHandoff + missing intent
187
249
  if (mustResolveBeforeHandoff && !intent) {
188
250
  return "your final_answer is missing required intent. when you must keep going until done or blocked, call final_answer again with answer plus intent=complete, blocked, or direct_reply.";
189
251
  }
252
+ // 4. mustResolveBeforeHandoff + direct_reply without follow-up
190
253
  if (mustResolveBeforeHandoff && intent === "direct_reply" && !sawSteeringFollowUp) {
191
254
  return "your final_answer used intent=direct_reply without a newer steering follow-up. continue the unresolved work, or call final_answer again with intent=complete or blocked when appropriate.";
192
255
  }
256
+ // 5. Default malformed fallback
193
257
  return "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
194
258
  }
195
259
  // Re-export kick utilities for backward compat
@@ -405,6 +469,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
405
469
  let sawSteeringFollowUp = false;
406
470
  let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
407
471
  let currentReasoningEffort = "medium";
472
+ let sawSendMessageSelf = false;
473
+ let sawGoInward = false;
474
+ let sawQuerySession = false;
475
+ let sawBridgeManage = false;
408
476
  // Prevent MaxListenersExceeded warning — each iteration adds a listener
409
477
  try {
410
478
  require("events").setMaxListeners(50, signal);
@@ -429,7 +497,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
429
497
  // model must call a tool every turn — final_answer is how it exits.
430
498
  // Overridable via options.toolChoiceRequired = false (e.g. CLI).
431
499
  const activeTools = toolChoiceRequired
432
- ? [...baseTools, ...(currentContext?.isGroupChat ? [tools_1.noResponseTool] : []), tools_1.finalAnswerTool]
500
+ ? [...baseTools, tools_1.goInwardTool, ...(currentContext?.isGroupChat ? [tools_1.noResponseTool] : []), tools_1.finalAnswerTool]
433
501
  : baseTools;
434
502
  const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
435
503
  if (steeringFollowUps.length > 0) {
@@ -553,7 +621,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
553
621
  // malformed. Clear any partial streamed text or noise, then push the
554
622
  // assistant msg + error tool result and let the model try again.
555
623
  callbacks.onClearText?.();
556
- const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp);
624
+ const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward);
557
625
  messages.push(msg);
558
626
  messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: retryError });
559
627
  providerRuntime.appendToolOutput(result.toolCalls[0].id, retryError);
@@ -584,8 +652,77 @@ async function runAgent(messages, callbacks, channel, signal, options) {
584
652
  done = true;
585
653
  continue;
586
654
  }
655
+ // Check for go_inward sole call: intercept before tool execution
656
+ const isSoleGoInward = result.toolCalls.length === 1 && result.toolCalls[0].name === "go_inward";
657
+ if (isSoleGoInward) {
658
+ let parsedArgs = {};
659
+ try {
660
+ parsedArgs = JSON.parse(result.toolCalls[0].arguments);
661
+ }
662
+ catch { /* ignore */ }
663
+ /* v8 ignore next -- defensive: content always string from model @preserve */
664
+ const content = typeof parsedArgs.content === "string" ? parsedArgs.content : "";
665
+ const answer = typeof parsedArgs.answer === "string" ? parsedArgs.answer : undefined;
666
+ const parsedMode = parsedArgs.mode === "reflect" || parsedArgs.mode === "plan" || parsedArgs.mode === "relay"
667
+ ? parsedArgs.mode
668
+ : undefined;
669
+ const mode = parsedMode || "reflect";
670
+ // Emit outward answer if provided
671
+ if (answer) {
672
+ callbacks.onClearText?.();
673
+ callbacks.onTextChunk(answer);
674
+ }
675
+ // Build handoff packet and enqueue
676
+ const handoffContent = buildGoInwardHandoffPacket({
677
+ content,
678
+ mode,
679
+ delegationDecision: options?.delegationDecision,
680
+ currentSession: options?.toolContext?.currentSession ?? null,
681
+ currentObligation: options?.currentObligation ?? null,
682
+ outwardClosureRequired: options?.delegationDecision?.outwardClosureRequired ?? false,
683
+ });
684
+ const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
685
+ const currentSession = options?.toolContext?.currentSession;
686
+ const isInnerChannel = currentSession?.friendId === "self" && currentSession?.channel === "inner";
687
+ const envelope = {
688
+ from: (0, identity_2.getAgentName)(),
689
+ friendId: "self",
690
+ channel: "inner",
691
+ key: "dialog",
692
+ content: handoffContent,
693
+ timestamp: Date.now(),
694
+ mode,
695
+ ...(currentSession && !isInnerChannel ? {
696
+ delegatedFrom: {
697
+ friendId: currentSession.friendId,
698
+ channel: currentSession.channel,
699
+ key: currentSession.key,
700
+ },
701
+ obligationStatus: "pending",
702
+ } : {}),
703
+ };
704
+ (0, pending_1.queuePendingMessage)(pendingDir, envelope);
705
+ try {
706
+ await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
707
+ }
708
+ catch { /* daemon may not be running */ }
709
+ sawGoInward = true;
710
+ messages.push(msg);
711
+ const ack = "(going inward)";
712
+ messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
713
+ providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
714
+ (0, runtime_1.emitNervesEvent)({
715
+ component: "engine",
716
+ event: "engine.go_inward",
717
+ message: "taking thread inward",
718
+ meta: { mode, hasAnswer: answer !== undefined, contentSnippet: content.slice(0, 80) },
719
+ });
720
+ outcome = "go_inward";
721
+ done = true;
722
+ continue;
723
+ }
587
724
  messages.push(msg);
588
- // SHARED: execute tools (final_answer and no_response in mixed calls are rejected inline)
725
+ // SHARED: execute tools (final_answer, no_response, go_inward in mixed calls are rejected inline)
589
726
  for (const tc of result.toolCalls) {
590
727
  if (signal?.aborted)
591
728
  break;
@@ -603,6 +740,13 @@ async function runAgent(messages, callbacks, channel, signal, options) {
603
740
  providerRuntime.appendToolOutput(tc.id, rejection);
604
741
  continue;
605
742
  }
743
+ // Intercept go_inward in mixed call: reject it
744
+ if (tc.name === "go_inward") {
745
+ const rejection = "rejected: go_inward must be the only tool call. finish your other work first, then call go_inward alone.";
746
+ messages.push({ role: "tool", tool_call_id: tc.id, content: rejection });
747
+ providerRuntime.appendToolOutput(tc.id, rejection);
748
+ continue;
749
+ }
606
750
  let args = {};
607
751
  try {
608
752
  args = JSON.parse(tc.arguments);
@@ -610,6 +754,15 @@ async function runAgent(messages, callbacks, channel, signal, options) {
610
754
  catch {
611
755
  /* ignore */
612
756
  }
757
+ if (tc.name === "send_message" && args.friendId === "self") {
758
+ sawSendMessageSelf = true;
759
+ }
760
+ /* v8 ignore next -- flag tested via truth-check integration tests @preserve */
761
+ if (tc.name === "query_session")
762
+ sawQuerySession = true;
763
+ /* v8 ignore next -- flag tested via truth-check integration tests @preserve */
764
+ if (tc.name === "bridge_manage")
765
+ sawBridgeManage = true;
613
766
  const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
614
767
  // Confirmation check for mutate tools
615
768
  if ((0, tools_1.isConfirmationRequired)(tc.name) && !options?.skipConfirmation) {
@@ -708,7 +861,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
708
861
  trace_id: traceId,
709
862
  component: "engine",
710
863
  message: "runAgent turn completed",
711
- meta: { done },
864
+ meta: { done, sawGoInward, sawQuerySession, sawBridgeManage },
712
865
  });
713
866
  return { usage: lastUsage, outcome, completion };
714
867
  }
@@ -37,12 +37,14 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.formatSurfacedValue = formatSurfacedValue;
39
39
  exports.deriveInnerDialogStatus = deriveInnerDialogStatus;
40
+ exports.deriveInnerJob = deriveInnerJob;
40
41
  exports.formatInnerDialogStatus = formatInnerDialogStatus;
41
42
  exports.extractThoughtResponseFromMessages = extractThoughtResponseFromMessages;
42
43
  exports.parseInnerDialogSession = parseInnerDialogSession;
43
44
  exports.formatThoughtTurns = formatThoughtTurns;
44
45
  exports.getInnerDialogSessionPath = getInnerDialogSessionPath;
45
46
  exports.readInnerDialogStatus = readInnerDialogStatus;
47
+ exports.readInnerDialogRawData = readInnerDialogRawData;
46
48
  exports.followThoughts = followThoughts;
47
49
  const fs = __importStar(require("fs"));
48
50
  const path = __importStar(require("path"));
@@ -149,6 +151,21 @@ function formatSurfacedValue(text, maxLength = 120) {
149
151
  return `"${firstLine}"`;
150
152
  return `"${firstLine.slice(0, maxLength - 3)}..."`;
151
153
  }
154
+ function extractEnrichedFields(pendingMessages) {
155
+ const delegated = pendingMessages.find((msg) => msg.delegatedFrom);
156
+ if (!delegated?.delegatedFrom)
157
+ return {};
158
+ const snippet = delegated.content.length > 80 ? delegated.content.slice(0, 77) + "..." : delegated.content;
159
+ return {
160
+ origin: {
161
+ friendId: delegated.delegatedFrom.friendId,
162
+ channel: delegated.delegatedFrom.channel,
163
+ key: delegated.delegatedFrom.key,
164
+ },
165
+ contentSnippet: snippet,
166
+ ...(delegated.obligationStatus === "pending" ? { obligationPending: true } : {}),
167
+ };
168
+ }
152
169
  function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
153
170
  if (runtimeState?.status === "running") {
154
171
  if (pendingMessages.length > 0) {
@@ -157,6 +174,7 @@ function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
157
174
  wake: "queued behind active turn",
158
175
  processing: "pending",
159
176
  surfaced: "nothing yet",
177
+ ...extractEnrichedFields(pendingMessages),
160
178
  };
161
179
  }
162
180
  return {
@@ -172,6 +190,7 @@ function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
172
190
  wake: "awaiting inner session",
173
191
  processing: "pending",
174
192
  surfaced: "nothing yet",
193
+ ...extractEnrichedFields(pendingMessages),
175
194
  };
176
195
  }
177
196
  const latestProcessedPendingTurn = [...turns]
@@ -192,13 +211,115 @@ function deriveInnerDialogStatus(pendingMessages, turns, runtimeState) {
192
211
  surfaced: formatSurfacedValue(latestProcessedPendingTurn.response),
193
212
  };
194
213
  }
214
+ function deriveInnerJob(pendingMessages, turns, runtimeState) {
215
+ const isRunning = runtimeState?.status === "running";
216
+ const delegated = pendingMessages.find((msg) => msg.delegatedFrom);
217
+ const enriched = extractEnrichedFields(pendingMessages);
218
+ const pendingMode = delegated && "mode" in delegated && delegated.mode ? delegated.mode : "reflect";
219
+ const origin = enriched.origin ?? null;
220
+ const content = delegated?.content ?? null;
221
+ const obligationStatus = delegated?.obligationStatus ?? null;
222
+ const queuedAt = delegated?.timestamp ?? null;
223
+ if (isRunning) {
224
+ (0, runtime_1.emitNervesEvent)({
225
+ component: "engine",
226
+ event: "engine.inner_job_derive",
227
+ message: "derived inner job state",
228
+ meta: { status: "running", mode: pendingMode, hasOrigin: origin !== null, hasObligation: obligationStatus !== null },
229
+ });
230
+ return {
231
+ status: "running",
232
+ content,
233
+ origin,
234
+ mode: pendingMode,
235
+ obligationStatus,
236
+ surfacedResult: null,
237
+ queuedAt,
238
+ startedAt: runtimeState?.startedAt ?? null,
239
+ surfacedAt: null,
240
+ };
241
+ }
242
+ if (pendingMessages.length > 0) {
243
+ (0, runtime_1.emitNervesEvent)({
244
+ component: "engine",
245
+ event: "engine.inner_job_derive",
246
+ message: "derived inner job state",
247
+ meta: { status: "queued", mode: pendingMode, hasOrigin: origin !== null, hasObligation: obligationStatus !== null },
248
+ });
249
+ return {
250
+ status: "queued",
251
+ content,
252
+ origin,
253
+ mode: pendingMode,
254
+ obligationStatus,
255
+ surfacedResult: null,
256
+ queuedAt,
257
+ startedAt: null,
258
+ surfacedAt: null,
259
+ };
260
+ }
261
+ // No pending, not running -- check for surfaced result
262
+ const latestProcessedPendingTurn = [...turns]
263
+ .reverse()
264
+ .find((turn) => extractPendingPromptMessages(turn.prompt).length > 0);
265
+ if (latestProcessedPendingTurn) {
266
+ const surfacedResult = extractThoughtResponseFromMessages([
267
+ { role: "assistant", content: latestProcessedPendingTurn.response },
268
+ ]);
269
+ (0, runtime_1.emitNervesEvent)({
270
+ component: "engine",
271
+ event: "engine.inner_job_derive",
272
+ message: "derived inner job state",
273
+ meta: { status: "surfaced", mode: "reflect", hasOrigin: false, hasObligation: false },
274
+ });
275
+ return {
276
+ status: "surfaced",
277
+ content: null,
278
+ origin: null,
279
+ mode: "reflect",
280
+ obligationStatus: null,
281
+ /* v8 ignore next -- defensive: surfacedResult fallback @preserve */
282
+ surfacedResult: surfacedResult || null,
283
+ queuedAt: null,
284
+ startedAt: null,
285
+ surfacedAt: null,
286
+ };
287
+ }
288
+ (0, runtime_1.emitNervesEvent)({
289
+ component: "engine",
290
+ event: "engine.inner_job_derive",
291
+ message: "derived inner job state",
292
+ meta: { status: "idle", mode: "reflect", hasOrigin: false, hasObligation: false },
293
+ });
294
+ return {
295
+ status: "idle",
296
+ content: null,
297
+ origin: null,
298
+ mode: "reflect",
299
+ obligationStatus: null,
300
+ surfacedResult: null,
301
+ queuedAt: null,
302
+ startedAt: null,
303
+ surfacedAt: null,
304
+ };
305
+ }
195
306
  function formatInnerDialogStatus(status) {
196
- return [
307
+ const lines = [
197
308
  `queue: ${status.queue}`,
198
309
  `wake: ${status.wake}`,
199
310
  `processing: ${status.processing}`,
200
311
  `surfaced: ${status.surfaced}`,
201
- ].join("\n");
312
+ ];
313
+ if (status.origin) {
314
+ lines.push(`origin: ${status.origin.friendId}/${status.origin.channel}/${status.origin.key}`);
315
+ }
316
+ if (status.contentSnippet) {
317
+ lines.push(`asked: ${status.contentSnippet}`);
318
+ }
319
+ if (status.obligationPending) {
320
+ lines.push("obligation: pending");
321
+ }
322
+ return lines.join("\n");
202
323
  }
203
324
  /** Extract text from a final_answer tool call's arguments. */
204
325
  function extractFinalAnswer(messages) {
@@ -347,6 +468,13 @@ function readInnerDialogStatus(sessionPath, pendingDir, runtimePath = getInnerDi
347
468
  const runtimeState = readInnerDialogRuntimeState(runtimePath);
348
469
  return deriveInnerDialogStatus(pendingMessages, turns, runtimeState);
349
470
  }
471
+ function readInnerDialogRawData(sessionPath, pendingDir) {
472
+ const runtimePath = getInnerDialogRuntimeStatePath(sessionPath);
473
+ const pendingMessages = readPendingMessagesForStatus(pendingDir);
474
+ const turns = parseInnerDialogSession(sessionPath);
475
+ const runtimeState = readInnerDialogRuntimeState(runtimePath);
476
+ return { pendingMessages, turns, runtimeState };
477
+ }
350
478
  /**
351
479
  * Watch a session file and emit new turns as they appear.
352
480
  * Returns a cleanup function that stops the watcher.
@@ -38,6 +38,7 @@ exports.getPendingDir = getPendingDir;
38
38
  exports.getDeferredReturnDir = getDeferredReturnDir;
39
39
  exports.getInnerDialogPendingDir = getInnerDialogPendingDir;
40
40
  exports.hasPendingMessages = hasPendingMessages;
41
+ exports.queuePendingMessage = queuePendingMessage;
41
42
  exports.enqueueDeferredReturn = enqueueDeferredReturn;
42
43
  exports.drainDeferredReturns = drainDeferredReturns;
43
44
  exports.drainPending = drainPending;
@@ -74,6 +75,9 @@ function writeQueueFile(queueDir, message) {
74
75
  fs.writeFileSync(filePath, JSON.stringify(message, null, 2));
75
76
  return filePath;
76
77
  }
78
+ function queuePendingMessage(pendingDir, message) {
79
+ writeQueueFile(pendingDir, message);
80
+ }
77
81
  function drainQueue(queueDir) {
78
82
  if (!fs.existsSync(queueDir))
79
83
  return { messages: [], recovered: 0 };
@@ -39,6 +39,9 @@ exports.bodyMapSection = bodyMapSection;
39
39
  exports.mcpToolsSection = mcpToolsSection;
40
40
  exports.runtimeInfoSection = runtimeInfoSection;
41
41
  exports.toolRestrictionSection = toolRestrictionSection;
42
+ exports.centerOfGravitySteeringSection = centerOfGravitySteeringSection;
43
+ exports.commitmentsSection = commitmentsSection;
44
+ exports.delegationHintSection = delegationHintSection;
42
45
  exports.contextSection = contextSection;
43
46
  exports.metacognitiveFramingSection = metacognitiveFramingSection;
44
47
  exports.loopOrientationSection = loopOrientationSection;
@@ -61,6 +64,7 @@ const first_impressions_1 = require("./first-impressions");
61
64
  const tasks_1 = require("../repertoire/tasks");
62
65
  const session_activity_1 = require("../heart/session-activity");
63
66
  const active_work_1 = require("../heart/active-work");
67
+ const commitments_1 = require("../heart/commitments");
64
68
  // Lazy-loaded psyche text cache
65
69
  let _psycheCache = null;
66
70
  let _senseStatusLinesCache = null;
@@ -444,16 +448,90 @@ function activeWorkSection(options) {
444
448
  return "";
445
449
  return (0, active_work_1.formatActiveWorkFrame)(options.activeWorkFrame);
446
450
  }
451
+ function centerOfGravitySteeringSection(channel, options) {
452
+ if (channel === "inner")
453
+ return "";
454
+ const frame = options?.activeWorkFrame;
455
+ if (!frame)
456
+ return "";
457
+ const cog = frame.centerOfGravity;
458
+ if (cog === "local-turn")
459
+ return "";
460
+ const job = frame.inner?.job;
461
+ if (cog === "inward-work") {
462
+ if (job?.status === "queued" || job?.status === "running") {
463
+ const originClause = job.origin
464
+ ? ` ${job.origin.friendName ?? job.origin.friendId} asked about something and i wanted to give it real thought before responding.`
465
+ : "";
466
+ const obligationClause = job.obligationStatus === "pending"
467
+ ? "\ni still owe them an answer."
468
+ : "";
469
+ return `## where my attention is
470
+ i'm thinking through something privately right now.${originClause}${obligationClause}
471
+
472
+ if this conversation connects to that inner work, i can weave them together.
473
+ if it's separate, i can be fully present here -- my inner work will wait.`;
474
+ }
475
+ /* v8 ignore start -- surfaced/idle/shared branches tested in prompt-steering.test.ts; CI module caching prevents attribution @preserve */
476
+ if (job?.status === "surfaced") {
477
+ const originClause = job.origin
478
+ ? ` this started when ${job.origin.friendName ?? job.origin.friendId} asked about something.`
479
+ : "";
480
+ return `## where my attention is
481
+ i've been thinking privately and reached something.${originClause}
482
+
483
+ i should bring my answer back to the conversation it came from.`;
484
+ }
485
+ return `## where my attention is
486
+ i have unfinished work that needs attention before i move on.
487
+
488
+ i can take it inward with go_inward to think privately, or address it directly here.`;
489
+ }
490
+ if (cog === "shared-work") {
491
+ /* v8 ignore stop */
492
+ return `## where my attention is
493
+ this work touches multiple conversations -- i'm holding threads across sessions.
494
+
495
+ i should keep the different sides aligned. what i learn here may matter there, and vice versa.`;
496
+ }
497
+ /* v8 ignore next -- unreachable: all center-of-gravity modes covered above @preserve */
498
+ return "";
499
+ }
500
+ function commitmentsSection(options) {
501
+ if (!options?.activeWorkFrame)
502
+ return "";
503
+ const job = options.activeWorkFrame.inner?.job;
504
+ if (!job)
505
+ return "";
506
+ const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job);
507
+ if (commitments.committedTo.length === 0)
508
+ return "";
509
+ return `## my commitments\n${commitments.committedTo.map((c) => `- ${c}`).join("\n")}`;
510
+ }
511
+ const DELEGATION_REASON_PROSE_HINT = {
512
+ explicit_reflection: "something here calls for reflection",
513
+ cross_session: "this touches other conversations i'm in",
514
+ bridge_state: "there's shared work spanning sessions",
515
+ task_state: "this relates to tasks i'm tracking",
516
+ non_fast_path_tool: "this needs more than a simple reply",
517
+ unresolved_obligation: "i have an unresolved commitment from earlier",
518
+ };
447
519
  function delegationHintSection(options) {
448
520
  if (!options?.delegationDecision)
449
521
  return "";
450
- const lines = [
451
- "## delegation hint",
452
- `target: ${options.delegationDecision.target}`,
453
- `reasons: ${options.delegationDecision.reasons.length > 0 ? options.delegationDecision.reasons.join(", ") : "none"}`,
454
- `outward closure: ${options.delegationDecision.outwardClosureRequired ? "required" : "not required"}`,
455
- ];
456
- return lines.join("\n");
522
+ if (options.delegationDecision.target === "fast-path")
523
+ return "";
524
+ const reasons = options.delegationDecision.reasons;
525
+ if (reasons.length === 0)
526
+ return "";
527
+ const reasonProse = reasons
528
+ .map((r) => DELEGATION_REASON_PROSE_HINT[r])
529
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
530
+ .join(". ");
531
+ const closureLine = options.delegationDecision.outwardClosureRequired
532
+ ? "\ni should make sure to say something outward before going inward."
533
+ : "";
534
+ return `## what i'm sensing about this conversation\n${reasonProse}.${closureLine}`;
457
535
  }
458
536
  function reasoningEffortSection(options) {
459
537
  if (!options?.providerCapabilities?.has("reasoning-effort"))
@@ -615,6 +693,8 @@ async function buildSystem(channel = "cli", options, context) {
615
693
  skillsSection(),
616
694
  taskBoardSection(),
617
695
  activeWorkSection(options),
696
+ centerOfGravitySteeringSection(channel, options),
697
+ commitmentsSection(options),
618
698
  delegationHintSection(options),
619
699
  bridgeContextSection(options),
620
700
  buildSessionSummary({
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.finalAnswerTool = exports.noResponseTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
36
+ exports.finalAnswerTool = exports.noResponseTool = exports.goInwardTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
37
+ exports.renderInnerProgressStatus = renderInnerProgressStatus;
37
38
  const fs = __importStar(require("fs"));
38
39
  const fg = __importStar(require("fast-glob"));
39
40
  const child_process_1 = require("child_process");
@@ -112,6 +113,7 @@ async function recallSessionSafely(options) {
112
113
  }
113
114
  function normalizeProgressOutcome(text) {
114
115
  const trimmed = text.trim();
116
+ /* v8 ignore next -- defensive: normalizeProgressOutcome null branch @preserve */
115
117
  if (!trimmed || trimmed === "nothing yet" || trimmed === "nothing recent") {
116
118
  return null;
117
119
  }
@@ -150,27 +152,16 @@ function renderCrossChatDeliveryStatus(target, result) {
150
152
  }
151
153
  function renderInnerProgressStatus(status) {
152
154
  if (status.processing === "pending") {
153
- return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
154
- scope: "inner-delegation",
155
- phase: "queued",
156
- objective: status.queue,
157
- outcomeText: `wake: ${status.wake}`,
158
- }));
155
+ return "i've queued this thought for private attention. it'll come up when my inner dialog is free.";
159
156
  }
160
157
  if (status.processing === "started") {
161
- return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
162
- scope: "inner-delegation",
163
- phase: "processing",
164
- outcomeText: `wake: ${status.wake}`,
165
- }));
158
+ return "i'm working through this privately right now.";
166
159
  }
167
- const completedOutcome = normalizeProgressOutcome(status.surfaced) ?? status.surfaced;
168
- return (0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
169
- scope: "inner-delegation",
170
- phase: "completed",
171
- objective: null,
172
- outcomeText: completedOutcome,
173
- }));
160
+ // processed / completed
161
+ if (status.surfaced && status.surfaced !== "nothing recent" && status.surfaced !== "no outward result") {
162
+ return `i thought about this privately and came to something: ${status.surfaced}`;
163
+ }
164
+ return "i thought about this privately. i'll bring it back when the time is right.";
174
165
  }
175
166
  exports.baseToolDefinitions = [
176
167
  {
@@ -897,10 +888,22 @@ exports.baseToolDefinitions = [
897
888
  key,
898
889
  content,
899
890
  timestamp: now,
900
- ...(delegatedFrom ? { delegatedFrom } : {}),
891
+ ...(delegatedFrom ? { delegatedFrom, obligationStatus: "pending" } : {}),
901
892
  };
902
893
  if (isSelf) {
903
894
  writePendingEnvelope(pendingDir, envelope);
895
+ if (delegatedFrom) {
896
+ (0, runtime_1.emitNervesEvent)({
897
+ event: "repertoire.obligation_created",
898
+ component: "repertoire",
899
+ message: "obligation created for inner dialog delegation",
900
+ meta: {
901
+ friendId: delegatedFrom.friendId,
902
+ channel: delegatedFrom.channel,
903
+ key: delegatedFrom.key,
904
+ },
905
+ });
906
+ }
904
907
  let wakeResponse = null;
905
908
  try {
906
909
  wakeResponse = await (0, socket_client_1.requestInnerWake)(agentName);
@@ -1075,6 +1078,32 @@ exports.baseToolDefinitions = [
1075
1078
  ...tools_1.codingToolDefinitions,
1076
1079
  ];
1077
1080
  exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
1081
+ exports.goInwardTool = {
1082
+ type: "function",
1083
+ function: {
1084
+ name: "go_inward",
1085
+ description: "i need to think about this privately. this takes the current thread inward -- i'll sit with it, work through it, or carry it to where it needs to go. must be the only tool call in the turn.",
1086
+ parameters: {
1087
+ type: "object",
1088
+ properties: {
1089
+ content: {
1090
+ type: "string",
1091
+ description: "what i need to think about -- the question, the thread, the thing that needs private attention",
1092
+ },
1093
+ answer: {
1094
+ type: "string",
1095
+ description: "if i want to say something outward before going inward -- an acknowledgment, a 'let me think about that', whatever feels right",
1096
+ },
1097
+ mode: {
1098
+ type: "string",
1099
+ enum: ["reflect", "plan", "relay"],
1100
+ description: "reflect: something to sit with. plan: something to work through. relay: something to carry across.",
1101
+ },
1102
+ },
1103
+ required: ["content"],
1104
+ },
1105
+ },
1106
+ };
1078
1107
  exports.noResponseTool = {
1079
1108
  type: "function",
1080
1109
  function: {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noResponseTool = exports.finalAnswerTool = exports.tools = void 0;
3
+ exports.goInwardTool = exports.noResponseTool = exports.finalAnswerTool = exports.tools = void 0;
4
4
  exports.getToolsForChannel = getToolsForChannel;
5
5
  exports.isConfirmationRequired = isConfirmationRequired;
6
6
  exports.execTool = execTool;
@@ -26,6 +26,7 @@ var tools_base_2 = require("./tools-base");
26
26
  Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_base_2.tools; } });
27
27
  Object.defineProperty(exports, "finalAnswerTool", { enumerable: true, get: function () { return tools_base_2.finalAnswerTool; } });
28
28
  Object.defineProperty(exports, "noResponseTool", { enumerable: true, get: function () { return tools_base_2.noResponseTool; } });
29
+ Object.defineProperty(exports, "goInwardTool", { enumerable: true, get: function () { return tools_base_2.goInwardTool; } });
29
30
  // All tool definitions in a single registry
30
31
  const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
31
32
  function baseToolsForCapabilities() {
@@ -41,6 +41,7 @@ exports.readTaskFile = readTaskFile;
41
41
  exports.buildTaskTriggeredMessage = buildTaskTriggeredMessage;
42
42
  exports.deriveResumeCheckpoint = deriveResumeCheckpoint;
43
43
  exports.innerDialogSessionPath = innerDialogSessionPath;
44
+ exports.enrichDelegatedFromWithBridge = enrichDelegatedFromWithBridge;
44
45
  exports.runInnerDialogTurn = runInnerDialogTurn;
45
46
  const fs = __importStar(require("fs"));
46
47
  const path = __importStar(require("path"));
@@ -281,12 +282,40 @@ async function tryDeliverDelegatedCompletion(target, outboundEnvelope) {
281
282
  });
282
283
  return result.delivered;
283
284
  }
285
+ function enrichDelegatedFromWithBridge(delegatedFrom) {
286
+ if (delegatedFrom.bridgeId) {
287
+ return delegatedFrom;
288
+ }
289
+ const bridgeManager = (0, manager_1.createBridgeManager)();
290
+ const originBridges = bridgeManager.findBridgesForSession({
291
+ friendId: delegatedFrom.friendId,
292
+ channel: delegatedFrom.channel,
293
+ key: delegatedFrom.key,
294
+ });
295
+ const activeBridge = originBridges.find((b) => b.lifecycle === "active");
296
+ if (activeBridge) {
297
+ return { ...delegatedFrom, bridgeId: activeBridge.id };
298
+ }
299
+ return delegatedFrom;
300
+ }
284
301
  async function routeDelegatedCompletion(agentRoot, agentName, completion, drainedPending, timestamp) {
285
302
  const delegated = (drainedPending ?? []).find((message) => message.delegatedFrom);
286
303
  if (!delegated?.delegatedFrom || !completion?.answer?.trim()) {
287
304
  return;
288
305
  }
289
- const delegatedFrom = delegated.delegatedFrom;
306
+ const delegatedFrom = enrichDelegatedFromWithBridge(delegated.delegatedFrom);
307
+ if (delegated.obligationStatus === "pending") {
308
+ (0, runtime_1.emitNervesEvent)({
309
+ event: "senses.obligation_fulfilled",
310
+ component: "senses",
311
+ message: "obligation fulfilled via delegated completion",
312
+ meta: {
313
+ friendId: delegatedFrom.friendId,
314
+ channel: delegatedFrom.channel,
315
+ key: delegatedFrom.key,
316
+ },
317
+ });
318
+ }
290
319
  const outboundEnvelope = {
291
320
  from: agentName,
292
321
  friendId: delegatedFrom.friendId,
@@ -37,20 +37,38 @@ function emptyTaskBoard() {
37
37
  };
38
38
  }
39
39
  function readInnerWorkState() {
40
+ const defaultJob = {
41
+ status: "idle",
42
+ content: null,
43
+ origin: null,
44
+ mode: "reflect",
45
+ obligationStatus: null,
46
+ surfacedResult: null,
47
+ queuedAt: null,
48
+ startedAt: null,
49
+ surfacedAt: null,
50
+ };
40
51
  try {
41
52
  const agentRoot = (0, identity_1.getAgentRoot)();
42
53
  const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
43
54
  const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
44
- const status = (0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir);
55
+ const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
56
+ const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
57
+ const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
45
58
  return {
46
- status: status.processing === "started" ? "running" : "idle",
47
- hasPending: status.queue !== "clear",
59
+ status: dialogStatus.processing === "started" ? "running" : "idle",
60
+ hasPending: dialogStatus.queue !== "clear",
61
+ origin: dialogStatus.origin,
62
+ contentSnippet: dialogStatus.contentSnippet,
63
+ obligationPending: dialogStatus.obligationPending,
64
+ job,
48
65
  };
49
66
  }
50
67
  catch {
51
68
  return {
52
69
  status: "idle",
53
70
  hasPending: false,
71
+ job: defaultJob,
54
72
  };
55
73
  }
56
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.74",
3
+ "version": "0.1.0-alpha.75",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",