@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.131
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/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/changelog.json +814 -0
- package/dist/heart/active-work.js +622 -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 +66 -21
- package/dist/heart/core.js +518 -100
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1516 -195
- package/dist/heart/daemon/daemon-entry.js +43 -2
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +261 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -72
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +57 -29
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -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/sense-manager.js +50 -2
- 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 +7 -4
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- 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 +64 -21
- package/dist/heart/kicks.js +1 -19
- 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 +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/safe-workspace.js +381 -0
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +108 -24
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +194 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +12 -0
- package/dist/mind/context.js +60 -14
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -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/memory.js +27 -26
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +456 -77
- 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/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +39 -9
- package/dist/repertoire/coding/tools.js +171 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +925 -250
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- 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 +915 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +388 -83
- package/dist/senses/pipeline.js +444 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +9 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -0,0 +1,194 @@
|
|
|
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.GLOBAL_CIRCUIT_BREAKER_LIMIT = exports.PING_PONG_WINDOW = exports.GENERIC_REPEAT_LIMIT = exports.POLL_NO_PROGRESS_LIMIT = exports.TOOL_LOOP_HISTORY_LIMIT = void 0;
|
|
37
|
+
exports.createToolLoopState = createToolLoopState;
|
|
38
|
+
exports.recordToolOutcome = recordToolOutcome;
|
|
39
|
+
exports.detectToolLoop = detectToolLoop;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const runtime_1 = require("../nerves/runtime");
|
|
42
|
+
exports.TOOL_LOOP_HISTORY_LIMIT = 30;
|
|
43
|
+
exports.POLL_NO_PROGRESS_LIMIT = 3;
|
|
44
|
+
exports.GENERIC_REPEAT_LIMIT = 4;
|
|
45
|
+
exports.PING_PONG_WINDOW = 6;
|
|
46
|
+
exports.GLOBAL_CIRCUIT_BREAKER_LIMIT = 24;
|
|
47
|
+
function stableStringify(value) {
|
|
48
|
+
if (value === null || typeof value !== "object") {
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
}
|
|
51
|
+
/* v8 ignore next -- stableStringify currently receives only objects/strings from normalized loop inputs @preserve */
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
54
|
+
}
|
|
55
|
+
const record = value;
|
|
56
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
57
|
+
}
|
|
58
|
+
function digest(value) {
|
|
59
|
+
return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
60
|
+
}
|
|
61
|
+
function normalizeArgs(toolName, args) {
|
|
62
|
+
if (toolName === "query_active_work") {
|
|
63
|
+
return { toolName };
|
|
64
|
+
}
|
|
65
|
+
if (toolName === "coding_status" || toolName === "coding_tail") {
|
|
66
|
+
return {
|
|
67
|
+
toolName,
|
|
68
|
+
sessionId: args.sessionId ?? "",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (toolName === "query_session") {
|
|
72
|
+
return {
|
|
73
|
+
toolName,
|
|
74
|
+
mode: args.mode ?? "",
|
|
75
|
+
friendId: args.friendId ?? "",
|
|
76
|
+
channel: args.channel ?? "",
|
|
77
|
+
key: args.key ?? "",
|
|
78
|
+
query: args.query ?? "",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
toolName,
|
|
83
|
+
args,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function normalizeOutcome(result, success) {
|
|
87
|
+
return {
|
|
88
|
+
success,
|
|
89
|
+
result: result.replace(/\s+/g, " ").trim(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function isKnownPollTool(toolName, args) {
|
|
93
|
+
return toolName === "query_active_work"
|
|
94
|
+
|| toolName === "coding_status"
|
|
95
|
+
|| toolName === "coding_tail"
|
|
96
|
+
|| (toolName === "query_session" && (args.mode ?? "").trim() === "status");
|
|
97
|
+
}
|
|
98
|
+
function emitDetection(detector, toolName, count, message, pairedToolName) {
|
|
99
|
+
(0, runtime_1.emitNervesEvent)({
|
|
100
|
+
level: "warn",
|
|
101
|
+
component: "engine",
|
|
102
|
+
event: "engine.tool_loop_detected",
|
|
103
|
+
message: "tool loop guard detected repeated non-progress work",
|
|
104
|
+
meta: {
|
|
105
|
+
detector,
|
|
106
|
+
toolName,
|
|
107
|
+
count,
|
|
108
|
+
pairedToolName: pairedToolName ?? null,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
stuck: true,
|
|
113
|
+
detector,
|
|
114
|
+
count,
|
|
115
|
+
message,
|
|
116
|
+
pairedToolName,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function countTrailingRepeats(history, toolName, callHash) {
|
|
120
|
+
let count = 0;
|
|
121
|
+
let outcomeHash;
|
|
122
|
+
for (let index = history.length - 1; index >= 0; index--) {
|
|
123
|
+
const record = history[index];
|
|
124
|
+
if (record.toolName !== toolName || record.callHash !== callHash) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
if (!outcomeHash) {
|
|
128
|
+
outcomeHash = record.outcomeHash;
|
|
129
|
+
}
|
|
130
|
+
if (record.outcomeHash !== outcomeHash) {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
count += 1;
|
|
134
|
+
}
|
|
135
|
+
return { count, outcomeHash };
|
|
136
|
+
}
|
|
137
|
+
function detectPingPong(history, toolName, callHash) {
|
|
138
|
+
if (history.length < exports.PING_PONG_WINDOW) {
|
|
139
|
+
return { stuck: false };
|
|
140
|
+
}
|
|
141
|
+
const recent = history.slice(-exports.PING_PONG_WINDOW);
|
|
142
|
+
const first = recent[0];
|
|
143
|
+
const second = recent[1];
|
|
144
|
+
if (!first.isKnownPoll || !second.isKnownPoll) {
|
|
145
|
+
return { stuck: false };
|
|
146
|
+
}
|
|
147
|
+
if (first.toolName === second.toolName && first.callHash === second.callHash) {
|
|
148
|
+
return { stuck: false };
|
|
149
|
+
}
|
|
150
|
+
for (let index = 0; index < recent.length; index++) {
|
|
151
|
+
const expected = index % 2 === 0 ? first : second;
|
|
152
|
+
const actual = recent[index];
|
|
153
|
+
if (actual.toolName !== expected.toolName
|
|
154
|
+
|| actual.callHash !== expected.callHash
|
|
155
|
+
|| actual.outcomeHash !== expected.outcomeHash) {
|
|
156
|
+
return { stuck: false };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const matchesPair = (toolName === first.toolName && callHash === first.callHash)
|
|
160
|
+
|| (toolName === second.toolName && callHash === second.callHash);
|
|
161
|
+
if (!matchesPair) {
|
|
162
|
+
return { stuck: false };
|
|
163
|
+
}
|
|
164
|
+
const pairedToolName = toolName === first.toolName ? second.toolName : first.toolName;
|
|
165
|
+
return emitDetection("ping_pong", toolName, exports.PING_PONG_WINDOW, `repeated ${first.toolName}/${second.toolName} polling is not changing. stop cycling between status checks and either act on the current state, change approach, or answer truthfully with what is known.`, pairedToolName);
|
|
166
|
+
}
|
|
167
|
+
function createToolLoopState() {
|
|
168
|
+
return { history: [] };
|
|
169
|
+
}
|
|
170
|
+
function recordToolOutcome(state, toolName, args, result, success) {
|
|
171
|
+
state.history.push({
|
|
172
|
+
toolName,
|
|
173
|
+
callHash: digest(normalizeArgs(toolName, args)),
|
|
174
|
+
outcomeHash: digest(normalizeOutcome(result, success)),
|
|
175
|
+
isKnownPoll: isKnownPollTool(toolName, args),
|
|
176
|
+
});
|
|
177
|
+
if (state.history.length > exports.TOOL_LOOP_HISTORY_LIMIT) {
|
|
178
|
+
state.history.splice(0, state.history.length - exports.TOOL_LOOP_HISTORY_LIMIT);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function detectToolLoop(state, toolName, args) {
|
|
182
|
+
if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT) {
|
|
183
|
+
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.`);
|
|
184
|
+
}
|
|
185
|
+
const callHash = digest(normalizeArgs(toolName, args));
|
|
186
|
+
const trailing = countTrailingRepeats(state.history, toolName, callHash);
|
|
187
|
+
if (isKnownPollTool(toolName, args) && trailing.count >= exports.POLL_NO_PROGRESS_LIMIT) {
|
|
188
|
+
return emitDetection("known_poll_no_progress", toolName, trailing.count, `repeated ${toolName} calls have returned the same state ${trailing.count} times. stop polling and either act on the current state, wait for a meaningful change, or answer truthfully with what is known.`);
|
|
189
|
+
}
|
|
190
|
+
if (trailing.count >= exports.GENERIC_REPEAT_LIMIT) {
|
|
191
|
+
return emitDetection("generic_repeat", toolName, trailing.count, `repeating ${toolName} with the same inputs is not producing new information. change approach, use the evidence already gathered, or answer truthfully with what is known.`);
|
|
192
|
+
}
|
|
193
|
+
return detectPingPong(state.history, toolName, callHash);
|
|
194
|
+
}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createTurnCoordinator = createTurnCoordinator;
|
|
4
|
+
exports.withSharedTurnLock = withSharedTurnLock;
|
|
5
|
+
exports.tryBeginSharedTurn = tryBeginSharedTurn;
|
|
6
|
+
exports.endSharedTurn = endSharedTurn;
|
|
7
|
+
exports.isSharedTurnActive = isSharedTurnActive;
|
|
8
|
+
exports.enqueueSharedFollowUp = enqueueSharedFollowUp;
|
|
9
|
+
exports.drainSharedFollowUps = drainSharedFollowUps;
|
|
4
10
|
const runtime_1 = require("../nerves/runtime");
|
|
11
|
+
function scopedKey(scope, key) {
|
|
12
|
+
return `${scope}:${key}`;
|
|
13
|
+
}
|
|
5
14
|
function createTurnCoordinator() {
|
|
6
15
|
const turnLocks = new Map();
|
|
7
16
|
const activeTurns = new Set();
|
|
@@ -60,3 +69,22 @@ function createTurnCoordinator() {
|
|
|
60
69
|
},
|
|
61
70
|
};
|
|
62
71
|
}
|
|
72
|
+
const _sharedTurnCoordinator = createTurnCoordinator();
|
|
73
|
+
function withSharedTurnLock(scope, key, fn) {
|
|
74
|
+
return _sharedTurnCoordinator.withTurnLock(scopedKey(scope, key), fn);
|
|
75
|
+
}
|
|
76
|
+
function tryBeginSharedTurn(scope, key) {
|
|
77
|
+
return _sharedTurnCoordinator.tryBeginTurn(scopedKey(scope, key));
|
|
78
|
+
}
|
|
79
|
+
function endSharedTurn(scope, key) {
|
|
80
|
+
_sharedTurnCoordinator.endTurn(scopedKey(scope, key));
|
|
81
|
+
}
|
|
82
|
+
function isSharedTurnActive(scope, key) {
|
|
83
|
+
return _sharedTurnCoordinator.isTurnActive(scopedKey(scope, key));
|
|
84
|
+
}
|
|
85
|
+
function enqueueSharedFollowUp(scope, key, followUp) {
|
|
86
|
+
_sharedTurnCoordinator.enqueueFollowUp(scopedKey(scope, key), followUp);
|
|
87
|
+
}
|
|
88
|
+
function drainSharedFollowUps(scope, key) {
|
|
89
|
+
return _sharedTurnCoordinator.drainFollowUps(scopedKey(scope, key));
|
|
90
|
+
}
|
|
@@ -87,7 +87,19 @@ function readFacts(memoryRoot) {
|
|
|
87
87
|
const raw = fs.readFileSync(factsPath, "utf8").trim();
|
|
88
88
|
if (!raw)
|
|
89
89
|
return [];
|
|
90
|
-
|
|
90
|
+
const facts = [];
|
|
91
|
+
for (const line of raw.split("\n")) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
continue;
|
|
95
|
+
try {
|
|
96
|
+
facts.push(JSON.parse(trimmed));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Skip corrupt lines (e.g. partial write from a crash).
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return facts;
|
|
91
103
|
}
|
|
92
104
|
function getLatestUserText(messages) {
|
|
93
105
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -190,7 +202,7 @@ async function injectAssociativeRecall(messages, options) {
|
|
|
190
202
|
event: "mind.associative_recall_error",
|
|
191
203
|
message: "associative recall failed",
|
|
192
204
|
meta: {
|
|
193
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
205
|
+
reason: error instanceof Error ? error.message : /* v8 ignore start -- defensive: non-Error catch branch @preserve */ String(error) /* v8 ignore stop */,
|
|
194
206
|
},
|
|
195
207
|
});
|
|
196
208
|
}
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CANONICAL_BUNDLE_MANIFEST = void 0;
|
|
37
|
+
exports.getChangelogPath = getChangelogPath;
|
|
37
38
|
exports.getPackageVersion = getPackageVersion;
|
|
38
39
|
exports.createBundleMeta = createBundleMeta;
|
|
39
40
|
exports.backfillBundleMeta = backfillBundleMeta;
|
|
@@ -53,11 +54,22 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
|
|
|
53
54
|
{ path: "psyche/ASPIRATIONS.md", kind: "file" },
|
|
54
55
|
{ path: "psyche/memory", kind: "dir" },
|
|
55
56
|
{ path: "friends", kind: "dir" },
|
|
57
|
+
{ path: "state", kind: "dir" },
|
|
56
58
|
{ path: "tasks", kind: "dir" },
|
|
57
59
|
{ path: "skills", kind: "dir" },
|
|
58
60
|
{ path: "senses", kind: "dir" },
|
|
59
61
|
{ path: "senses/teams", kind: "dir" },
|
|
60
62
|
];
|
|
63
|
+
function getChangelogPath() {
|
|
64
|
+
const changelogPath = path.resolve(__dirname, "../../changelog.json");
|
|
65
|
+
(0, runtime_1.emitNervesEvent)({
|
|
66
|
+
component: "mind",
|
|
67
|
+
event: "mind.changelog_path_resolved",
|
|
68
|
+
message: "resolved changelog path",
|
|
69
|
+
meta: { path: changelogPath },
|
|
70
|
+
});
|
|
71
|
+
return changelogPath;
|
|
72
|
+
}
|
|
61
73
|
function getPackageVersion() {
|
|
62
74
|
const packageJsonPath = path.resolve(__dirname, "../../package.json");
|
|
63
75
|
const raw = fs.readFileSync(packageJsonPath, "utf-8");
|
package/dist/mind/context.js
CHANGED
|
@@ -50,17 +50,17 @@ function buildTrimmableBlocks(messages) {
|
|
|
50
50
|
let i = 0;
|
|
51
51
|
while (i < messages.length) {
|
|
52
52
|
const msg = messages[i];
|
|
53
|
-
if (msg
|
|
53
|
+
if (msg.role === "system") {
|
|
54
54
|
i++;
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
57
|
// Tool coherence block: assistant message with tool_calls + immediately following tool results
|
|
58
|
-
if (msg
|
|
58
|
+
if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
59
59
|
const indices = [i];
|
|
60
60
|
i++;
|
|
61
61
|
while (i < messages.length) {
|
|
62
62
|
const next = messages[i];
|
|
63
|
-
if (next
|
|
63
|
+
if (next.role !== "tool")
|
|
64
64
|
break;
|
|
65
65
|
indices.push(i);
|
|
66
66
|
i++;
|
|
@@ -78,13 +78,13 @@ function buildTrimmableBlocks(messages) {
|
|
|
78
78
|
function getSystemMessageIndices(messages) {
|
|
79
79
|
const indices = [];
|
|
80
80
|
for (let i = 0; i < messages.length; i++) {
|
|
81
|
-
if (messages[i]
|
|
81
|
+
if (messages[i].role === "system")
|
|
82
82
|
indices.push(i);
|
|
83
83
|
}
|
|
84
84
|
return indices;
|
|
85
85
|
}
|
|
86
86
|
function buildTrimmedMessages(messages, kept) {
|
|
87
|
-
return messages.filter((m, idx) => m
|
|
87
|
+
return messages.filter((m, idx) => m.role === "system" || kept.has(idx));
|
|
88
88
|
}
|
|
89
89
|
function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
90
90
|
const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
|
|
@@ -132,7 +132,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
132
132
|
let remaining = actualTokenCount;
|
|
133
133
|
const kept = new Set();
|
|
134
134
|
for (let i = 0; i < messages.length; i++) {
|
|
135
|
-
if (messages[i]
|
|
135
|
+
if (messages[i].role !== "system")
|
|
136
136
|
kept.add(i);
|
|
137
137
|
}
|
|
138
138
|
// Drop oldest blocks until we fall under target.
|
|
@@ -146,7 +146,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
146
146
|
let trimmed = buildTrimmedMessages(messages, kept);
|
|
147
147
|
// If we're still above budget after dropping everything trimmable, preserve system only.
|
|
148
148
|
if (remaining > targetTokens) {
|
|
149
|
-
trimmed = messages.filter((m) => m
|
|
149
|
+
trimmed = messages.filter((m) => m.role === "system");
|
|
150
150
|
}
|
|
151
151
|
const estimatedAfter = (0, token_estimate_1.estimateTokensForMessages)(trimmed);
|
|
152
152
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -219,7 +219,7 @@ function repairSessionMessages(messages) {
|
|
|
219
219
|
result.push(msg);
|
|
220
220
|
}
|
|
221
221
|
(0, runtime_1.emitNervesEvent)({
|
|
222
|
-
level: "
|
|
222
|
+
level: "info",
|
|
223
223
|
event: "mind.session_invariant_repair",
|
|
224
224
|
component: "mind",
|
|
225
225
|
message: "repaired session invariant violations",
|
|
@@ -227,11 +227,39 @@ function repairSessionMessages(messages) {
|
|
|
227
227
|
});
|
|
228
228
|
return result;
|
|
229
229
|
}
|
|
230
|
-
function
|
|
230
|
+
function stripOrphanedToolResults(messages) {
|
|
231
|
+
const validCallIds = new Set();
|
|
232
|
+
for (const msg of messages) {
|
|
233
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
234
|
+
continue;
|
|
235
|
+
for (const toolCall of msg.tool_calls)
|
|
236
|
+
validCallIds.add(toolCall.id);
|
|
237
|
+
}
|
|
238
|
+
let removed = 0;
|
|
239
|
+
const repaired = messages.filter((msg) => {
|
|
240
|
+
if (msg.role !== "tool")
|
|
241
|
+
return true;
|
|
242
|
+
const keep = validCallIds.has(msg.tool_call_id);
|
|
243
|
+
if (!keep)
|
|
244
|
+
removed++;
|
|
245
|
+
return keep;
|
|
246
|
+
});
|
|
247
|
+
if (removed > 0) {
|
|
248
|
+
(0, runtime_1.emitNervesEvent)({
|
|
249
|
+
level: "info",
|
|
250
|
+
event: "mind.session_orphan_tool_result_repair",
|
|
251
|
+
component: "mind",
|
|
252
|
+
message: "removed orphaned tool results from session history",
|
|
253
|
+
meta: { removed },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return repaired;
|
|
257
|
+
}
|
|
258
|
+
function saveSession(filePath, messages, lastUsage, state) {
|
|
231
259
|
const violations = validateSessionMessages(messages);
|
|
232
260
|
if (violations.length > 0) {
|
|
233
261
|
(0, runtime_1.emitNervesEvent)({
|
|
234
|
-
level: "
|
|
262
|
+
level: "info",
|
|
235
263
|
event: "mind.session_invariant_violation",
|
|
236
264
|
component: "mind",
|
|
237
265
|
message: "session invariant violated on save",
|
|
@@ -239,10 +267,17 @@ function saveSession(filePath, messages, lastUsage) {
|
|
|
239
267
|
});
|
|
240
268
|
messages = repairSessionMessages(messages);
|
|
241
269
|
}
|
|
270
|
+
messages = stripOrphanedToolResults(messages);
|
|
242
271
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
243
272
|
const envelope = { version: 1, messages };
|
|
244
273
|
if (lastUsage)
|
|
245
274
|
envelope.lastUsage = lastUsage;
|
|
275
|
+
if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
|
|
276
|
+
envelope.state = {
|
|
277
|
+
...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
278
|
+
...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
246
281
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
247
282
|
}
|
|
248
283
|
function loadSession(filePath) {
|
|
@@ -255,7 +290,7 @@ function loadSession(filePath) {
|
|
|
255
290
|
const violations = validateSessionMessages(messages);
|
|
256
291
|
if (violations.length > 0) {
|
|
257
292
|
(0, runtime_1.emitNervesEvent)({
|
|
258
|
-
level: "
|
|
293
|
+
level: "info",
|
|
259
294
|
event: "mind.session_invariant_violation",
|
|
260
295
|
component: "mind",
|
|
261
296
|
message: "session invariant violated on load",
|
|
@@ -263,13 +298,24 @@ function loadSession(filePath) {
|
|
|
263
298
|
});
|
|
264
299
|
messages = repairSessionMessages(messages);
|
|
265
300
|
}
|
|
266
|
-
|
|
301
|
+
messages = stripOrphanedToolResults(messages);
|
|
302
|
+
const rawState = data?.state && typeof data.state === "object" && data.state !== null
|
|
303
|
+
? data.state
|
|
304
|
+
: undefined;
|
|
305
|
+
const state = rawState && (rawState.mustResolveBeforeHandoff === true
|
|
306
|
+
|| typeof rawState.lastFriendActivityAt === "string")
|
|
307
|
+
? {
|
|
308
|
+
...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
309
|
+
...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
|
|
310
|
+
}
|
|
311
|
+
: undefined;
|
|
312
|
+
return { messages, lastUsage: data.lastUsage, state };
|
|
267
313
|
}
|
|
268
314
|
catch {
|
|
269
315
|
return null;
|
|
270
316
|
}
|
|
271
317
|
}
|
|
272
|
-
function postTurn(messages, sessPath, usage, hooks) {
|
|
318
|
+
function postTurn(messages, sessPath, usage, hooks, state) {
|
|
273
319
|
if (hooks?.beforeTrim) {
|
|
274
320
|
try {
|
|
275
321
|
hooks.beforeTrim([...messages]);
|
|
@@ -289,7 +335,7 @@ function postTurn(messages, sessPath, usage, hooks) {
|
|
|
289
335
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
290
336
|
const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
|
|
291
337
|
messages.splice(0, messages.length, ...trimmed);
|
|
292
|
-
saveSession(sessPath, messages, usage);
|
|
338
|
+
saveSession(sessPath, messages, usage, state);
|
|
293
339
|
}
|
|
294
340
|
function deleteSession(filePath) {
|
|
295
341
|
try {
|
|
@@ -11,9 +11,22 @@ exports.ONBOARDING_TOKEN_THRESHOLD = 100_000;
|
|
|
11
11
|
function isOnboarding(friend) {
|
|
12
12
|
return (friend.totalTokens ?? 0) < exports.ONBOARDING_TOKEN_THRESHOLD;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function hasLiveContinuityPressure(state) {
|
|
15
|
+
if (!state)
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof state.currentObligation === "string" && state.currentObligation.trim().length > 0)
|
|
18
|
+
return true;
|
|
19
|
+
if (state.mustResolveBeforeHandoff === true)
|
|
20
|
+
return true;
|
|
21
|
+
if (state.hasQueuedFollowUp === true)
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function getFirstImpressions(friend, state) {
|
|
15
26
|
if (!isOnboarding(friend))
|
|
16
27
|
return "";
|
|
28
|
+
if (hasLiveContinuityPressure(state))
|
|
29
|
+
return "";
|
|
17
30
|
(0, runtime_1.emitNervesEvent)({
|
|
18
31
|
component: "mind",
|
|
19
32
|
event: "mind.first_impressions",
|
|
@@ -37,7 +50,8 @@ function getFirstImpressions(friend) {
|
|
|
37
50
|
lines.push("- what do they do outside of work that they care about?");
|
|
38
51
|
lines.push("i don't ask all of these at once -- i weave them into conversation naturally, one or two at a time, and i genuinely follow up on what they share.");
|
|
39
52
|
lines.push("i introduce what i can do -- i have tools, integrations, and skills that can help them. i mention these naturally as they become relevant.");
|
|
40
|
-
lines.push("if
|
|
53
|
+
lines.push("if we're already in motion on a task, thread, or follow-up, i do not reset with a generic opener like 'hiya' or 'what do ya need help with?'. i continue directly or ask the specific next question.");
|
|
54
|
+
lines.push("only when the conversation is genuinely fresh and idle, with no active ask or thread in flight, a light opener is okay.");
|
|
41
55
|
lines.push("i save everything i learn immediately with save_friend_note -- names, roles, preferences, projects, anything. the bar is low: if i learned it, i save it.");
|
|
42
56
|
return lines.join("\n");
|
|
43
57
|
}
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
6
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
7
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
8
|
const runtime_1 = require("../../nerves/runtime");
|
|
7
9
|
const CHANNEL_CAPABILITIES = {
|
|
8
10
|
cli: {
|
|
9
11
|
channel: "cli",
|
|
12
|
+
senseType: "local",
|
|
10
13
|
availableIntegrations: [],
|
|
11
14
|
supportsMarkdown: false,
|
|
12
15
|
supportsStreaming: true,
|
|
@@ -15,6 +18,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
18
|
},
|
|
16
19
|
teams: {
|
|
17
20
|
channel: "teams",
|
|
21
|
+
senseType: "closed",
|
|
18
22
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
23
|
supportsMarkdown: true,
|
|
20
24
|
supportsStreaming: true,
|
|
@@ -23,15 +27,26 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
27
|
},
|
|
24
28
|
bluebubbles: {
|
|
25
29
|
channel: "bluebubbles",
|
|
30
|
+
senseType: "open",
|
|
26
31
|
availableIntegrations: [],
|
|
27
32
|
supportsMarkdown: false,
|
|
28
33
|
supportsStreaming: false,
|
|
29
34
|
supportsRichCards: false,
|
|
30
35
|
maxMessageLength: Infinity,
|
|
31
36
|
},
|
|
37
|
+
inner: {
|
|
38
|
+
channel: "inner",
|
|
39
|
+
senseType: "internal",
|
|
40
|
+
availableIntegrations: [],
|
|
41
|
+
supportsMarkdown: false,
|
|
42
|
+
supportsStreaming: true,
|
|
43
|
+
supportsRichCards: false,
|
|
44
|
+
maxMessageLength: Infinity,
|
|
45
|
+
},
|
|
32
46
|
};
|
|
33
47
|
const DEFAULT_CAPABILITIES = {
|
|
34
48
|
channel: "cli",
|
|
49
|
+
senseType: "local",
|
|
35
50
|
availableIntegrations: [],
|
|
36
51
|
supportsMarkdown: false,
|
|
37
52
|
supportsStreaming: false,
|
|
@@ -47,3 +62,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
62
|
});
|
|
48
63
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
64
|
}
|
|
65
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
66
|
+
function isRemoteChannel(capabilities) {
|
|
67
|
+
const senseType = capabilities?.senseType;
|
|
68
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
72
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
73
|
+
*/
|
|
74
|
+
function getAlwaysOnSenseNames() {
|
|
75
|
+
(0, runtime_1.emitNervesEvent)({
|
|
76
|
+
component: "channels",
|
|
77
|
+
event: "channel.always_on_lookup",
|
|
78
|
+
message: "always-on sense names lookup",
|
|
79
|
+
meta: {},
|
|
80
|
+
});
|
|
81
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
82
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
83
|
+
.map(([channel]) => channel);
|
|
84
|
+
}
|