@ouro.bot/cli 0.1.0-alpha.75 → 0.1.0-alpha.77
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 +18 -0
- package/dist/heart/active-work.js +1 -0
- package/dist/heart/commitments.js +9 -2
- package/dist/heart/core.js +16 -0
- package/dist/heart/daemon/daemon-cli.js +37 -0
- package/dist/heart/obligations.js +141 -0
- package/dist/mind/prompt.js +1 -1
- package/dist/repertoire/tools-base.js +15 -0
- package/dist/senses/cli-layout.js +100 -0
- package/dist/senses/cli.js +22 -5
- package/dist/senses/inner-dialog.js +16 -0
- package/dist/senses/pipeline.js +12 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
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.77",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New: Persistent obligation store (state/obligations/*.json). Obligations survive pending message consumption so the agent always knows what it owes and to whom.",
|
|
8
|
+
"Obligations are created when send_message delegates to self or go_inward fires with a return address, and fulfilled when routeDelegatedCompletion delivers the answer.",
|
|
9
|
+
"The commitments frame now reads pending obligations from the store, giving the agent an accurate view of outstanding commitments across all sessions.",
|
|
10
|
+
"readInnerWorkState derives obligationPending from both pending messages and the obligation store, ensuring obligations are visible even after the inner dialog queue drains."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.76",
|
|
15
|
+
"changes": [
|
|
16
|
+
"Fix: CLI chat terminal logging now filters to warn/error only — info-level nerves logs go to ndjson file only, keeping the interactive TUI clean.",
|
|
17
|
+
"Fix: Streamed model output now wraps at word boundaries instead of mid-word. A new StreamingWordWrapper buffers partial lines and breaks at spaces when approaching terminal width.",
|
|
18
|
+
"New: `ouro up` now prints 'ouro updated to <version> (was <previous>)' when npx downloads a newer CLI binary, separate from the agent bundle update message.",
|
|
19
|
+
"Fix: Spinner/log interleave verified — terminal sink reads pause/resume hooks at call time, not creation time, so the filterSink wrapper in CLI logging does not break spinner coordination."
|
|
20
|
+
]
|
|
21
|
+
},
|
|
4
22
|
{
|
|
5
23
|
"version": "0.1.0-alpha.75",
|
|
6
24
|
"changes": [
|
|
@@ -108,6 +108,7 @@ function buildActiveWorkFrame(input) {
|
|
|
108
108
|
freshestForCurrentFriend: friendSessions[0] ?? null,
|
|
109
109
|
otherLiveSessionsForCurrentFriend: friendSessions,
|
|
110
110
|
},
|
|
111
|
+
pendingObligations: input.pendingObligations ?? [],
|
|
111
112
|
targetCandidates: input.targetCandidates ?? [],
|
|
112
113
|
bridgeSuggestion: suggestBridgeForActiveWork({
|
|
113
114
|
currentSession: input.currentSession,
|
|
@@ -3,11 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.deriveCommitments = deriveCommitments;
|
|
4
4
|
exports.formatCommitments = formatCommitments;
|
|
5
5
|
const runtime_1 = require("../nerves/runtime");
|
|
6
|
-
function deriveCommitments(activeWorkFrame, innerJob) {
|
|
6
|
+
function deriveCommitments(activeWorkFrame, innerJob, pendingObligations) {
|
|
7
7
|
const committedTo = [];
|
|
8
8
|
const completionCriteria = [];
|
|
9
9
|
const safeToIgnore = [];
|
|
10
|
-
//
|
|
10
|
+
// Persistent obligations from the obligation store
|
|
11
|
+
if (pendingObligations && pendingObligations.length > 0) {
|
|
12
|
+
for (const ob of pendingObligations) {
|
|
13
|
+
committedTo.push(`i owe ${ob.origin.friendId}: ${ob.content}`);
|
|
14
|
+
}
|
|
15
|
+
completionCriteria.push("fulfill my outstanding obligations");
|
|
16
|
+
}
|
|
17
|
+
// Obligation (from current turn -- kept for backward compat)
|
|
11
18
|
if (typeof activeWorkFrame.currentObligation === "string" && activeWorkFrame.currentObligation.trim().length > 0) {
|
|
12
19
|
committedTo.push(`i told them i'd ${activeWorkFrame.currentObligation.trim()}`);
|
|
13
20
|
}
|
package/dist/heart/core.js
CHANGED
|
@@ -29,6 +29,7 @@ const github_copilot_1 = require("./providers/github-copilot");
|
|
|
29
29
|
const pending_1 = require("../mind/pending");
|
|
30
30
|
const identity_2 = require("./identity");
|
|
31
31
|
const socket_client_1 = require("./daemon/socket-client");
|
|
32
|
+
const obligations_1 = require("./obligations");
|
|
32
33
|
let _providerRuntime = null;
|
|
33
34
|
function getProviderRuntimeFingerprint() {
|
|
34
35
|
const provider = (0, identity_1.loadAgentConfig)().provider;
|
|
@@ -702,6 +703,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
702
703
|
} : {}),
|
|
703
704
|
};
|
|
704
705
|
(0, pending_1.queuePendingMessage)(pendingDir, envelope);
|
|
706
|
+
if (currentSession && !isInnerChannel) {
|
|
707
|
+
try {
|
|
708
|
+
(0, obligations_1.createObligation)((0, identity_2.getAgentRoot)(), {
|
|
709
|
+
origin: {
|
|
710
|
+
friendId: currentSession.friendId,
|
|
711
|
+
channel: currentSession.channel,
|
|
712
|
+
key: currentSession.key,
|
|
713
|
+
},
|
|
714
|
+
content,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
/* v8 ignore next -- defensive: obligation store write failure should not break go_inward @preserve */
|
|
719
|
+
}
|
|
720
|
+
}
|
|
705
721
|
try {
|
|
706
722
|
await (0, socket_client_1.requestInnerWake)((0, identity_2.getAgentName)());
|
|
707
723
|
}
|
|
@@ -37,6 +37,7 @@ exports.ensureDaemonRunning = ensureDaemonRunning;
|
|
|
37
37
|
exports.listGithubCopilotModels = listGithubCopilotModels;
|
|
38
38
|
exports.pingGithubCopilotModel = pingGithubCopilotModel;
|
|
39
39
|
exports.parseOuroCommand = parseOuroCommand;
|
|
40
|
+
exports.readFirstBundleMetaVersion = readFirstBundleMetaVersion;
|
|
40
41
|
exports.discoverExistingCredentials = discoverExistingCredentials;
|
|
41
42
|
exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
|
|
42
43
|
exports.runOuroCli = runOuroCli;
|
|
@@ -971,6 +972,33 @@ function defaultWriteStdout(text) {
|
|
|
971
972
|
// eslint-disable-next-line no-console -- terminal UX: CLI command output
|
|
972
973
|
console.log(text);
|
|
973
974
|
}
|
|
975
|
+
/**
|
|
976
|
+
* Read the runtimeVersion from the first .ouro bundle's bundle-meta.json.
|
|
977
|
+
* Returns undefined if none found or unreadable.
|
|
978
|
+
*/
|
|
979
|
+
function readFirstBundleMetaVersion(bundlesRoot) {
|
|
980
|
+
try {
|
|
981
|
+
if (!fs.existsSync(bundlesRoot))
|
|
982
|
+
return undefined;
|
|
983
|
+
const entries = fs.readdirSync(bundlesRoot, { withFileTypes: true });
|
|
984
|
+
for (const entry of entries) {
|
|
985
|
+
/* v8 ignore next -- skip non-.ouro dirs: tested via version-detect tests @preserve */
|
|
986
|
+
if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
|
|
987
|
+
continue;
|
|
988
|
+
const metaPath = path.join(bundlesRoot, entry.name, "bundle-meta.json");
|
|
989
|
+
if (!fs.existsSync(metaPath))
|
|
990
|
+
continue;
|
|
991
|
+
const raw = fs.readFileSync(metaPath, "utf-8");
|
|
992
|
+
const meta = JSON.parse(raw);
|
|
993
|
+
if (meta.runtimeVersion)
|
|
994
|
+
return meta.runtimeVersion;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
// Best effort — return undefined on any error
|
|
999
|
+
}
|
|
1000
|
+
return undefined;
|
|
1001
|
+
}
|
|
974
1002
|
function defaultCleanupStaleSocket(socketPath) {
|
|
975
1003
|
if (fs.existsSync(socketPath)) {
|
|
976
1004
|
fs.unlinkSync(socketPath);
|
|
@@ -1743,7 +1771,16 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1743
1771
|
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
1744
1772
|
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
1745
1773
|
const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
|
|
1774
|
+
// Snapshot the previous CLI version from the first bundle-meta before
|
|
1775
|
+
// hooks overwrite it. This detects when npx downloaded a newer CLI.
|
|
1776
|
+
const previousCliVersion = readFirstBundleMetaVersion(bundlesRoot);
|
|
1746
1777
|
const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
|
|
1778
|
+
// Notify about CLI binary update (npx downloaded a new version)
|
|
1779
|
+
/* v8 ignore start -- CLI update detection: tested via daemon-cli-version-detect.test.ts @preserve */
|
|
1780
|
+
if (previousCliVersion && previousCliVersion !== currentVersion) {
|
|
1781
|
+
deps.writeStdout(`ouro updated to ${currentVersion} (was ${previousCliVersion})`);
|
|
1782
|
+
}
|
|
1783
|
+
/* v8 ignore stop */
|
|
1747
1784
|
if (updateSummary.updated.length > 0) {
|
|
1748
1785
|
const agents = updateSummary.updated.map((e) => e.agent);
|
|
1749
1786
|
const from = updateSummary.updated[0].from;
|
|
@@ -0,0 +1,141 @@
|
|
|
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.createObligation = createObligation;
|
|
37
|
+
exports.readObligations = readObligations;
|
|
38
|
+
exports.readPendingObligations = readPendingObligations;
|
|
39
|
+
exports.fulfillObligation = fulfillObligation;
|
|
40
|
+
exports.findPendingObligationForOrigin = findPendingObligationForOrigin;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const runtime_1 = require("../nerves/runtime");
|
|
44
|
+
function obligationsDir(agentRoot) {
|
|
45
|
+
return path.join(agentRoot, "state", "obligations");
|
|
46
|
+
}
|
|
47
|
+
function obligationFilePath(agentRoot, id) {
|
|
48
|
+
return path.join(obligationsDir(agentRoot), `${id}.json`);
|
|
49
|
+
}
|
|
50
|
+
function generateId() {
|
|
51
|
+
const timestamp = Date.now();
|
|
52
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
53
|
+
return `${timestamp}-${random}`;
|
|
54
|
+
}
|
|
55
|
+
function createObligation(agentRoot, input) {
|
|
56
|
+
const id = generateId();
|
|
57
|
+
const obligation = {
|
|
58
|
+
id,
|
|
59
|
+
origin: input.origin,
|
|
60
|
+
...(input.bridgeId ? { bridgeId: input.bridgeId } : {}),
|
|
61
|
+
content: input.content,
|
|
62
|
+
status: "pending",
|
|
63
|
+
createdAt: new Date().toISOString(),
|
|
64
|
+
};
|
|
65
|
+
const dir = obligationsDir(agentRoot);
|
|
66
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
67
|
+
fs.writeFileSync(obligationFilePath(agentRoot, id), JSON.stringify(obligation, null, 2), "utf-8");
|
|
68
|
+
(0, runtime_1.emitNervesEvent)({
|
|
69
|
+
component: "engine",
|
|
70
|
+
event: "engine.obligation_created",
|
|
71
|
+
message: "obligation created",
|
|
72
|
+
meta: {
|
|
73
|
+
obligationId: id,
|
|
74
|
+
friendId: input.origin.friendId,
|
|
75
|
+
channel: input.origin.channel,
|
|
76
|
+
key: input.origin.key,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
return obligation;
|
|
80
|
+
}
|
|
81
|
+
function readObligations(agentRoot) {
|
|
82
|
+
const dir = obligationsDir(agentRoot);
|
|
83
|
+
if (!fs.existsSync(dir))
|
|
84
|
+
return [];
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = fs.readdirSync(dir);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* v8 ignore next -- defensive: readdirSync race after existsSync @preserve */
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const jsonFiles = entries.filter((entry) => entry.endsWith(".json")).sort();
|
|
94
|
+
const obligations = [];
|
|
95
|
+
for (const file of jsonFiles) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
if (typeof parsed.id === "string" && typeof parsed.content === "string") {
|
|
100
|
+
obligations.push(parsed);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// skip malformed files
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return obligations;
|
|
108
|
+
}
|
|
109
|
+
function readPendingObligations(agentRoot) {
|
|
110
|
+
return readObligations(agentRoot).filter((ob) => ob.status === "pending");
|
|
111
|
+
}
|
|
112
|
+
function fulfillObligation(agentRoot, obligationId) {
|
|
113
|
+
const filePath = obligationFilePath(agentRoot, obligationId);
|
|
114
|
+
let obligation;
|
|
115
|
+
try {
|
|
116
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
117
|
+
obligation = JSON.parse(raw);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
obligation.status = "fulfilled";
|
|
123
|
+
obligation.fulfilledAt = new Date().toISOString();
|
|
124
|
+
fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf-8");
|
|
125
|
+
(0, runtime_1.emitNervesEvent)({
|
|
126
|
+
component: "engine",
|
|
127
|
+
event: "engine.obligation_fulfilled",
|
|
128
|
+
message: "obligation fulfilled",
|
|
129
|
+
meta: {
|
|
130
|
+
obligationId,
|
|
131
|
+
friendId: obligation.origin.friendId,
|
|
132
|
+
channel: obligation.origin.channel,
|
|
133
|
+
key: obligation.origin.key,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function findPendingObligationForOrigin(agentRoot, origin) {
|
|
138
|
+
return readPendingObligations(agentRoot).find((ob) => ob.origin.friendId === origin.friendId
|
|
139
|
+
&& ob.origin.channel === origin.channel
|
|
140
|
+
&& ob.origin.key === origin.key);
|
|
141
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -503,7 +503,7 @@ function commitmentsSection(options) {
|
|
|
503
503
|
const job = options.activeWorkFrame.inner?.job;
|
|
504
504
|
if (!job)
|
|
505
505
|
return "";
|
|
506
|
-
const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job);
|
|
506
|
+
const commitments = (0, commitments_1.deriveCommitments)(options.activeWorkFrame, job, options.activeWorkFrame.pendingObligations);
|
|
507
507
|
if (commitments.committedTo.length === 0)
|
|
508
508
|
return "";
|
|
509
509
|
return `## my commitments\n${commitments.committedTo.map((c) => `- ${c}`).join("\n")}`;
|
|
@@ -53,6 +53,7 @@ const memory_1 = require("../mind/memory");
|
|
|
53
53
|
const pending_1 = require("../mind/pending");
|
|
54
54
|
const progress_story_1 = require("../heart/progress-story");
|
|
55
55
|
const cross_chat_delivery_1 = require("../heart/cross-chat-delivery");
|
|
56
|
+
const obligations_1 = require("../heart/obligations");
|
|
56
57
|
// Tracks which file paths have been read via read_file in this session.
|
|
57
58
|
// edit_file requires a file to be read first (must-read-first guard).
|
|
58
59
|
exports.editFileReadTracker = new Set();
|
|
@@ -893,6 +894,20 @@ exports.baseToolDefinitions = [
|
|
|
893
894
|
if (isSelf) {
|
|
894
895
|
writePendingEnvelope(pendingDir, envelope);
|
|
895
896
|
if (delegatedFrom) {
|
|
897
|
+
try {
|
|
898
|
+
(0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
|
|
899
|
+
origin: {
|
|
900
|
+
friendId: delegatedFrom.friendId,
|
|
901
|
+
channel: delegatedFrom.channel,
|
|
902
|
+
key: delegatedFrom.key,
|
|
903
|
+
},
|
|
904
|
+
...(delegatedFrom.bridgeId ? { bridgeId: delegatedFrom.bridgeId } : {}),
|
|
905
|
+
content,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
catch {
|
|
909
|
+
/* v8 ignore next -- defensive: obligation store write failure should not break send_message @preserve */
|
|
910
|
+
}
|
|
896
911
|
(0, runtime_1.emitNervesEvent)({
|
|
897
912
|
event: "repertoire.obligation_created",
|
|
898
913
|
component: "repertoire",
|
|
@@ -1,8 +1,108 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamingWordWrapper = void 0;
|
|
3
4
|
exports.wrapCliText = wrapCliText;
|
|
4
5
|
exports.formatEchoedInputSummary = formatEchoedInputSummary;
|
|
5
6
|
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
// Strip ANSI escape sequences to measure visible text width.
|
|
8
|
+
const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
9
|
+
function visibleLength(text) {
|
|
10
|
+
return text.replace(ANSI_RE, "").length;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Streaming word wrapper for terminal output.
|
|
14
|
+
*
|
|
15
|
+
* Text arrives in small chunks (sometimes single characters). This class
|
|
16
|
+
* buffers a partial line and emits complete wrapped lines at word boundaries
|
|
17
|
+
* when the visible width approaches the terminal column limit.
|
|
18
|
+
*
|
|
19
|
+
* ANSI escape sequences are treated as zero-width so colours and styles
|
|
20
|
+
* pass through without affecting line-break decisions.
|
|
21
|
+
*/
|
|
22
|
+
class StreamingWordWrapper {
|
|
23
|
+
col = 0; // visible columns consumed on the current line
|
|
24
|
+
buf = ""; // buffered text for the current line (not yet emitted)
|
|
25
|
+
width; // terminal column count
|
|
26
|
+
constructor(cols) {
|
|
27
|
+
this.width = Math.max(cols ?? (process.stdout.columns || 80), 1);
|
|
28
|
+
}
|
|
29
|
+
/** Accept a chunk of already-rendered text and return text ready for stdout. */
|
|
30
|
+
push(text) {
|
|
31
|
+
let out = "";
|
|
32
|
+
for (let i = 0; i < text.length; i++) {
|
|
33
|
+
const ch = text[i];
|
|
34
|
+
// Pass through ANSI escape sequences without counting width
|
|
35
|
+
/* v8 ignore start -- ANSI handling: tested via StreamingWordWrapper ANSI test @preserve */
|
|
36
|
+
if (ch === "\x1b") {
|
|
37
|
+
const rest = text.slice(i);
|
|
38
|
+
const m = rest.match(/^\x1b\[[0-9;]*[A-Za-z]/);
|
|
39
|
+
if (m) {
|
|
40
|
+
this.buf += m[0];
|
|
41
|
+
i += m[0].length - 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/* v8 ignore stop */
|
|
46
|
+
// Explicit newline: flush current line and reset
|
|
47
|
+
if (ch === "\n") {
|
|
48
|
+
out += this.buf + "\n";
|
|
49
|
+
this.buf = "";
|
|
50
|
+
this.col = 0;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Space: if the current line is already at or past width, wrap now.
|
|
54
|
+
// Otherwise just append.
|
|
55
|
+
if (ch === " ") {
|
|
56
|
+
/* v8 ignore start -- wrap-at-space: tested via StreamingWordWrapper unit tests @preserve */
|
|
57
|
+
if (this.col >= this.width) {
|
|
58
|
+
out += this.buf + "\n";
|
|
59
|
+
this.buf = "";
|
|
60
|
+
this.col = 0;
|
|
61
|
+
// Drop the space at the wrap point
|
|
62
|
+
continue;
|
|
63
|
+
/* v8 ignore stop */
|
|
64
|
+
}
|
|
65
|
+
this.buf += ch;
|
|
66
|
+
this.col++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Non-space character
|
|
70
|
+
this.col++;
|
|
71
|
+
if (this.col > this.width) {
|
|
72
|
+
// We've exceeded the width. Try to break at the last space.
|
|
73
|
+
const lastSpace = this.buf.lastIndexOf(" ");
|
|
74
|
+
if (lastSpace !== -1) {
|
|
75
|
+
out += this.buf.slice(0, lastSpace) + "\n";
|
|
76
|
+
// Keep the remainder (after space) plus current char
|
|
77
|
+
this.buf = this.buf.slice(lastSpace + 1) + ch;
|
|
78
|
+
this.col = visibleLength(this.buf);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// No space to break at — hard wrap
|
|
82
|
+
out += this.buf + "\n";
|
|
83
|
+
this.buf = ch;
|
|
84
|
+
this.col = 1;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
this.buf += ch;
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/** Flush any remaining buffered text (call at end of response). */
|
|
93
|
+
flush() {
|
|
94
|
+
const remainder = this.buf;
|
|
95
|
+
this.buf = "";
|
|
96
|
+
this.col = 0;
|
|
97
|
+
return remainder;
|
|
98
|
+
}
|
|
99
|
+
/** Reset wrapper state (call at start of new model turn). */
|
|
100
|
+
reset() {
|
|
101
|
+
this.buf = "";
|
|
102
|
+
this.col = 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.StreamingWordWrapper = StreamingWordWrapper;
|
|
6
106
|
function splitLongWord(word, width) {
|
|
7
107
|
const chunks = [];
|
|
8
108
|
for (let index = 0; index < word.length; index += width) {
|
package/dist/senses/cli.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.MarkdownStreamer = exports.InputController = exports.Spinner = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
36
|
+
exports.MarkdownStreamer = exports.InputController = exports.Spinner = exports.StreamingWordWrapper = exports.wrapCliText = exports.formatEchoedInputSummary = void 0;
|
|
37
37
|
exports.formatPendingPrefix = formatPendingPrefix;
|
|
38
38
|
exports.getCliContinuityIngressTexts = getCliContinuityIngressTexts;
|
|
39
39
|
exports.pauseActiveSpinner = pauseActiveSpinner;
|
|
@@ -76,6 +76,7 @@ const cli_layout_1 = require("./cli-layout");
|
|
|
76
76
|
var cli_layout_2 = require("./cli-layout");
|
|
77
77
|
Object.defineProperty(exports, "formatEchoedInputSummary", { enumerable: true, get: function () { return cli_layout_2.formatEchoedInputSummary; } });
|
|
78
78
|
Object.defineProperty(exports, "wrapCliText", { enumerable: true, get: function () { return cli_layout_2.wrapCliText; } });
|
|
79
|
+
Object.defineProperty(exports, "StreamingWordWrapper", { enumerable: true, get: function () { return cli_layout_2.StreamingWordWrapper; } });
|
|
79
80
|
/**
|
|
80
81
|
* Format pending messages as content-prefix strings for injection into
|
|
81
82
|
* the next user message. Self-messages (from === agentName) become
|
|
@@ -349,6 +350,7 @@ function createCliCallbacks() {
|
|
|
349
350
|
let hadToolRun = false;
|
|
350
351
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
351
352
|
const streamer = new MarkdownStreamer();
|
|
353
|
+
const wrapper = new cli_layout_1.StreamingWordWrapper();
|
|
352
354
|
return {
|
|
353
355
|
onModelStart: () => {
|
|
354
356
|
currentSpinner?.stop();
|
|
@@ -356,6 +358,7 @@ function createCliCallbacks() {
|
|
|
356
358
|
hadReasoning = false;
|
|
357
359
|
textDirty = false;
|
|
358
360
|
streamer.reset();
|
|
361
|
+
wrapper.reset();
|
|
359
362
|
const phrases = (0, phrases_1.getPhrases)();
|
|
360
363
|
const pool = hadToolRun ? phrases.followup : phrases.thinking;
|
|
361
364
|
const first = (0, phrases_1.pickPhrase)(pool);
|
|
@@ -369,6 +372,7 @@ function createCliCallbacks() {
|
|
|
369
372
|
},
|
|
370
373
|
onClearText: () => {
|
|
371
374
|
streamer.reset();
|
|
375
|
+
wrapper.reset();
|
|
372
376
|
},
|
|
373
377
|
onTextChunk: (text) => {
|
|
374
378
|
// Stop spinner if still running — final_answer streaming and Anthropic
|
|
@@ -385,8 +389,13 @@ function createCliCallbacks() {
|
|
|
385
389
|
hadReasoning = false;
|
|
386
390
|
}
|
|
387
391
|
const rendered = streamer.push(text);
|
|
388
|
-
|
|
389
|
-
|
|
392
|
+
/* v8 ignore start -- wrapper integration: tested via cli.test.ts onTextChunk tests @preserve */
|
|
393
|
+
if (rendered) {
|
|
394
|
+
const wrapped = wrapper.push(rendered);
|
|
395
|
+
if (wrapped)
|
|
396
|
+
process.stdout.write(wrapped);
|
|
397
|
+
}
|
|
398
|
+
/* v8 ignore stop */
|
|
390
399
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
391
400
|
},
|
|
392
401
|
onReasoningChunk: (text) => {
|
|
@@ -445,9 +454,17 @@ function createCliCallbacks() {
|
|
|
445
454
|
flushMarkdown: () => {
|
|
446
455
|
currentSpinner?.stop();
|
|
447
456
|
setSpinner(null);
|
|
457
|
+
/* v8 ignore start -- wrapper flush: tested via cli.test.ts flushMarkdown tests @preserve */
|
|
448
458
|
const remaining = streamer.flush();
|
|
449
|
-
if (remaining)
|
|
450
|
-
|
|
459
|
+
if (remaining) {
|
|
460
|
+
const wrapped = wrapper.push(remaining);
|
|
461
|
+
if (wrapped)
|
|
462
|
+
process.stdout.write(wrapped);
|
|
463
|
+
}
|
|
464
|
+
const tail = wrapper.flush();
|
|
465
|
+
if (tail)
|
|
466
|
+
process.stdout.write(tail);
|
|
467
|
+
/* v8 ignore stop */
|
|
451
468
|
},
|
|
452
469
|
};
|
|
453
470
|
}
|
|
@@ -62,6 +62,7 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
62
62
|
const manager_1 = require("../heart/bridges/manager");
|
|
63
63
|
const session_activity_1 = require("../heart/session-activity");
|
|
64
64
|
const bluebubbles_1 = require("./bluebubbles");
|
|
65
|
+
const obligations_1 = require("../heart/obligations");
|
|
65
66
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
66
67
|
{
|
|
67
68
|
id: "heartbeat_checkin",
|
|
@@ -305,6 +306,21 @@ async function routeDelegatedCompletion(agentRoot, agentName, completion, draine
|
|
|
305
306
|
}
|
|
306
307
|
const delegatedFrom = enrichDelegatedFromWithBridge(delegated.delegatedFrom);
|
|
307
308
|
if (delegated.obligationStatus === "pending") {
|
|
309
|
+
// Fulfill the persistent obligation in the store
|
|
310
|
+
try {
|
|
311
|
+
const pending = (0, obligations_1.findPendingObligationForOrigin)(agentRoot, {
|
|
312
|
+
friendId: delegatedFrom.friendId,
|
|
313
|
+
channel: delegatedFrom.channel,
|
|
314
|
+
key: delegatedFrom.key,
|
|
315
|
+
});
|
|
316
|
+
/* v8 ignore next 2 -- obligation fulfillment tested via obligations.test.ts; integration requires real disk state @preserve */
|
|
317
|
+
if (pending) {
|
|
318
|
+
(0, obligations_1.fulfillObligation)(agentRoot, pending.id);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
/* v8 ignore next -- defensive: obligation store read failure should not break delivery @preserve */
|
|
323
|
+
}
|
|
308
324
|
(0, runtime_1.emitNervesEvent)({
|
|
309
325
|
event: "senses.obligation_fulfilled",
|
|
310
326
|
component: "senses",
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -17,6 +17,7 @@ const delegation_1 = require("../heart/delegation");
|
|
|
17
17
|
const target_resolution_1 = require("../heart/target-resolution");
|
|
18
18
|
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
19
19
|
const pending_1 = require("../mind/pending");
|
|
20
|
+
const obligations_1 = require("../heart/obligations");
|
|
20
21
|
function emptyTaskBoard() {
|
|
21
22
|
return {
|
|
22
23
|
compact: "",
|
|
@@ -55,12 +56,14 @@ function readInnerWorkState() {
|
|
|
55
56
|
const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
|
|
56
57
|
const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
|
|
57
58
|
const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
|
|
59
|
+
// Derive obligationPending from both the pending message field and the obligation store
|
|
60
|
+
const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
|
|
58
61
|
return {
|
|
59
62
|
status: dialogStatus.processing === "started" ? "running" : "idle",
|
|
60
63
|
hasPending: dialogStatus.queue !== "clear",
|
|
61
64
|
origin: dialogStatus.origin,
|
|
62
65
|
contentSnippet: dialogStatus.contentSnippet,
|
|
63
|
-
obligationPending: dialogStatus.obligationPending,
|
|
66
|
+
obligationPending: dialogStatus.obligationPending || storeObligationPending,
|
|
64
67
|
job,
|
|
65
68
|
};
|
|
66
69
|
}
|
|
@@ -176,12 +179,20 @@ async function handleInboundTurn(input) {
|
|
|
176
179
|
catch {
|
|
177
180
|
targetCandidates = [];
|
|
178
181
|
}
|
|
182
|
+
let pendingObligations = [];
|
|
183
|
+
try {
|
|
184
|
+
pendingObligations = (0, obligations_1.readPendingObligations)((0, identity_1.getAgentRoot)());
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
pendingObligations = [];
|
|
188
|
+
}
|
|
179
189
|
const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
|
|
180
190
|
currentSession,
|
|
181
191
|
currentObligation,
|
|
182
192
|
mustResolveBeforeHandoff,
|
|
183
193
|
inner: readInnerWorkState(),
|
|
184
194
|
bridges: activeBridges,
|
|
195
|
+
pendingObligations,
|
|
185
196
|
taskBoard: (() => {
|
|
186
197
|
try {
|
|
187
198
|
return (0, tasks_1.getTaskModule)().getBoard();
|