@ouro.bot/cli 0.1.0-alpha.140 → 0.1.0-alpha.142
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 +19 -0
- package/dist/heart/core.js +147 -81
- package/dist/heart/daemon/daemon-cli.js +88 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/tool-loop.js +4 -2
- package/dist/mind/context.js +2 -1
- package/dist/mind/prompt.js +1 -1
- package/dist/repertoire/tools-base.js +19 -14
- package/dist/repertoire/tools.js +18 -2
- package/dist/senses/surface-tool.js +14 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
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.142",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Surface tool fulfills heart obligations on successful routing: findPendingObligationForOrigin + fulfillObligation called after inner obligation advance, wrapped in try/catch.",
|
|
8
|
+
"New fulfillHeartObligation callback on HandleSurfaceInput — origin-based lookup independent of inner obligationId.",
|
|
9
|
+
"ouro inner status command: reads runtime.json, journal dir, heartbeat cadence, attention count. Shows last turn, status, heartbeat health, journal listing, held thoughts."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.141",
|
|
14
|
+
"changes": [
|
|
15
|
+
"Ponder tool replaces descend: thought+say required from outer sessions, parameterless from inner dialog. Creates heart obligation, enqueues pending with delegatedFrom, emits say text outward.",
|
|
16
|
+
"Ponder from inner dialog enqueues synthetic pending message (no delegatedFrom) to continue thinking across turns.",
|
|
17
|
+
"Rest tool for inner dialog: parameterless, gated by attention queue. Rejected with guidance when unsurfaced thoughts remain.",
|
|
18
|
+
"Settle removed from inner dialog tool set (replaced by rest). Outer sessions still use settle.",
|
|
19
|
+
"Session migration: descend -> ponder added to migrateToolNames(). go_inward now maps to ponder.",
|
|
20
|
+
"Ponder and rest added to CIRCUIT_BREAKER_EXEMPT and SOLE_CALL_REJECTION."
|
|
21
|
+
]
|
|
22
|
+
},
|
|
4
23
|
{
|
|
5
24
|
"version": "0.1.0-alpha.140",
|
|
6
25
|
"changes": [
|
package/dist/heart/core.js
CHANGED
|
@@ -176,7 +176,8 @@ Object.defineProperty(exports, "buildSystem", { enumerable: true, get: function
|
|
|
176
176
|
const SOLE_CALL_REJECTION = {
|
|
177
177
|
settle: "rejected: settle must be the only tool call. finish your work first, then call settle alone.",
|
|
178
178
|
observe: "rejected: observe must be the only tool call. call observe alone when you want to stay silent.",
|
|
179
|
-
|
|
179
|
+
ponder: "rejected: ponder must be the only tool call. finish your other work first, then call ponder alone.",
|
|
180
|
+
rest: "rejected: rest must be the only tool call. finish your work first, then call rest alone.",
|
|
180
181
|
};
|
|
181
182
|
const DELEGATION_REASON_PROSE_HANDOFF = {
|
|
182
183
|
explicit_reflection: "something in the conversation called for reflection",
|
|
@@ -186,7 +187,7 @@ const DELEGATION_REASON_PROSE_HANDOFF = {
|
|
|
186
187
|
non_fast_path_tool: "this needs tools beyond a simple reply",
|
|
187
188
|
unresolved_obligation: "there's an unresolved commitment from an earlier conversation",
|
|
188
189
|
};
|
|
189
|
-
function
|
|
190
|
+
function buildPonderHandoffPacket(params) {
|
|
190
191
|
const reasons = params.delegationDecision?.reasons ?? [];
|
|
191
192
|
const reasonProse = reasons.length > 0
|
|
192
193
|
? reasons.map((r) => DELEGATION_REASON_PROSE_HANDOFF[r]).join("; ")
|
|
@@ -245,14 +246,14 @@ function isExternalStateQuery(toolName, args) {
|
|
|
245
246
|
const cmd = String(args.command ?? "");
|
|
246
247
|
return /\bgh\s+(pr|run|api|issue)\b/.test(cmd) || /\bnpm\s+(view|info|show)\b/.test(cmd);
|
|
247
248
|
}
|
|
248
|
-
function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf,
|
|
249
|
+
function getSettleRetryError(mustResolveBeforeHandoff, intent, sawSteeringFollowUp, _delegationDecision, sawSendMessageSelf, sawPonder, _sawQuerySession, currentObligation, innerJob, sawExternalStateQuery) {
|
|
249
250
|
// Delegation adherence removed: the delegation decision is surfaced in the
|
|
250
251
|
// system prompt as a suggestion. Hard-gating settle caused infinite
|
|
251
252
|
// rejection loops where the agent couldn't respond to the user at all.
|
|
252
253
|
// The agent is free to follow or ignore the delegation hint.
|
|
253
254
|
// 2. Pending obligation not addressed
|
|
254
|
-
if (innerJob?.obligationStatus === "pending" && !sawSendMessageSelf && !
|
|
255
|
-
return "you're still holding something from an earlier conversation -- someone is waiting for your answer. finish the thought first, or
|
|
255
|
+
if (innerJob?.obligationStatus === "pending" && !sawSendMessageSelf && !sawPonder) {
|
|
256
|
+
return "you're still holding something from an earlier conversation -- someone is waiting for your answer. finish the thought first, or ponder to keep working on it privately.";
|
|
256
257
|
}
|
|
257
258
|
// 3. mustResolveBeforeHandoff + missing intent
|
|
258
259
|
if (mustResolveBeforeHandoff && !intent) {
|
|
@@ -496,7 +497,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
496
497
|
let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
|
|
497
498
|
let currentReasoningEffort = "medium";
|
|
498
499
|
let sawSendMessageSelf = false;
|
|
499
|
-
let
|
|
500
|
+
let sawPonder = false;
|
|
500
501
|
let sawQuerySession = false;
|
|
501
502
|
let sawBridgeManage = false;
|
|
502
503
|
let sawExternalStateQuery = false;
|
|
@@ -522,11 +523,13 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
522
523
|
providerRuntime.resetTurnState(messages);
|
|
523
524
|
while (!done) {
|
|
524
525
|
// Channel-based tool filtering:
|
|
525
|
-
// - Inner dialog: exclude
|
|
526
|
+
// - Inner dialog: exclude send_message (delivery via surface), observe (no one to observe)
|
|
526
527
|
// - 1:1 sessions: exclude observe (can't ignore someone talking directly to you)
|
|
527
528
|
// - Group chats: observe available
|
|
528
529
|
//
|
|
529
|
-
//
|
|
530
|
+
// ponder, settle/rest, surface, and observe are always assembled based on channel context.
|
|
531
|
+
// ponder is available in ALL channels (outer: think privately, inner: keep turning).
|
|
532
|
+
// Inner dialog gets restTool instead of settleTool (rest = end turn, gated by attention queue).
|
|
530
533
|
// toolChoiceRequired only controls whether tool_choice: "required" is set in the API call.
|
|
531
534
|
const isInnerDialog = channel === "inner";
|
|
532
535
|
const filteredBaseTools = isInnerDialog
|
|
@@ -534,10 +537,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
534
537
|
: baseTools;
|
|
535
538
|
const activeTools = [
|
|
536
539
|
...filteredBaseTools,
|
|
537
|
-
|
|
538
|
-
...(isInnerDialog ? [surface_tool_1.surfaceToolDef] : []),
|
|
540
|
+
tools_1.ponderTool,
|
|
541
|
+
...(isInnerDialog ? [surface_tool_1.surfaceToolDef, tools_1.restTool] : []),
|
|
539
542
|
...(currentContext?.isGroupChat && !isInnerDialog ? [tools_1.observeTool] : []),
|
|
540
|
-
tools_1.settleTool,
|
|
543
|
+
...(!isInnerDialog ? [tools_1.settleTool] : []),
|
|
541
544
|
];
|
|
542
545
|
const steeringFollowUps = options?.drainSteeringFollowUps?.() ?? [];
|
|
543
546
|
if (steeringFollowUps.length > 0) {
|
|
@@ -652,7 +655,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
652
655
|
done = true;
|
|
653
656
|
continue;
|
|
654
657
|
}
|
|
655
|
-
const retryError = getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf,
|
|
658
|
+
const retryError = getSettleRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawPonder, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
|
|
656
659
|
const deliveredAnswer = answer;
|
|
657
660
|
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
658
661
|
const validTerminalIntent = intent === "complete" || intent === "blocked";
|
|
@@ -734,94 +737,157 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
734
737
|
done = true;
|
|
735
738
|
continue;
|
|
736
739
|
}
|
|
737
|
-
// Check for
|
|
738
|
-
const
|
|
739
|
-
if (
|
|
740
|
+
// Check for ponder sole call: intercept before tool execution
|
|
741
|
+
const isSolePonder = result.toolCalls.length === 1 && result.toolCalls[0].name === "ponder";
|
|
742
|
+
if (isSolePonder) {
|
|
740
743
|
let parsedArgs = {};
|
|
741
744
|
try {
|
|
742
745
|
parsedArgs = JSON.parse(result.toolCalls[0].arguments);
|
|
743
746
|
}
|
|
744
747
|
catch { /* ignore */ }
|
|
745
|
-
callbacks.onToolStart("
|
|
746
|
-
/* v8 ignore next -- defensive: topic always string from model @preserve */
|
|
747
|
-
const topic = typeof parsedArgs.topic === "string" ? parsedArgs.topic : "";
|
|
748
|
-
const answer = typeof parsedArgs.answer === "string" ? parsedArgs.answer : undefined;
|
|
749
|
-
const parsedMode = parsedArgs.mode === "reflect" || parsedArgs.mode === "plan" || parsedArgs.mode === "relay"
|
|
750
|
-
? parsedArgs.mode
|
|
751
|
-
: undefined;
|
|
752
|
-
const mode = parsedMode || "reflect";
|
|
753
|
-
// Emit outward answer if provided
|
|
754
|
-
if (answer) {
|
|
755
|
-
callbacks.onClearText?.();
|
|
756
|
-
callbacks.onTextChunk(answer);
|
|
757
|
-
}
|
|
758
|
-
// Build handoff packet and enqueue
|
|
759
|
-
const handoffContent = buildDescendHandoffPacket({
|
|
760
|
-
topic,
|
|
761
|
-
mode,
|
|
762
|
-
delegationDecision: options?.delegationDecision,
|
|
763
|
-
currentSession: options?.toolContext?.currentSession ?? null,
|
|
764
|
-
currentObligation: options?.currentObligation ?? null,
|
|
765
|
-
outwardClosureRequired: options?.delegationDecision?.outwardClosureRequired ?? false,
|
|
766
|
-
});
|
|
767
|
-
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
|
|
748
|
+
callbacks.onToolStart("ponder", parsedArgs);
|
|
768
749
|
const currentSession = options?.toolContext?.currentSession;
|
|
769
750
|
const isInnerChannel = currentSession?.friendId === "self" && currentSession?.channel === "inner";
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
751
|
+
if (!isInnerChannel) {
|
|
752
|
+
// Outer session: thought and say are required
|
|
753
|
+
const thought = typeof parsedArgs.thought === "string" ? parsedArgs.thought : undefined;
|
|
754
|
+
const say = typeof parsedArgs.say === "string" ? parsedArgs.say : undefined;
|
|
755
|
+
if (!thought || !say) {
|
|
756
|
+
callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), false);
|
|
757
|
+
messages.push(msg);
|
|
758
|
+
const errorMsg = "ponder requires both thought and say when called from a conversation. thought is what needs more thinking; say is what you tell them before going quiet.";
|
|
759
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: errorMsg });
|
|
760
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, errorMsg);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
// Emit outward say text
|
|
764
|
+
callbacks.onClearText?.();
|
|
765
|
+
callbacks.onTextChunk(say);
|
|
766
|
+
// Build handoff packet and enqueue
|
|
767
|
+
const handoffContent = buildPonderHandoffPacket({
|
|
768
|
+
topic: thought,
|
|
769
|
+
mode: "reflect",
|
|
770
|
+
delegationDecision: options?.delegationDecision,
|
|
771
|
+
currentSession: currentSession ?? null,
|
|
772
|
+
currentObligation: options?.currentObligation ?? null,
|
|
773
|
+
outwardClosureRequired: options?.delegationDecision?.outwardClosureRequired ?? false,
|
|
774
|
+
});
|
|
775
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
|
|
776
|
+
// Create obligation FIRST so we can attach its ID to the pending message
|
|
777
|
+
let createdObligationId;
|
|
778
|
+
if (currentSession) {
|
|
779
|
+
try {
|
|
780
|
+
const obligation = (0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
|
|
781
|
+
origin: {
|
|
782
|
+
friendId: currentSession.friendId,
|
|
783
|
+
channel: currentSession.channel,
|
|
784
|
+
key: currentSession.key,
|
|
785
|
+
},
|
|
786
|
+
content: thought,
|
|
787
|
+
});
|
|
788
|
+
createdObligationId = obligation.id;
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
/* v8 ignore next -- defensive: obligation store write failure should not break ponder @preserve */
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const envelope = {
|
|
795
|
+
from: (0, identity_2.getAgentName)(),
|
|
796
|
+
friendId: "self",
|
|
797
|
+
channel: "inner",
|
|
798
|
+
key: "dialog",
|
|
799
|
+
content: handoffContent,
|
|
800
|
+
timestamp: Date.now(),
|
|
801
|
+
...(currentSession ? {
|
|
802
|
+
delegatedFrom: {
|
|
776
803
|
friendId: currentSession.friendId,
|
|
777
804
|
channel: currentSession.channel,
|
|
778
805
|
key: currentSession.key,
|
|
779
806
|
},
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
807
|
+
obligationStatus: "pending",
|
|
808
|
+
/* v8 ignore next -- defensive: createdObligationId is undefined only when obligation store write fails @preserve */
|
|
809
|
+
...(createdObligationId ? { obligationId: createdObligationId } : {}),
|
|
810
|
+
} : {}),
|
|
811
|
+
};
|
|
812
|
+
(0, pending_1.queuePendingMessage)(pendingDir, envelope);
|
|
813
|
+
try {
|
|
814
|
+
await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
|
|
786
815
|
}
|
|
816
|
+
catch { /* daemon may not be running */ }
|
|
817
|
+
callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), true);
|
|
818
|
+
sawPonder = true;
|
|
819
|
+
messages.push(msg);
|
|
820
|
+
const ack = "(pondering)";
|
|
821
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
|
|
822
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
|
|
823
|
+
(0, runtime_1.emitNervesEvent)({
|
|
824
|
+
component: "engine",
|
|
825
|
+
event: "engine.pondered",
|
|
826
|
+
message: "pondering",
|
|
827
|
+
meta: { hasThought: true, hasSay: true, contentSnippet: thought.slice(0, 80) },
|
|
828
|
+
});
|
|
787
829
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
830
|
+
else {
|
|
831
|
+
// Inner dialog: parameterless, enqueue synthetic pending WITHOUT delegatedFrom
|
|
832
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_2.getAgentName)());
|
|
833
|
+
const envelope = {
|
|
834
|
+
from: (0, identity_2.getAgentName)(),
|
|
835
|
+
friendId: "self",
|
|
836
|
+
channel: "inner",
|
|
837
|
+
key: "dialog",
|
|
838
|
+
content: "continuing to think...",
|
|
839
|
+
timestamp: Date.now(),
|
|
840
|
+
};
|
|
841
|
+
(0, pending_1.queuePendingMessage)(pendingDir, envelope);
|
|
842
|
+
callbacks.onToolEnd("ponder", (0, tools_1.summarizeArgs)("ponder", parsedArgs), true);
|
|
843
|
+
sawPonder = true;
|
|
844
|
+
messages.push(msg);
|
|
845
|
+
const ack = "(pondering)";
|
|
846
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
|
|
847
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
|
|
848
|
+
(0, runtime_1.emitNervesEvent)({
|
|
849
|
+
component: "engine",
|
|
850
|
+
event: "engine.pondered",
|
|
851
|
+
message: "pondering from inner dialog",
|
|
852
|
+
meta: { inner: true },
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
outcome = "pondered";
|
|
856
|
+
done = true;
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
// Check for rest sole call: intercept before tool execution
|
|
860
|
+
const isSoleRest = result.toolCalls.length === 1 && result.toolCalls[0].name === "rest";
|
|
861
|
+
if (isSoleRest) {
|
|
862
|
+
const restArgs = (() => { try {
|
|
863
|
+
return JSON.parse(result.toolCalls[0].arguments);
|
|
864
|
+
}
|
|
865
|
+
catch {
|
|
866
|
+
return {};
|
|
867
|
+
} })();
|
|
868
|
+
callbacks.onToolStart("rest", restArgs);
|
|
869
|
+
// Attention queue gate: reject rest if items remain
|
|
870
|
+
const attentionQueue = (augmentedToolContext ?? options?.toolContext)?.delegatedOrigins;
|
|
871
|
+
if (attentionQueue && attentionQueue.length > 0) {
|
|
872
|
+
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), false);
|
|
873
|
+
messages.push(msg);
|
|
874
|
+
const gateMessage = "you're holding thoughts someone is waiting for — surface them before you rest.";
|
|
875
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: gateMessage });
|
|
876
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, gateMessage);
|
|
877
|
+
continue;
|
|
810
878
|
}
|
|
811
|
-
|
|
812
|
-
callbacks.onToolEnd("descend", (0, tools_1.summarizeArgs)("descend", parsedArgs), true);
|
|
813
|
-
sawDescend = true;
|
|
879
|
+
callbacks.onToolEnd("rest", (0, tools_1.summarizeArgs)("rest", restArgs), true);
|
|
814
880
|
messages.push(msg);
|
|
815
|
-
const ack = "(
|
|
881
|
+
const ack = "(resting)";
|
|
816
882
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: ack });
|
|
817
883
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, ack);
|
|
818
884
|
(0, runtime_1.emitNervesEvent)({
|
|
819
885
|
component: "engine",
|
|
820
|
-
event: "engine.
|
|
821
|
-
message: "
|
|
822
|
-
meta: {
|
|
886
|
+
event: "engine.rested",
|
|
887
|
+
message: "resting until next heartbeat",
|
|
888
|
+
meta: {},
|
|
823
889
|
});
|
|
824
|
-
outcome = "
|
|
890
|
+
outcome = "rested";
|
|
825
891
|
done = true;
|
|
826
892
|
continue;
|
|
827
893
|
}
|
|
@@ -981,7 +1047,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
981
1047
|
trace_id: traceId,
|
|
982
1048
|
component: "engine",
|
|
983
1049
|
message: "runAgent turn completed",
|
|
984
|
-
meta: { done,
|
|
1050
|
+
meta: { done, sawPonder, sawQuerySession, sawBridgeManage },
|
|
985
1051
|
});
|
|
986
1052
|
return {
|
|
987
1053
|
usage: lastUsage,
|
|
@@ -295,6 +295,7 @@ function usage() {
|
|
|
295
295
|
" ouro friend create --name <name> [--trust <level>] [--agent <name>]",
|
|
296
296
|
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
297
297
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
298
|
+
" ouro inner [--agent <name>]",
|
|
298
299
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
299
300
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
300
301
|
" ouro whoami [--agent <name>]",
|
|
@@ -987,6 +988,10 @@ function parseOuroCommand(args) {
|
|
|
987
988
|
return parseThoughtsCommand(args.slice(1));
|
|
988
989
|
if (head === "attention")
|
|
989
990
|
return parseAttentionCommand(args.slice(1));
|
|
991
|
+
if (head === "inner") {
|
|
992
|
+
const { agent } = extractAgentFlag(args.slice(1));
|
|
993
|
+
return { kind: "inner.status", ...(agent ? { agent } : {}) };
|
|
994
|
+
}
|
|
990
995
|
if (head === "chat") {
|
|
991
996
|
if (!second)
|
|
992
997
|
throw new Error(`Usage\n${usage()}`);
|
|
@@ -2607,6 +2612,89 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
2607
2612
|
}
|
|
2608
2613
|
}
|
|
2609
2614
|
/* v8 ignore stop */
|
|
2615
|
+
// ── inner dialog status (local, no daemon socket needed) ──
|
|
2616
|
+
/* v8 ignore start -- inner status handler: requires real agent state on disk @preserve */
|
|
2617
|
+
if (command.kind === "inner.status") {
|
|
2618
|
+
try {
|
|
2619
|
+
const agentName = command.agent ?? (0, identity_1.getAgentName)();
|
|
2620
|
+
const agentRoot = (0, identity_1.getAgentRoot)(agentName);
|
|
2621
|
+
const { buildInnerStatusOutput } = await Promise.resolve().then(() => __importStar(require("./inner-status")));
|
|
2622
|
+
const { sessionPath: getSessionPath } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
2623
|
+
const { parseCadenceMs, DEFAULT_CADENCE_MS } = await Promise.resolve().then(() => __importStar(require("./heartbeat-timer")));
|
|
2624
|
+
const { parseFrontmatter } = await Promise.resolve().then(() => __importStar(require("../../repertoire/tasks/parser")));
|
|
2625
|
+
const { listActiveObligations } = await Promise.resolve().then(() => __importStar(require("../../mind/obligations")));
|
|
2626
|
+
// Read runtime state
|
|
2627
|
+
const innerSessionPath = getSessionPath("inner-dialog", "inner", "session");
|
|
2628
|
+
const runtimeJsonPath = path.join(path.dirname(innerSessionPath), "runtime.json");
|
|
2629
|
+
let runtimeState = null;
|
|
2630
|
+
try {
|
|
2631
|
+
const raw = fs.readFileSync(runtimeJsonPath, "utf-8");
|
|
2632
|
+
runtimeState = JSON.parse(raw);
|
|
2633
|
+
}
|
|
2634
|
+
catch { /* missing or corrupt — will show "unknown" */ }
|
|
2635
|
+
// Read journal files
|
|
2636
|
+
const journalDir = path.join(agentRoot, "journal");
|
|
2637
|
+
let journalFiles = [];
|
|
2638
|
+
try {
|
|
2639
|
+
const entries = fs.readdirSync(journalDir, { withFileTypes: true });
|
|
2640
|
+
journalFiles = entries
|
|
2641
|
+
.filter((e) => e.isFile() && !e.name.startsWith("."))
|
|
2642
|
+
.map((e) => {
|
|
2643
|
+
const stat = fs.statSync(path.join(journalDir, e.name));
|
|
2644
|
+
return { name: e.name, mtimeMs: stat.mtimeMs };
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
catch { /* missing dir — will show (empty) */ }
|
|
2648
|
+
// Read heartbeat cadence
|
|
2649
|
+
let heartbeat = null;
|
|
2650
|
+
try {
|
|
2651
|
+
const habitsDir = path.join(agentRoot, "habits");
|
|
2652
|
+
const habitFiles = fs.readdirSync(habitsDir);
|
|
2653
|
+
const heartbeatFile = habitFiles.find((f) => f.endsWith("-heartbeat.md"));
|
|
2654
|
+
let cadenceMs = DEFAULT_CADENCE_MS;
|
|
2655
|
+
if (heartbeatFile) {
|
|
2656
|
+
const content = fs.readFileSync(path.join(habitsDir, heartbeatFile), "utf-8");
|
|
2657
|
+
const lines = content.split(/\r?\n/);
|
|
2658
|
+
if (lines[0]?.trim() === "---") {
|
|
2659
|
+
const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
|
2660
|
+
if (closing !== -1) {
|
|
2661
|
+
const rawFrontmatter = lines.slice(1, closing).join("\n");
|
|
2662
|
+
const frontmatter = parseFrontmatter(rawFrontmatter);
|
|
2663
|
+
const parsed = parseCadenceMs(frontmatter.cadence);
|
|
2664
|
+
if (parsed !== null)
|
|
2665
|
+
cadenceMs = parsed;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
let lastCompletedAt = null;
|
|
2670
|
+
if (runtimeState?.lastCompletedAt) {
|
|
2671
|
+
const ms = new Date(runtimeState.lastCompletedAt).getTime();
|
|
2672
|
+
if (!Number.isNaN(ms))
|
|
2673
|
+
lastCompletedAt = ms;
|
|
2674
|
+
}
|
|
2675
|
+
heartbeat = { cadenceMs, lastCompletedAt };
|
|
2676
|
+
}
|
|
2677
|
+
catch { /* no habits — heartbeat unknown */ }
|
|
2678
|
+
// Attention count
|
|
2679
|
+
const activeObligations = listActiveObligations(agentName);
|
|
2680
|
+
const message = buildInnerStatusOutput({
|
|
2681
|
+
agentName,
|
|
2682
|
+
runtimeState,
|
|
2683
|
+
journalFiles,
|
|
2684
|
+
heartbeat,
|
|
2685
|
+
attentionCount: activeObligations.length,
|
|
2686
|
+
now: Date.now(),
|
|
2687
|
+
});
|
|
2688
|
+
deps.writeStdout(message);
|
|
2689
|
+
return message;
|
|
2690
|
+
}
|
|
2691
|
+
catch {
|
|
2692
|
+
const message = "error: no agent context — use --agent <name> to specify";
|
|
2693
|
+
deps.writeStdout(message);
|
|
2694
|
+
return message;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
/* v8 ignore stop */
|
|
2610
2698
|
// ── session list (local, no daemon socket needed) ──
|
|
2611
2699
|
if (command.kind === "session.list") {
|
|
2612
2700
|
/* v8 ignore start -- production default: requires full identity setup @preserve */
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildInnerStatusOutput = buildInnerStatusOutput;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
function formatRelativeTime(elapsedMs) {
|
|
6
|
+
const minutes = Math.floor(elapsedMs / (60 * 1000));
|
|
7
|
+
if (minutes < 1)
|
|
8
|
+
return "just now";
|
|
9
|
+
if (minutes === 1)
|
|
10
|
+
return "1 minute ago";
|
|
11
|
+
if (minutes < 60)
|
|
12
|
+
return `${minutes} minutes ago`;
|
|
13
|
+
const hours = Math.floor(minutes / 60);
|
|
14
|
+
if (hours === 1)
|
|
15
|
+
return "1 hour ago";
|
|
16
|
+
return `${hours} hours ago`;
|
|
17
|
+
}
|
|
18
|
+
function formatCadence(cadenceMs) {
|
|
19
|
+
const minutes = Math.round(cadenceMs / (60 * 1000));
|
|
20
|
+
if (minutes >= 60) {
|
|
21
|
+
const hours = Math.round(minutes / 60);
|
|
22
|
+
return `${hours}h`;
|
|
23
|
+
}
|
|
24
|
+
return `${minutes}m`;
|
|
25
|
+
}
|
|
26
|
+
function buildInnerStatusOutput(input) {
|
|
27
|
+
const { agentName, runtimeState, journalFiles, heartbeat, attentionCount, now } = input;
|
|
28
|
+
const lines = [];
|
|
29
|
+
lines.push(`inner dialog status: ${agentName}`);
|
|
30
|
+
// Last turn
|
|
31
|
+
if (runtimeState?.lastCompletedAt) {
|
|
32
|
+
const lastMs = new Date(runtimeState.lastCompletedAt).getTime();
|
|
33
|
+
const elapsed = now - lastMs;
|
|
34
|
+
const relativeTime = formatRelativeTime(elapsed);
|
|
35
|
+
const reasonSuffix = runtimeState.reason ? ` (${runtimeState.reason})` : "";
|
|
36
|
+
lines.push(` last turn: ${relativeTime}${reasonSuffix}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
lines.push(" last turn: unknown");
|
|
40
|
+
}
|
|
41
|
+
// Status
|
|
42
|
+
if (runtimeState) {
|
|
43
|
+
const reasonSuffix = runtimeState.status === "running" && runtimeState.reason ? ` (${runtimeState.reason})` : "";
|
|
44
|
+
lines.push(` status: ${runtimeState.status}${reasonSuffix}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
lines.push(" status: unknown");
|
|
48
|
+
}
|
|
49
|
+
// Heartbeat health
|
|
50
|
+
if (heartbeat && heartbeat.lastCompletedAt !== null) {
|
|
51
|
+
const elapsed = now - heartbeat.lastCompletedAt;
|
|
52
|
+
const threshold = heartbeat.cadenceMs * 1.5;
|
|
53
|
+
const health = elapsed < threshold ? "healthy" : "overdue";
|
|
54
|
+
const cadenceStr = formatCadence(heartbeat.cadenceMs);
|
|
55
|
+
const sinceStr = formatRelativeTime(elapsed);
|
|
56
|
+
lines.push(` heartbeat: ${health} (cadence ${cadenceStr}, ${sinceStr})`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.push(" heartbeat: unknown");
|
|
60
|
+
}
|
|
61
|
+
// Journal
|
|
62
|
+
if (journalFiles.length === 0) {
|
|
63
|
+
lines.push(" journal: (empty)");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
lines.push(" journal:");
|
|
67
|
+
const sorted = [...journalFiles].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
68
|
+
for (const file of sorted) {
|
|
69
|
+
const elapsed = now - file.mtimeMs;
|
|
70
|
+
const relativeTime = formatRelativeTime(elapsed);
|
|
71
|
+
lines.push(` - ${file.name} (${relativeTime})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Attention
|
|
75
|
+
const thoughtWord = attentionCount === 1 ? "thought" : "thoughts";
|
|
76
|
+
lines.push(` attention: ${attentionCount} held ${thoughtWord}`);
|
|
77
|
+
(0, runtime_1.emitNervesEvent)({
|
|
78
|
+
component: "daemon",
|
|
79
|
+
event: "daemon.inner_status_read",
|
|
80
|
+
message: "inner dialog status read",
|
|
81
|
+
meta: {
|
|
82
|
+
agentName,
|
|
83
|
+
status: runtimeState?.status ?? "unknown",
|
|
84
|
+
journalCount: journalFiles.length,
|
|
85
|
+
attentionCount,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
package/dist/heart/tool-loop.js
CHANGED
|
@@ -180,8 +180,10 @@ function recordToolOutcome(state, toolName, args, result, success) {
|
|
|
180
180
|
}
|
|
181
181
|
// Tools that must never be blocked by the circuit breaker.
|
|
182
182
|
// settle = end the turn, surface = deliver results outward.
|
|
183
|
-
//
|
|
184
|
-
|
|
183
|
+
// ponder = continue thinking (inner dialog) or hand off to inner dialog (outer).
|
|
184
|
+
// rest = end inner dialog turn (added in Unit 8b).
|
|
185
|
+
// Blocking these traps the agent: it can think all it wants but can never speak or stop.
|
|
186
|
+
const CIRCUIT_BREAKER_EXEMPT = new Set(["settle", "surface", "ponder", "rest"]);
|
|
185
187
|
function detectToolLoop(state, toolName, args) {
|
|
186
188
|
if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT && !CIRCUIT_BREAKER_EXEMPT.has(toolName)) {
|
|
187
189
|
return emitDetection("global_circuit_breaker", toolName, state.history.length, `this turn has already made ${state.history.length} tool calls. stop thrashing, use the current evidence, and either change approach or answer truthfully with the best grounded status.`);
|
package/dist/mind/context.js
CHANGED
|
@@ -263,7 +263,8 @@ function stripOrphanedToolResults(messages) {
|
|
|
263
263
|
const TOOL_NAME_MIGRATIONS = {
|
|
264
264
|
final_answer: "settle",
|
|
265
265
|
no_response: "observe",
|
|
266
|
-
go_inward: "
|
|
266
|
+
go_inward: "ponder",
|
|
267
|
+
descend: "ponder",
|
|
267
268
|
memory_save: "diary_write",
|
|
268
269
|
memory_search: "recall",
|
|
269
270
|
};
|
package/dist/mind/prompt.js
CHANGED
|
@@ -566,7 +566,7 @@ i should orient around that live lane first, then decide what still needs to com
|
|
|
566
566
|
return `## where my attention is
|
|
567
567
|
i have unfinished work that needs attention before i move on.
|
|
568
568
|
|
|
569
|
-
i can take it inward with
|
|
569
|
+
i can take it inward with ponder to think privately, or address it directly here.`;
|
|
570
570
|
}
|
|
571
571
|
if (cog === "shared-work") {
|
|
572
572
|
/* v8 ignore stop */
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.settleTool = exports.observeTool = exports.
|
|
36
|
+
exports.restTool = exports.settleTool = exports.observeTool = exports.ponderTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
|
|
37
37
|
exports.renderInnerProgressStatus = renderInnerProgressStatus;
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const fg = __importStar(require("fast-glob"));
|
|
@@ -1362,29 +1362,23 @@ exports.baseToolDefinitions = [
|
|
|
1362
1362
|
...tools_1.codingToolDefinitions,
|
|
1363
1363
|
];
|
|
1364
1364
|
exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
|
|
1365
|
-
exports.
|
|
1365
|
+
exports.ponderTool = {
|
|
1366
1366
|
type: "function",
|
|
1367
1367
|
function: {
|
|
1368
|
-
name: "
|
|
1369
|
-
description: "i need to
|
|
1368
|
+
name: "ponder",
|
|
1369
|
+
description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn.",
|
|
1370
1370
|
parameters: {
|
|
1371
1371
|
type: "object",
|
|
1372
1372
|
properties: {
|
|
1373
|
-
|
|
1373
|
+
thought: {
|
|
1374
1374
|
type: "string",
|
|
1375
|
-
description: "the question or
|
|
1375
|
+
description: "the question or thread that needs more thought — brief framing, not analysis. required from a conversation, ignored from inner dialog.",
|
|
1376
1376
|
},
|
|
1377
|
-
|
|
1377
|
+
say: {
|
|
1378
1378
|
type: "string",
|
|
1379
|
-
description: "
|
|
1380
|
-
},
|
|
1381
|
-
mode: {
|
|
1382
|
-
type: "string",
|
|
1383
|
-
enum: ["reflect", "plan", "relay"],
|
|
1384
|
-
description: "reflect: something to sit with. plan: something to work through. relay: something to carry across.",
|
|
1379
|
+
description: "what you say before going quiet — speak to what caught your attention, not just that something did. required from a conversation, ignored from inner dialog.",
|
|
1385
1380
|
},
|
|
1386
1381
|
},
|
|
1387
|
-
required: ["topic"],
|
|
1388
1382
|
},
|
|
1389
1383
|
},
|
|
1390
1384
|
};
|
|
@@ -1416,3 +1410,14 @@ exports.settleTool = {
|
|
|
1416
1410
|
},
|
|
1417
1411
|
},
|
|
1418
1412
|
};
|
|
1413
|
+
exports.restTool = {
|
|
1414
|
+
type: "function",
|
|
1415
|
+
function: {
|
|
1416
|
+
name: "rest",
|
|
1417
|
+
description: "put this down for now — the wheel stops until the next heartbeat. must be the only tool call in the turn.",
|
|
1418
|
+
parameters: {
|
|
1419
|
+
type: "object",
|
|
1420
|
+
properties: {},
|
|
1421
|
+
},
|
|
1422
|
+
},
|
|
1423
|
+
};
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
37
37
|
exports.getToolsForChannel = getToolsForChannel;
|
|
38
38
|
exports.isConfirmationRequired = isConfirmationRequired;
|
|
39
39
|
exports.execTool = execTool;
|
|
@@ -48,6 +48,7 @@ const guardrails_1 = require("./guardrails");
|
|
|
48
48
|
const identity_1 = require("../heart/identity");
|
|
49
49
|
const surface_tool_1 = require("../senses/surface-tool");
|
|
50
50
|
const obligations_1 = require("../mind/obligations");
|
|
51
|
+
const obligations_2 = require("../heart/obligations");
|
|
51
52
|
const session_activity_1 = require("../heart/session-activity");
|
|
52
53
|
const path = __importStar(require("path"));
|
|
53
54
|
function safeGetAgentRoot() {
|
|
@@ -63,7 +64,8 @@ var tools_base_2 = require("./tools-base");
|
|
|
63
64
|
Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_base_2.tools; } });
|
|
64
65
|
Object.defineProperty(exports, "settleTool", { enumerable: true, get: function () { return tools_base_2.settleTool; } });
|
|
65
66
|
Object.defineProperty(exports, "observeTool", { enumerable: true, get: function () { return tools_base_2.observeTool; } });
|
|
66
|
-
Object.defineProperty(exports, "
|
|
67
|
+
Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_base_2.ponderTool; } });
|
|
68
|
+
Object.defineProperty(exports, "restTool", { enumerable: true, get: function () { return tools_base_2.restTool; } });
|
|
67
69
|
// Surface tool handler: routes content to friend's freshest session
|
|
68
70
|
/* v8 ignore start -- surface handler wiring: core logic tested via surface-tool.test.ts; this wires identity/routing deps @preserve */
|
|
69
71
|
const surfaceToolDefinition = {
|
|
@@ -206,6 +208,20 @@ const surfaceToolDefinition = {
|
|
|
206
208
|
}
|
|
207
209
|
/* v8 ignore stop */
|
|
208
210
|
},
|
|
211
|
+
fulfillHeartObligation: (origin) => {
|
|
212
|
+
/* v8 ignore start -- heart obligation fulfillment: tested via surface-tool.test.ts @preserve */
|
|
213
|
+
try {
|
|
214
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
215
|
+
const heartObligation = (0, obligations_2.findPendingObligationForOrigin)(agentRoot, origin);
|
|
216
|
+
if (heartObligation) {
|
|
217
|
+
(0, obligations_2.fulfillObligation)(agentRoot, heartObligation.id);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// swallowed — heart obligation fulfillment must never break surface delivery
|
|
222
|
+
}
|
|
223
|
+
/* v8 ignore stop */
|
|
224
|
+
},
|
|
209
225
|
});
|
|
210
226
|
},
|
|
211
227
|
summaryKeys: ["content", "delegationId"],
|
|
@@ -31,7 +31,7 @@ exports.surfaceToolDef = {
|
|
|
31
31
|
},
|
|
32
32
|
};
|
|
33
33
|
async function handleSurface(input) {
|
|
34
|
-
const { content, delegationId, friendId, queue, routeToFriend, advanceObligation } = input;
|
|
34
|
+
const { content, delegationId, friendId, queue, routeToFriend, advanceObligation, fulfillHeartObligation } = input;
|
|
35
35
|
// Resolve target friend
|
|
36
36
|
let targetFriendId;
|
|
37
37
|
let queueItem;
|
|
@@ -74,6 +74,19 @@ async function handleSurface(input) {
|
|
|
74
74
|
returnTarget: "surface",
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
+
// Fulfill the heart obligation for this origin (separate from inner/mind obligation)
|
|
78
|
+
if (fulfillHeartObligation) {
|
|
79
|
+
try {
|
|
80
|
+
fulfillHeartObligation({
|
|
81
|
+
friendId: queueItem.friendId,
|
|
82
|
+
channel: queueItem.channel,
|
|
83
|
+
key: queueItem.key,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// swallowed — heart obligation fulfillment must never break surface delivery
|
|
88
|
+
}
|
|
89
|
+
}
|
|
77
90
|
(0, attention_queue_1.dequeueAttentionItem)(queue, delegationId);
|
|
78
91
|
}
|
|
79
92
|
// Return delivery status
|