@ouro.bot/cli 0.1.0-alpha.17 → 0.1.0-alpha.170
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/AdoptionSpecialist.ouro/agent.json +3 -2
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -3
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +187 -191
- package/changelog.json +1145 -0
- package/dist/heart/active-work.js +633 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +40 -30
- package/dist/heart/core.js +608 -123
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/auth-flow.js +477 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/daemon-cli.js +2473 -206
- package/dist/heart/daemon/daemon-entry.js +140 -4
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +446 -17
- package/dist/heart/daemon/habit-migration.js +181 -0
- package/dist/heart/daemon/habit-parser.js +119 -0
- package/dist/heart/daemon/habit-scheduler.js +369 -0
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +30 -111
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +161 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/mcp-server.js +591 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +78 -34
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/outlook-http.js +399 -0
- package/dist/heart/daemon/outlook-read.js +1291 -0
- package/dist/heart/daemon/outlook-render.js +1032 -0
- package/dist/heart/daemon/outlook-types.js +7 -0
- package/dist/heart/daemon/outlook-view.js +194 -0
- package/dist/heart/daemon/process-manager.js +75 -0
- package/dist/heart/daemon/run-hooks.js +39 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +13 -8
- package/dist/heart/daemon/specialist-tools.js +53 -4
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/identity.js +136 -55
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +172 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +196 -36
- package/dist/heart/providers/azure.js +116 -10
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +51 -16
- package/dist/heart/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +62 -32
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +122 -32
- package/dist/mind/bundle-manifest.js +14 -0
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +70 -56
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +56 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/obligations.js +134 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +649 -89
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +63 -3
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-CBueh0d3.css +1 -0
- package/dist/outlook-ui/assets/index-GtHoFpRM.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-semantic.js +6 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +149 -2
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +135 -2
- package/dist/repertoire/guardrails.js +291 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +11 -28
- package/dist/repertoire/tools-base.js +991 -272
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-teams.js +12 -59
- package/dist/repertoire/tools.js +281 -122
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/bluebubbles-client.js +102 -3
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +118 -23
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +1008 -102
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +419 -152
- package/dist/senses/commands.js +38 -1
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +592 -84
- package/dist/senses/pipeline.js +502 -0
- package/dist/senses/shared-turn.js +196 -0
- package/dist/senses/surface-tool.js +95 -0
- package/dist/senses/teams.js +667 -120
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +13 -5
- package/skills/configure-dev-tools.md +81 -0
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/senses/debug-activity.js +0 -107
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
|
@@ -0,0 +1,172 @@
|
|
|
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.listSessionActivity = listSessionActivity;
|
|
37
|
+
exports.findFreshestFriendSession = findFreshestFriendSession;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const runtime_1 = require("../nerves/runtime");
|
|
41
|
+
const config_1 = require("./config");
|
|
42
|
+
const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
43
|
+
function activityPriority(source) {
|
|
44
|
+
return source === "friend-facing" ? 0 : 1;
|
|
45
|
+
}
|
|
46
|
+
function resolveFriendName(friendId, friendsDir, agentName) {
|
|
47
|
+
if (friendId === "self")
|
|
48
|
+
return agentName;
|
|
49
|
+
try {
|
|
50
|
+
const raw = fs.readFileSync(path.join(friendsDir, `${friendId}.json`), "utf-8");
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
return parsed.name ?? friendId;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return friendId;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseFriendActivity(sessionPath) {
|
|
59
|
+
let mtimeMs;
|
|
60
|
+
try {
|
|
61
|
+
mtimeMs = fs.statSync(sessionPath).mtimeMs;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(sessionPath, "utf-8");
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
const explicit = parsed?.state?.lastFriendActivityAt;
|
|
70
|
+
if (typeof explicit === "string") {
|
|
71
|
+
const parsedMs = Date.parse(explicit);
|
|
72
|
+
if (Number.isFinite(parsedMs)) {
|
|
73
|
+
return {
|
|
74
|
+
lastActivityMs: parsedMs,
|
|
75
|
+
lastActivityAt: new Date(parsedMs).toISOString(),
|
|
76
|
+
activitySource: "friend-facing",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// fall back to file mtime below
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
lastActivityMs: mtimeMs,
|
|
86
|
+
lastActivityAt: new Date(mtimeMs).toISOString(),
|
|
87
|
+
activitySource: "mtime-fallback",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function listSessionActivity(query) {
|
|
91
|
+
const { sessionsDir, friendsDir, agentName, activeThresholdMs = DEFAULT_ACTIVE_THRESHOLD_MS, nowMs = Date.now(), currentSession = null, } = query;
|
|
92
|
+
(0, runtime_1.emitNervesEvent)({
|
|
93
|
+
component: "daemon",
|
|
94
|
+
event: "daemon.session_activity_scan",
|
|
95
|
+
message: "scanning session activity",
|
|
96
|
+
meta: {
|
|
97
|
+
sessionsDir,
|
|
98
|
+
currentSession: currentSession ? `${currentSession.friendId}/${currentSession.channel}/${currentSession.key}` : null,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (!fs.existsSync(sessionsDir))
|
|
102
|
+
return [];
|
|
103
|
+
const results = [];
|
|
104
|
+
let friendDirs;
|
|
105
|
+
try {
|
|
106
|
+
friendDirs = fs.readdirSync(sessionsDir);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
for (const friendId of friendDirs) {
|
|
112
|
+
const friendPath = path.join(sessionsDir, friendId);
|
|
113
|
+
let channels;
|
|
114
|
+
try {
|
|
115
|
+
channels = fs.readdirSync(friendPath);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const channel of channels) {
|
|
121
|
+
const channelPath = path.join(friendPath, channel);
|
|
122
|
+
let keys;
|
|
123
|
+
try {
|
|
124
|
+
keys = fs.readdirSync(channelPath);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const keyFile of keys) {
|
|
130
|
+
if (!keyFile.endsWith(".json"))
|
|
131
|
+
continue;
|
|
132
|
+
const key = keyFile.replace(/\.json$/, "");
|
|
133
|
+
// Compare with sanitizeKey on both sides — session keys from the filesystem
|
|
134
|
+
// are already sanitized (colons → underscores), but the canonical key from
|
|
135
|
+
// the pipeline may still have colons (e.g. "chat:any" vs "chat_any").
|
|
136
|
+
if (currentSession && friendId === currentSession.friendId && channel === currentSession.channel && (0, config_1.sanitizeKey)(key) === (0, config_1.sanitizeKey)(currentSession.key)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const sessionPath = path.join(channelPath, keyFile);
|
|
140
|
+
const activity = parseFriendActivity(sessionPath);
|
|
141
|
+
if (!activity)
|
|
142
|
+
continue;
|
|
143
|
+
if (nowMs - activity.lastActivityMs > activeThresholdMs)
|
|
144
|
+
continue;
|
|
145
|
+
results.push({
|
|
146
|
+
friendId,
|
|
147
|
+
friendName: resolveFriendName(friendId, friendsDir, agentName),
|
|
148
|
+
channel,
|
|
149
|
+
key,
|
|
150
|
+
sessionPath,
|
|
151
|
+
lastActivityAt: activity.lastActivityAt,
|
|
152
|
+
lastActivityMs: activity.lastActivityMs,
|
|
153
|
+
activitySource: activity.activitySource,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return results.sort((a, b) => {
|
|
159
|
+
const sourceDiff = activityPriority(a.activitySource) - activityPriority(b.activitySource);
|
|
160
|
+
if (sourceDiff !== 0)
|
|
161
|
+
return sourceDiff;
|
|
162
|
+
return b.lastActivityMs - a.lastActivityMs;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function findFreshestFriendSession(query) {
|
|
166
|
+
const { activeOnly = false, activeThresholdMs = DEFAULT_ACTIVE_THRESHOLD_MS, nowMs, ...rest } = query;
|
|
167
|
+
const currentSession = rest.currentSession ?? null;
|
|
168
|
+
const all = activeOnly
|
|
169
|
+
? listSessionActivity({ ...rest, activeThresholdMs, nowMs, currentSession })
|
|
170
|
+
: listSessionActivity({ ...rest, activeThresholdMs: Number.MAX_SAFE_INTEGER, nowMs, currentSession });
|
|
171
|
+
return all.find((entry) => entry.friendId === query.friendId) ?? null;
|
|
172
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.recallSession = recallSession;
|
|
37
|
+
exports.searchSessionTranscript = searchSessionTranscript;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const runtime_1 = require("../nerves/runtime");
|
|
40
|
+
function normalizeContent(content) {
|
|
41
|
+
if (typeof content === "string")
|
|
42
|
+
return content;
|
|
43
|
+
if (!Array.isArray(content))
|
|
44
|
+
return "";
|
|
45
|
+
return content
|
|
46
|
+
.map((part) => (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
|
|
47
|
+
? String(part.text ?? "")
|
|
48
|
+
: ""))
|
|
49
|
+
.filter((text) => text.length > 0)
|
|
50
|
+
.join("");
|
|
51
|
+
}
|
|
52
|
+
function normalizeSessionMessages(messages) {
|
|
53
|
+
if (!Array.isArray(messages))
|
|
54
|
+
return [];
|
|
55
|
+
return messages
|
|
56
|
+
.map((message) => {
|
|
57
|
+
const record = message && typeof message === "object" ? message : {};
|
|
58
|
+
return {
|
|
59
|
+
role: typeof record.role === "string" ? record.role : "",
|
|
60
|
+
content: normalizeContent(record.content),
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
.filter((message) => message.role !== "system" && message.content.length > 0);
|
|
64
|
+
}
|
|
65
|
+
function buildSummaryInstruction(friendId, channel, trustLevel) {
|
|
66
|
+
if (friendId === "self" && channel === "inner") {
|
|
67
|
+
return "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning.";
|
|
68
|
+
}
|
|
69
|
+
return `summarize this session transcript. the person asking has trust level: ${trustLevel}. family=full transparency, friend=share work and general topics but protect other people's identities, acquaintance=very guarded minimal disclosure.`;
|
|
70
|
+
}
|
|
71
|
+
function clip(text, limit = 160) {
|
|
72
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
73
|
+
return compact.length > limit ? compact.slice(0, limit - 1) + "…" : compact;
|
|
74
|
+
}
|
|
75
|
+
function buildSnapshot(summary, tailMessages) {
|
|
76
|
+
const lines = [`recent focus: ${clip(summary, 240)}`];
|
|
77
|
+
const latestUser = [...tailMessages].reverse().find((message) => message.role === "user")?.content;
|
|
78
|
+
const latestAssistant = [...tailMessages].reverse().find((message) => message.role === "assistant")?.content;
|
|
79
|
+
if (latestUser) {
|
|
80
|
+
lines.push(`latest user: ${clip(latestUser)}`);
|
|
81
|
+
}
|
|
82
|
+
if (latestAssistant) {
|
|
83
|
+
lines.push(`latest assistant: ${clip(latestAssistant)}`);
|
|
84
|
+
}
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
function buildSearchSnapshot(query, messages, includeLatestTurn = true) {
|
|
88
|
+
const lines = [`history query: "${clip(query, 120)}"`];
|
|
89
|
+
if (!includeLatestTurn) {
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
const latestUser = [...messages].reverse().find((message) => message.role === "user")?.content;
|
|
93
|
+
const latestAssistant = [...messages].reverse().find((message) => message.role === "assistant")?.content;
|
|
94
|
+
if (latestUser) {
|
|
95
|
+
lines.push(`latest user: ${clip(latestUser)}`);
|
|
96
|
+
}
|
|
97
|
+
if (latestAssistant) {
|
|
98
|
+
lines.push(`latest assistant: ${clip(latestAssistant)}`);
|
|
99
|
+
}
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
function buildSearchExcerpts(messages, query, maxMatches) {
|
|
103
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
104
|
+
if (!normalizedQuery)
|
|
105
|
+
return [];
|
|
106
|
+
const candidates = [];
|
|
107
|
+
let lastMatchIndex = -2;
|
|
108
|
+
for (let i = 0; i < messages.length; i++) {
|
|
109
|
+
if (!messages[i].content.toLowerCase().includes(normalizedQuery))
|
|
110
|
+
continue;
|
|
111
|
+
if (i <= lastMatchIndex + 1)
|
|
112
|
+
continue;
|
|
113
|
+
lastMatchIndex = i;
|
|
114
|
+
const start = Math.max(0, i - 1);
|
|
115
|
+
const end = Math.min(messages.length, i + 2);
|
|
116
|
+
const excerpt = messages
|
|
117
|
+
.slice(start, end)
|
|
118
|
+
.map((message) => `[${message.role}] ${clip(message.content, 200)}`)
|
|
119
|
+
.join("\n");
|
|
120
|
+
const score = messages
|
|
121
|
+
.slice(start, end)
|
|
122
|
+
.filter((message) => message.content.toLowerCase().includes(normalizedQuery))
|
|
123
|
+
.length;
|
|
124
|
+
candidates.push({ excerpt, score, index: i });
|
|
125
|
+
}
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
return candidates
|
|
128
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
129
|
+
.filter((candidate) => {
|
|
130
|
+
if (seen.has(candidate.excerpt))
|
|
131
|
+
return false;
|
|
132
|
+
seen.add(candidate.excerpt);
|
|
133
|
+
return true;
|
|
134
|
+
})
|
|
135
|
+
.slice(0, maxMatches)
|
|
136
|
+
.map((candidate) => candidate.excerpt);
|
|
137
|
+
}
|
|
138
|
+
async function recallSession(options) {
|
|
139
|
+
(0, runtime_1.emitNervesEvent)({
|
|
140
|
+
component: "daemon",
|
|
141
|
+
event: "daemon.session_recall",
|
|
142
|
+
message: "recalling session transcript tail",
|
|
143
|
+
meta: {
|
|
144
|
+
friendId: options.friendId,
|
|
145
|
+
channel: options.channel,
|
|
146
|
+
key: options.key,
|
|
147
|
+
messageCount: options.messageCount,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
let raw;
|
|
151
|
+
try {
|
|
152
|
+
raw = fs.readFileSync(options.sessionPath, "utf-8");
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { kind: "missing" };
|
|
156
|
+
}
|
|
157
|
+
const parsed = JSON.parse(raw);
|
|
158
|
+
const tailMessages = normalizeSessionMessages(parsed.messages).slice(-options.messageCount);
|
|
159
|
+
if (tailMessages.length === 0) {
|
|
160
|
+
return { kind: "empty" };
|
|
161
|
+
}
|
|
162
|
+
const transcript = tailMessages
|
|
163
|
+
.map((message) => `[${message.role}] ${message.content}`)
|
|
164
|
+
.join("\n");
|
|
165
|
+
const summary = options.summarize
|
|
166
|
+
? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
|
|
167
|
+
: transcript;
|
|
168
|
+
return {
|
|
169
|
+
kind: "ok",
|
|
170
|
+
transcript,
|
|
171
|
+
summary,
|
|
172
|
+
snapshot: buildSnapshot(summary, tailMessages),
|
|
173
|
+
tailMessages,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function searchSessionTranscript(options) {
|
|
177
|
+
(0, runtime_1.emitNervesEvent)({
|
|
178
|
+
component: "daemon",
|
|
179
|
+
event: "daemon.session_search",
|
|
180
|
+
message: "searching session transcript",
|
|
181
|
+
meta: {
|
|
182
|
+
friendId: options.friendId,
|
|
183
|
+
channel: options.channel,
|
|
184
|
+
key: options.key,
|
|
185
|
+
query: options.query,
|
|
186
|
+
maxMatches: options.maxMatches ?? 5,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
let raw;
|
|
190
|
+
try {
|
|
191
|
+
raw = fs.readFileSync(options.sessionPath, "utf-8");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return { kind: "missing" };
|
|
195
|
+
}
|
|
196
|
+
const parsed = JSON.parse(raw);
|
|
197
|
+
const messages = normalizeSessionMessages(parsed.messages);
|
|
198
|
+
if (messages.length === 0) {
|
|
199
|
+
return { kind: "empty" };
|
|
200
|
+
}
|
|
201
|
+
const query = options.query.trim();
|
|
202
|
+
const matches = buildSearchExcerpts(messages, query, options.maxMatches ?? 5);
|
|
203
|
+
if (matches.length === 0) {
|
|
204
|
+
return {
|
|
205
|
+
kind: "no_match",
|
|
206
|
+
query,
|
|
207
|
+
snapshot: buildSearchSnapshot(query, messages),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
kind: "ok",
|
|
212
|
+
query,
|
|
213
|
+
snapshot: buildSearchSnapshot(query, messages, false),
|
|
214
|
+
matches,
|
|
215
|
+
};
|
|
216
|
+
}
|
package/dist/heart/streaming.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.SettleStreamer = exports.SettleParser = void 0;
|
|
4
4
|
exports.toResponsesInput = toResponsesInput;
|
|
5
5
|
exports.toResponsesTools = toResponsesTools;
|
|
6
6
|
exports.streamChatCompletion = streamChatCompletion;
|
|
7
7
|
exports.streamResponsesApi = streamResponsesApi;
|
|
8
8
|
const runtime_1 = require("../nerves/runtime");
|
|
9
9
|
// Character-level state machine that extracts the answer value from
|
|
10
|
-
// `
|
|
10
|
+
// `settle` tool call JSON arguments as they stream in.
|
|
11
11
|
// Scans for prefix `"answer":"` or `"answer": "` in the character stream,
|
|
12
12
|
// then emits text handling JSON escapes, stopping at unescaped closing `"`.
|
|
13
|
-
class
|
|
13
|
+
class SettleParser {
|
|
14
14
|
// Possible prefixes to match (with and without space after colon)
|
|
15
15
|
static PREFIXES = ['"answer":"', '"answer": "'];
|
|
16
16
|
// Buffer of characters seen so far (pre-activation only)
|
|
@@ -29,7 +29,7 @@ class FinalAnswerParser {
|
|
|
29
29
|
if (!this._active) {
|
|
30
30
|
this.buf += ch;
|
|
31
31
|
// Check if any prefix has been fully matched in the buffer
|
|
32
|
-
for (const prefix of
|
|
32
|
+
for (const prefix of SettleParser.PREFIXES) {
|
|
33
33
|
if (this.buf.endsWith(prefix)) {
|
|
34
34
|
this._active = true;
|
|
35
35
|
break;
|
|
@@ -76,7 +76,42 @@ class FinalAnswerParser {
|
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
exports.
|
|
79
|
+
exports.SettleParser = SettleParser;
|
|
80
|
+
// Shared helper: wraps SettleParser with onClearText + onTextChunk wiring.
|
|
81
|
+
// Used by all streaming providers (Chat Completions, Responses API, Anthropic)
|
|
82
|
+
// so the eager-match settle streaming pattern lives in one place.
|
|
83
|
+
class SettleStreamer {
|
|
84
|
+
parser = new SettleParser();
|
|
85
|
+
_detected = false;
|
|
86
|
+
callbacks;
|
|
87
|
+
enabled;
|
|
88
|
+
constructor(callbacks, enabled = true) {
|
|
89
|
+
this.callbacks = callbacks;
|
|
90
|
+
this.enabled = enabled;
|
|
91
|
+
}
|
|
92
|
+
get detected() { return this._detected; }
|
|
93
|
+
get streamed() { return this.parser.active; }
|
|
94
|
+
/** Mark settle as detected. Calls onClearText on the callbacks. */
|
|
95
|
+
activate() {
|
|
96
|
+
if (!this.enabled)
|
|
97
|
+
return;
|
|
98
|
+
if (this._detected)
|
|
99
|
+
return;
|
|
100
|
+
this._detected = true;
|
|
101
|
+
this.callbacks.onClearText?.();
|
|
102
|
+
}
|
|
103
|
+
/** Feed an argument delta through the parser. Emits text via onTextChunk. */
|
|
104
|
+
processDelta(delta) {
|
|
105
|
+
if (!this.enabled)
|
|
106
|
+
return;
|
|
107
|
+
if (!this._detected)
|
|
108
|
+
return;
|
|
109
|
+
const text = this.parser.process(delta);
|
|
110
|
+
if (text)
|
|
111
|
+
this.callbacks.onTextChunk(text);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.SettleStreamer = SettleStreamer;
|
|
80
115
|
function toResponsesUserContent(content) {
|
|
81
116
|
if (typeof content === "string") {
|
|
82
117
|
return content;
|
|
@@ -156,7 +191,10 @@ function toResponsesInput(messages) {
|
|
|
156
191
|
}
|
|
157
192
|
}
|
|
158
193
|
if (a.content) {
|
|
159
|
-
|
|
194
|
+
const assistantItem = { role: "assistant", content: typeof a.content === "string" ? a.content : "" };
|
|
195
|
+
if (a.phase)
|
|
196
|
+
assistantItem.phase = a.phase;
|
|
197
|
+
input.push(assistantItem);
|
|
160
198
|
}
|
|
161
199
|
if (a.tool_calls) {
|
|
162
200
|
for (const tc of a.tool_calls) {
|
|
@@ -195,7 +233,7 @@ function toResponsesTools(ccTools) {
|
|
|
195
233
|
strict: false,
|
|
196
234
|
}));
|
|
197
235
|
}
|
|
198
|
-
async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
236
|
+
async function streamChatCompletion(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
|
|
199
237
|
(0, runtime_1.emitNervesEvent)({
|
|
200
238
|
component: "engine",
|
|
201
239
|
event: "engine.stream_start",
|
|
@@ -209,8 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
209
247
|
let toolCalls = {};
|
|
210
248
|
let streamStarted = false;
|
|
211
249
|
let usage;
|
|
212
|
-
const
|
|
213
|
-
let finalAnswerDetected = false;
|
|
250
|
+
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
214
251
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
215
252
|
let contentBuf = "";
|
|
216
253
|
let inThinkTag = false;
|
|
@@ -325,24 +362,21 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
325
362
|
toolCalls[tc.index].id = tc.id;
|
|
326
363
|
if (tc.function?.name) {
|
|
327
364
|
toolCalls[tc.index].name = tc.function.name;
|
|
328
|
-
// Detect
|
|
365
|
+
// Detect settle tool call on first name delta.
|
|
329
366
|
// Only activate streaming if this is the sole tool call (index 0
|
|
330
367
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
331
|
-
if (tc.function.name === "
|
|
368
|
+
if (tc.function.name === "settle" && !answerStreamer.detected
|
|
332
369
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
333
|
-
|
|
334
|
-
callbacks.onClearText?.();
|
|
370
|
+
answerStreamer.activate();
|
|
335
371
|
}
|
|
336
372
|
}
|
|
337
373
|
if (tc.function?.arguments) {
|
|
338
374
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
339
|
-
// Feed
|
|
375
|
+
// Feed settle argument deltas to the parser for progressive
|
|
340
376
|
// streaming, but only when it appears to be the sole tool call.
|
|
341
|
-
if (
|
|
377
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "settle"
|
|
342
378
|
&& Object.keys(toolCalls).length === 1) {
|
|
343
|
-
|
|
344
|
-
if (text)
|
|
345
|
-
callbacks.onTextChunk(text);
|
|
379
|
+
answerStreamer.processDelta(tc.function.arguments);
|
|
346
380
|
}
|
|
347
381
|
}
|
|
348
382
|
}
|
|
@@ -356,10 +390,10 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
356
390
|
toolCalls: Object.values(toolCalls),
|
|
357
391
|
outputItems: [],
|
|
358
392
|
usage,
|
|
359
|
-
|
|
393
|
+
settleStreamed: answerStreamer.streamed,
|
|
360
394
|
};
|
|
361
395
|
}
|
|
362
|
-
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
396
|
+
async function streamResponsesApi(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
|
|
363
397
|
(0, runtime_1.emitNervesEvent)({
|
|
364
398
|
component: "engine",
|
|
365
399
|
event: "engine.stream_start",
|
|
@@ -374,9 +408,8 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
374
408
|
const outputItems = [];
|
|
375
409
|
let currentToolCall = null;
|
|
376
410
|
let usage;
|
|
377
|
-
const
|
|
411
|
+
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
378
412
|
let functionCallCount = 0;
|
|
379
|
-
let finalAnswerDetected = false;
|
|
380
413
|
for await (const event of response) {
|
|
381
414
|
if (signal?.aborted)
|
|
382
415
|
break;
|
|
@@ -405,12 +438,11 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
405
438
|
name: String(event.item.name),
|
|
406
439
|
arguments: "",
|
|
407
440
|
};
|
|
408
|
-
// Detect
|
|
441
|
+
// Detect settle function call -- clear any streamed noise.
|
|
409
442
|
// Only activate when this is the first (and so far only) function call.
|
|
410
443
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
411
|
-
if (String(event.item.name) === "
|
|
412
|
-
|
|
413
|
-
callbacks.onClearText?.();
|
|
444
|
+
if (String(event.item.name) === "settle" && functionCallCount === 1) {
|
|
445
|
+
answerStreamer.activate();
|
|
414
446
|
}
|
|
415
447
|
}
|
|
416
448
|
break;
|
|
@@ -418,13 +450,11 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
418
450
|
case "response.function_call_arguments.delta": {
|
|
419
451
|
if (currentToolCall) {
|
|
420
452
|
currentToolCall.arguments += event.delta;
|
|
421
|
-
// Feed
|
|
453
|
+
// Feed settle argument deltas to the parser for progressive
|
|
422
454
|
// streaming, but only when it appears to be the sole function call.
|
|
423
|
-
if (
|
|
455
|
+
if (answerStreamer.detected && currentToolCall.name === "settle"
|
|
424
456
|
&& functionCallCount === 1) {
|
|
425
|
-
|
|
426
|
-
if (text)
|
|
427
|
-
callbacks.onTextChunk(text);
|
|
457
|
+
answerStreamer.processDelta(String(event.delta));
|
|
428
458
|
}
|
|
429
459
|
}
|
|
430
460
|
break;
|
|
@@ -464,6 +494,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
464
494
|
toolCalls,
|
|
465
495
|
outputItems,
|
|
466
496
|
usage,
|
|
467
|
-
|
|
497
|
+
settleStreamed: answerStreamer.streamed,
|
|
468
498
|
};
|
|
469
499
|
}
|