@ouro.bot/cli 0.1.0-alpha.109 → 0.1.0-alpha.110
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 +7 -0
- package/dist/heart/active-work.js +21 -6
- package/dist/heart/core.js +13 -0
- package/dist/heart/session-recall.js +107 -7
- package/dist/heart/tool-loop.js +194 -0
- package/dist/mind/prompt.js +1 -0
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +28 -17
- package/dist/repertoire/coding/manager.js +141 -0
- package/dist/repertoire/coding/spawner.js +18 -6
- package/dist/repertoire/coding/tools.js +18 -3
- package/dist/repertoire/tools-base.js +42 -2
- package/dist/senses/bluebubbles.js +7 -1
- package/dist/senses/cli.js +7 -1
- package/dist/senses/inner-dialog.js +4 -1
- package/dist/senses/teams.js +5 -1
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.110",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Pi-style capability salvage now lands fully inside the active-work-first architecture: coding lanes inherit a live world-state checkpoint, session recall stays evidence-based, and no parallel session-orientation self-model remains.",
|
|
8
|
+
"Repeated no-progress polling is now blocked across active-work, coding, and session status surfaces, while coding checkpoints/artifacts persist cleanly enough for the same agent to resume real work instead of narrating around it."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.109",
|
|
6
13
|
"changes": [
|
|
@@ -39,6 +39,12 @@ function hasSharedObligationPressure(input) {
|
|
|
39
39
|
function formatCodingLaneLabel(session) {
|
|
40
40
|
return `${session.runner} ${session.id}`;
|
|
41
41
|
}
|
|
42
|
+
function compactCodingCheckpoint(session) {
|
|
43
|
+
const checkpoint = session.checkpoint?.replace(/\s+/g, " ").trim();
|
|
44
|
+
if (!checkpoint)
|
|
45
|
+
return "";
|
|
46
|
+
return checkpoint.length <= 80 ? checkpoint : `${checkpoint.slice(0, 77)}...`;
|
|
47
|
+
}
|
|
42
48
|
function describeCodingSessionScope(session, currentSession) {
|
|
43
49
|
if (!session.originSession)
|
|
44
50
|
return "";
|
|
@@ -159,6 +165,12 @@ function formatActiveLane(frame, obligation) {
|
|
|
159
165
|
}
|
|
160
166
|
return null;
|
|
161
167
|
}
|
|
168
|
+
function formatCodingArtifact(session) {
|
|
169
|
+
const artifactPath = session?.artifactPath?.trim();
|
|
170
|
+
if (artifactPath)
|
|
171
|
+
return artifactPath;
|
|
172
|
+
return session ? "no PR or merge artifact yet" : null;
|
|
173
|
+
}
|
|
162
174
|
function formatCurrentArtifact(frame, obligation) {
|
|
163
175
|
if (obligation?.currentArtifact?.trim()) {
|
|
164
176
|
return obligation.currentArtifact.trim();
|
|
@@ -166,8 +178,9 @@ function formatCurrentArtifact(frame, obligation) {
|
|
|
166
178
|
if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
|
|
167
179
|
return obligation.currentSurface.label.trim();
|
|
168
180
|
}
|
|
169
|
-
|
|
170
|
-
|
|
181
|
+
const liveCodingArtifact = formatCodingArtifact(frame.codingSessions?.[0]);
|
|
182
|
+
if (liveCodingArtifact) {
|
|
183
|
+
return liveCodingArtifact;
|
|
171
184
|
}
|
|
172
185
|
if (obligation) {
|
|
173
186
|
return "no artifact yet";
|
|
@@ -232,8 +245,9 @@ function formatOtherSessionArtifact(obligation, codingSession) {
|
|
|
232
245
|
if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
|
|
233
246
|
return obligation.currentSurface.label.trim();
|
|
234
247
|
}
|
|
235
|
-
|
|
236
|
-
|
|
248
|
+
const codingArtifact = formatCodingArtifact(codingSession);
|
|
249
|
+
if (codingArtifact)
|
|
250
|
+
return codingArtifact;
|
|
237
251
|
return obligation ? "no artifact yet" : "no explicit artifact yet";
|
|
238
252
|
}
|
|
239
253
|
function formatOtherSessionNextAction(obligation, codingSession) {
|
|
@@ -278,7 +292,7 @@ function formatOtherActiveSessionSummaries(frame, nowMs = Date.now()) {
|
|
|
278
292
|
.sort((left, right) => codingSessionTimestampMs(right) - codingSessionTimestampMs(left))
|
|
279
293
|
.map((session) => ({
|
|
280
294
|
timestampMs: codingSessionTimestampMs(session),
|
|
281
|
-
line: formatOtherSessionLine("another session", session.status, formatCodingLaneLabel(session),
|
|
295
|
+
line: formatOtherSessionLine("another session", session.status, formatCodingLaneLabel(session), formatCodingArtifact(session), formatOtherSessionNextAction(null, session)),
|
|
282
296
|
}));
|
|
283
297
|
for (const session of frame.otherCodingSessions ?? []) {
|
|
284
298
|
if (!session.originSession)
|
|
@@ -533,7 +547,8 @@ function formatActiveWorkFrame(frame) {
|
|
|
533
547
|
lines.push("");
|
|
534
548
|
lines.push("## live coding work");
|
|
535
549
|
for (const session of frame.codingSessions) {
|
|
536
|
-
|
|
550
|
+
const checkpoint = compactCodingCheckpoint(session);
|
|
551
|
+
lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)}${describeCodingSessionScope(session, frame.currentSession)}${checkpoint ? `: ${checkpoint}` : ""}`);
|
|
537
552
|
}
|
|
538
553
|
}
|
|
539
554
|
if (otherActiveSessions.length > 0) {
|
package/dist/heart/core.js
CHANGED
|
@@ -31,6 +31,7 @@ const pending_1 = require("../mind/pending");
|
|
|
31
31
|
const identity_2 = require("./identity");
|
|
32
32
|
const socket_client_1 = require("./daemon/socket-client");
|
|
33
33
|
const obligations_1 = require("./obligations");
|
|
34
|
+
const tool_loop_1 = require("./tool-loop");
|
|
34
35
|
let _providerRuntime = null;
|
|
35
36
|
function getProviderRuntimeFingerprint() {
|
|
36
37
|
const provider = (0, identity_1.loadAgentConfig)().provider;
|
|
@@ -490,6 +491,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
490
491
|
let sawQuerySession = false;
|
|
491
492
|
let sawBridgeManage = false;
|
|
492
493
|
let sawExternalStateQuery = false;
|
|
494
|
+
const toolLoopState = (0, tool_loop_1.createToolLoopState)();
|
|
493
495
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
494
496
|
try {
|
|
495
497
|
require("events").setMaxListeners(50, signal);
|
|
@@ -503,6 +505,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
503
505
|
...options.toolContext,
|
|
504
506
|
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
505
507
|
setReasoningEffort: (level) => { currentReasoningEffort = level; },
|
|
508
|
+
activeWorkFrame: options?.activeWorkFrame,
|
|
506
509
|
}
|
|
507
510
|
: undefined;
|
|
508
511
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
@@ -804,6 +807,15 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
804
807
|
if (isExternalStateQuery(tc.name, args))
|
|
805
808
|
sawExternalStateQuery = true;
|
|
806
809
|
const argSummary = (0, tools_1.summarizeArgs)(tc.name, args);
|
|
810
|
+
const toolLoop = (0, tool_loop_1.detectToolLoop)(toolLoopState, tc.name, args);
|
|
811
|
+
if (toolLoop.stuck) {
|
|
812
|
+
const rejection = `loop guard: ${toolLoop.message}`;
|
|
813
|
+
callbacks.onToolStart(tc.name, args);
|
|
814
|
+
callbacks.onToolEnd(tc.name, argSummary, false);
|
|
815
|
+
messages.push({ role: "tool", tool_call_id: tc.id, content: rejection });
|
|
816
|
+
providerRuntime.appendToolOutput(tc.id, rejection);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
807
819
|
// Confirmation check for mutate tools
|
|
808
820
|
if ((0, tools_1.isConfirmationRequired)(tc.name) && !options?.skipConfirmation) {
|
|
809
821
|
let decision = "denied";
|
|
@@ -831,6 +843,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
831
843
|
toolResult = `error: ${e}`;
|
|
832
844
|
success = false;
|
|
833
845
|
}
|
|
846
|
+
(0, tool_loop_1.recordToolOutcome)(toolLoopState, tc.name, args, toolResult, success);
|
|
834
847
|
callbacks.onToolEnd(tc.name, argSummary, success);
|
|
835
848
|
messages.push({ role: "tool", tool_call_id: tc.id, content: toolResult });
|
|
836
849
|
providerRuntime.appendToolOutput(tc.id, toolResult);
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.recallSession = recallSession;
|
|
37
|
+
exports.searchSessionTranscript = searchSessionTranscript;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const runtime_1 = require("../nerves/runtime");
|
|
39
40
|
function normalizeContent(content) {
|
|
@@ -48,6 +49,19 @@ function normalizeContent(content) {
|
|
|
48
49
|
.filter((text) => text.length > 0)
|
|
49
50
|
.join("");
|
|
50
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
|
+
}
|
|
51
65
|
function buildSummaryInstruction(friendId, channel, trustLevel) {
|
|
52
66
|
if (friendId === "self" && channel === "inner") {
|
|
53
67
|
return "summarize this session transcript fully and transparently. this is my own inner dialog — include all details, decisions, and reasoning.";
|
|
@@ -70,6 +84,57 @@ function buildSnapshot(summary, tailMessages) {
|
|
|
70
84
|
}
|
|
71
85
|
return lines.join("\n");
|
|
72
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
|
+
}
|
|
73
138
|
async function recallSession(options) {
|
|
74
139
|
(0, runtime_1.emitNervesEvent)({
|
|
75
140
|
component: "daemon",
|
|
@@ -90,13 +155,7 @@ async function recallSession(options) {
|
|
|
90
155
|
return { kind: "missing" };
|
|
91
156
|
}
|
|
92
157
|
const parsed = JSON.parse(raw);
|
|
93
|
-
const tailMessages = (parsed.messages
|
|
94
|
-
.map((message) => ({
|
|
95
|
-
role: typeof message.role === "string" ? message.role : "",
|
|
96
|
-
content: normalizeContent(message.content),
|
|
97
|
-
}))
|
|
98
|
-
.filter((message) => message.role !== "system" && message.content.length > 0)
|
|
99
|
-
.slice(-options.messageCount);
|
|
158
|
+
const tailMessages = normalizeSessionMessages(parsed.messages).slice(-options.messageCount);
|
|
100
159
|
if (tailMessages.length === 0) {
|
|
101
160
|
return { kind: "empty" };
|
|
102
161
|
}
|
|
@@ -114,3 +173,44 @@ async function recallSession(options) {
|
|
|
114
173
|
tailMessages,
|
|
115
174
|
};
|
|
116
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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -437,6 +437,7 @@ function memoryFriendToolContractSection() {
|
|
|
437
437
|
2. \`memory_save\` — When I learn something general - about a project, codebase, system, decision, or anything I might need later that isn't about a specific person - I call \`memory_save\`. When in doubt, I save it.
|
|
438
438
|
3. \`get_friend_note\` — When I need to check what I know about someone who isn't in this conversation - cross-referencing before mentioning someone, or checking context about a person someone else brought up - I call \`get_friend_note\`.
|
|
439
439
|
4. \`memory_search\` — When I need to recall something I learned before - a topic comes up and I want to check what I know - I call \`memory_search\`.
|
|
440
|
+
5. \`query_session\` — When I need grounded session history, especially for ad-hoc questions or older turns beyond my prompt, I call \`query_session\`. Use \`mode=status\` for self/inner progress and \`mode=search\` with a query for older history.
|
|
440
441
|
|
|
441
442
|
## what's already in my context
|
|
442
443
|
- My active friend's notes are auto-loaded (I don't need \`get_friend_note\` for the person I'm talking to).
|
|
@@ -0,0 +1,254 @@
|
|
|
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.prepareCodingContextPack = prepareCodingContextPack;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const active_work_1 = require("../../heart/active-work");
|
|
42
|
+
const identity_1 = require("../../heart/identity");
|
|
43
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
44
|
+
const skills_1 = require("../skills");
|
|
45
|
+
const CONTEXT_FILENAMES = ["AGENTS.md", "CLAUDE.md"];
|
|
46
|
+
function defaultRunCommand(command, args, cwd) {
|
|
47
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
48
|
+
cwd,
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
status: result.status ?? 1,
|
|
53
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
54
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function stableContextKey(request) {
|
|
58
|
+
const payload = JSON.stringify({
|
|
59
|
+
runner: request.runner,
|
|
60
|
+
workdir: request.workdir,
|
|
61
|
+
taskRef: request.taskRef ?? "",
|
|
62
|
+
parentAgent: request.parentAgent ?? "",
|
|
63
|
+
obligationId: request.obligationId ?? "",
|
|
64
|
+
originSession: request.originSession ?? null,
|
|
65
|
+
});
|
|
66
|
+
return crypto.createHash("sha1").update(payload).digest("hex").slice(0, 12);
|
|
67
|
+
}
|
|
68
|
+
function collectProjectContextFiles(workdir, deps) {
|
|
69
|
+
const files = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
let current = path.resolve(workdir);
|
|
72
|
+
const root = path.parse(current).root;
|
|
73
|
+
while (true) {
|
|
74
|
+
for (const filename of CONTEXT_FILENAMES) {
|
|
75
|
+
const candidate = path.join(current, filename);
|
|
76
|
+
if (!deps.existsSync(candidate) || seen.has(candidate))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
const content = deps.readFileSync(candidate, "utf-8").trim();
|
|
80
|
+
if (content.length > 0) {
|
|
81
|
+
files.unshift({ path: candidate, content });
|
|
82
|
+
seen.add(candidate);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Best-effort loading only.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (current === root)
|
|
90
|
+
break;
|
|
91
|
+
current = path.dirname(current);
|
|
92
|
+
}
|
|
93
|
+
return files;
|
|
94
|
+
}
|
|
95
|
+
function captureRepoSnapshot(workdir, runCommand) {
|
|
96
|
+
const repoRoot = runCommand("git", ["rev-parse", "--show-toplevel"], workdir);
|
|
97
|
+
if (repoRoot.status !== 0) {
|
|
98
|
+
return {
|
|
99
|
+
available: false,
|
|
100
|
+
repoRoot: null,
|
|
101
|
+
branch: null,
|
|
102
|
+
head: null,
|
|
103
|
+
statusLines: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const branch = runCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], workdir);
|
|
107
|
+
const head = runCommand("git", ["rev-parse", "--short", "HEAD"], workdir);
|
|
108
|
+
const status = runCommand("git", ["status", "--short"], workdir);
|
|
109
|
+
return {
|
|
110
|
+
available: true,
|
|
111
|
+
repoRoot: repoRoot.stdout.trim() || null,
|
|
112
|
+
branch: branch.status === 0 ? branch.stdout.trim() || null : null,
|
|
113
|
+
head: head.status === 0 ? head.stdout.trim() || null : null,
|
|
114
|
+
statusLines: status.status === 0
|
|
115
|
+
? status.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean)
|
|
116
|
+
: [],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function formatContextFiles(files) {
|
|
120
|
+
if (files.length === 0)
|
|
121
|
+
return "(none found)";
|
|
122
|
+
return files.map((file) => `### ${file.path}\n${file.content}`).join("\n\n");
|
|
123
|
+
}
|
|
124
|
+
function formatSkills(skills) {
|
|
125
|
+
return skills.length > 0 ? skills.join(", ") : "(none found)";
|
|
126
|
+
}
|
|
127
|
+
function formatExistingSessions(sessions) {
|
|
128
|
+
if (sessions.length === 0)
|
|
129
|
+
return "activeSessions: none";
|
|
130
|
+
return sessions
|
|
131
|
+
.map((session) => {
|
|
132
|
+
return [
|
|
133
|
+
`- ${session.id}`,
|
|
134
|
+
`status=${session.status}`,
|
|
135
|
+
`lastActivityAt=${session.lastActivityAt}`,
|
|
136
|
+
session.taskRef ? `taskRef=${session.taskRef}` : null,
|
|
137
|
+
session.checkpoint ? `checkpoint=${session.checkpoint}` : null,
|
|
138
|
+
session.artifactPath ? `artifact=${session.artifactPath}` : null,
|
|
139
|
+
].filter(Boolean).join(" ");
|
|
140
|
+
})
|
|
141
|
+
.join("\n");
|
|
142
|
+
}
|
|
143
|
+
function formatOrigin(request) {
|
|
144
|
+
if (!request.originSession)
|
|
145
|
+
return "originSession: none";
|
|
146
|
+
return `originSession: ${request.originSession.channel}/${request.originSession.key} (${request.originSession.friendId})`;
|
|
147
|
+
}
|
|
148
|
+
function buildScopeContent(request, contextFiles, skills, agentName) {
|
|
149
|
+
return [
|
|
150
|
+
"# Coding Session Scope",
|
|
151
|
+
"",
|
|
152
|
+
"## Request",
|
|
153
|
+
`runner: ${request.runner}`,
|
|
154
|
+
`taskRef: ${request.taskRef ?? "unassigned"}`,
|
|
155
|
+
`parentAgent: ${request.parentAgent ?? agentName}`,
|
|
156
|
+
`workdir: ${request.workdir}`,
|
|
157
|
+
formatOrigin(request),
|
|
158
|
+
`obligationId: ${request.obligationId ?? "none"}`,
|
|
159
|
+
"",
|
|
160
|
+
"## Prompt",
|
|
161
|
+
request.prompt,
|
|
162
|
+
"",
|
|
163
|
+
"## Session Contract",
|
|
164
|
+
"- This is a focused coding lane opened by the parent Ouro agent.",
|
|
165
|
+
"- Execute the concrete prompt in the supplied workdir directly.",
|
|
166
|
+
"- Do not switch into planning/doing workflows or approval gates unless the prompt explicitly asks for them.",
|
|
167
|
+
"- Treat the current prompt, scope file, and live world-state checkpoint in the state file as the authoritative briefing for this lane.",
|
|
168
|
+
"",
|
|
169
|
+
"## Project Context Files",
|
|
170
|
+
formatContextFiles(contextFiles),
|
|
171
|
+
"",
|
|
172
|
+
"## Available Bundle Skills",
|
|
173
|
+
formatSkills(skills),
|
|
174
|
+
].join("\n");
|
|
175
|
+
}
|
|
176
|
+
function buildStateContent(request, contextKey, generatedAt, snapshot, existingSessions, agentName, activeWorkFrame) {
|
|
177
|
+
const gitSection = snapshot.available
|
|
178
|
+
? [
|
|
179
|
+
`repoRoot: ${snapshot.repoRoot ?? "unknown"}`,
|
|
180
|
+
`branch: ${snapshot.branch ?? "unknown"}`,
|
|
181
|
+
`head: ${snapshot.head ?? "unknown"}`,
|
|
182
|
+
"status:",
|
|
183
|
+
snapshot.statusLines.length > 0 ? snapshot.statusLines.join("\n") : "(clean)",
|
|
184
|
+
].join("\n")
|
|
185
|
+
: "git: unavailable";
|
|
186
|
+
return [
|
|
187
|
+
"# Coding Session State",
|
|
188
|
+
`generatedAt: ${generatedAt}`,
|
|
189
|
+
`contextKey: ${contextKey}`,
|
|
190
|
+
`agent: ${request.parentAgent ?? agentName}`,
|
|
191
|
+
formatOrigin(request),
|
|
192
|
+
`obligationId: ${request.obligationId ?? "none"}`,
|
|
193
|
+
"",
|
|
194
|
+
"## Workspace Snapshot",
|
|
195
|
+
gitSection,
|
|
196
|
+
...(activeWorkFrame ? ["", (0, active_work_1.formatLiveWorldStateCheckpoint)(activeWorkFrame)] : []),
|
|
197
|
+
"",
|
|
198
|
+
"## Related Coding Sessions",
|
|
199
|
+
formatExistingSessions(existingSessions),
|
|
200
|
+
].join("\n");
|
|
201
|
+
}
|
|
202
|
+
function relatedSessions(request, existingSessions) {
|
|
203
|
+
return existingSessions.filter((session) => {
|
|
204
|
+
return session.runner === request.runner
|
|
205
|
+
&& session.workdir === request.workdir
|
|
206
|
+
&& session.taskRef === request.taskRef;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function prepareCodingContextPack(input, deps = {}) {
|
|
210
|
+
const agentRoot = deps.agentRoot ?? (0, identity_1.getAgentRoot)();
|
|
211
|
+
const agentName = deps.agentName ?? (0, identity_1.getAgentName)();
|
|
212
|
+
const nowIso = deps.nowIso ?? (() => new Date().toISOString());
|
|
213
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
214
|
+
const readFileSync = deps.readFileSync ?? fs.readFileSync;
|
|
215
|
+
const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
|
|
216
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
217
|
+
const listAvailableSkills = deps.listSkills ?? skills_1.listSkills;
|
|
218
|
+
const runCommand = deps.runCommand ?? defaultRunCommand;
|
|
219
|
+
const contextKey = stableContextKey(input.request);
|
|
220
|
+
const contextDir = path.join(agentRoot, "state", "coding", "context");
|
|
221
|
+
const scopeFile = path.join(contextDir, `${contextKey}-scope.md`);
|
|
222
|
+
const stateFile = path.join(contextDir, `${contextKey}-state.md`);
|
|
223
|
+
const contextFiles = collectProjectContextFiles(input.request.workdir, { existsSync, readFileSync });
|
|
224
|
+
const skills = listAvailableSkills();
|
|
225
|
+
const existingSessions = relatedSessions(input.request, input.existingSessions ?? []);
|
|
226
|
+
const snapshot = captureRepoSnapshot(input.request.workdir, runCommand);
|
|
227
|
+
const generatedAt = nowIso();
|
|
228
|
+
const scopeContent = buildScopeContent(input.request, contextFiles, skills, agentName);
|
|
229
|
+
const stateContent = buildStateContent(input.request, contextKey, generatedAt, snapshot, existingSessions, agentName, input.activeWorkFrame);
|
|
230
|
+
mkdirSync(contextDir, { recursive: true });
|
|
231
|
+
writeFileSync(scopeFile, `${scopeContent}\n`, "utf-8");
|
|
232
|
+
writeFileSync(stateFile, `${stateContent}\n`, "utf-8");
|
|
233
|
+
(0, runtime_1.emitNervesEvent)({
|
|
234
|
+
component: "repertoire",
|
|
235
|
+
event: "repertoire.coding_context_pack_written",
|
|
236
|
+
message: "prepared coding session context pack",
|
|
237
|
+
meta: {
|
|
238
|
+
contextKey,
|
|
239
|
+
workdir: input.request.workdir,
|
|
240
|
+
taskRef: input.request.taskRef ?? null,
|
|
241
|
+
contextFiles: contextFiles.length,
|
|
242
|
+
skills: skills.length,
|
|
243
|
+
relatedSessions: existingSessions.length,
|
|
244
|
+
gitAvailable: snapshot.available,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
contextKey,
|
|
249
|
+
scopeFile,
|
|
250
|
+
stateFile,
|
|
251
|
+
scopeContent,
|
|
252
|
+
stateContent,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -131,10 +131,37 @@ function isSafeProgressSnippet(snippet) {
|
|
|
131
131
|
&& !/^parentAgent\b/i.test(normalized));
|
|
132
132
|
}
|
|
133
133
|
function pickUpdateSnippet(update) {
|
|
134
|
-
|
|
134
|
+
const checkpoint = update.session.checkpoint?.trim() || null;
|
|
135
|
+
return (checkpoint
|
|
136
|
+
?? lastMeaningfulLine(update.text)
|
|
135
137
|
?? lastMeaningfulLine(update.session.stderrTail)
|
|
136
138
|
?? lastMeaningfulLine(update.session.stdoutTail));
|
|
137
139
|
}
|
|
140
|
+
function renderValue(text) {
|
|
141
|
+
const trimmed = text?.trim();
|
|
142
|
+
return trimmed && trimmed.length > 0 ? trimmed : "(empty)";
|
|
143
|
+
}
|
|
144
|
+
function renderPath(text) {
|
|
145
|
+
return text && text.trim().length > 0 ? text : "(none)";
|
|
146
|
+
}
|
|
147
|
+
function formatCodingTail(session) {
|
|
148
|
+
const stdout = renderValue(session.stdoutTail);
|
|
149
|
+
const stderr = renderValue(session.stderrTail);
|
|
150
|
+
return [
|
|
151
|
+
`sessionId: ${session.id}`,
|
|
152
|
+
`runner: ${session.runner}`,
|
|
153
|
+
`status: ${session.status}`,
|
|
154
|
+
`checkpoint: ${renderValue(session.checkpoint ?? undefined)}`,
|
|
155
|
+
`artifactPath: ${renderPath(session.artifactPath)}`,
|
|
156
|
+
`workdir: ${session.workdir}`,
|
|
157
|
+
"",
|
|
158
|
+
"[stdout]",
|
|
159
|
+
stdout,
|
|
160
|
+
"",
|
|
161
|
+
"[stderr]",
|
|
162
|
+
stderr,
|
|
163
|
+
].join("\n");
|
|
164
|
+
}
|
|
138
165
|
function formatUpdateMessage(update) {
|
|
139
166
|
const label = formatSessionLabel(update.session);
|
|
140
167
|
const snippet = pickUpdateSnippet(update);
|
|
@@ -233,22 +260,6 @@ async function wakeInnerDialogForObligation(update) {
|
|
|
233
260
|
});
|
|
234
261
|
}
|
|
235
262
|
}
|
|
236
|
-
function formatCodingTail(session) {
|
|
237
|
-
const stdout = session.stdoutTail.trim() || "(empty)";
|
|
238
|
-
const stderr = session.stderrTail.trim() || "(empty)";
|
|
239
|
-
return [
|
|
240
|
-
`sessionId: ${session.id}`,
|
|
241
|
-
`runner: ${session.runner}`,
|
|
242
|
-
`status: ${session.status}`,
|
|
243
|
-
`workdir: ${session.workdir}`,
|
|
244
|
-
"",
|
|
245
|
-
"[stdout]",
|
|
246
|
-
stdout,
|
|
247
|
-
"",
|
|
248
|
-
"[stderr]",
|
|
249
|
-
stderr,
|
|
250
|
-
].join("\n");
|
|
251
|
-
}
|
|
252
263
|
function attachCodingSessionFeedback(manager, session, target) {
|
|
253
264
|
let lastMessage = "";
|
|
254
265
|
let closed = false;
|
|
@@ -50,6 +50,9 @@ function safeAgentName() {
|
|
|
50
50
|
function defaultStateFilePath(agentName) {
|
|
51
51
|
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "coding", "sessions.json");
|
|
52
52
|
}
|
|
53
|
+
function defaultArtifactDirPath(agentName) {
|
|
54
|
+
return path.join((0, identity_1.getAgentRoot)(agentName), "state", "coding", "sessions");
|
|
55
|
+
}
|
|
53
56
|
function isPidAlive(pid) {
|
|
54
57
|
try {
|
|
55
58
|
process.kill(pid, 0);
|
|
@@ -63,6 +66,8 @@ function cloneSession(session) {
|
|
|
63
66
|
return {
|
|
64
67
|
...session,
|
|
65
68
|
originSession: session.originSession ? { ...session.originSession } : undefined,
|
|
69
|
+
checkpoint: session.checkpoint ?? null,
|
|
70
|
+
artifactPath: session.artifactPath,
|
|
66
71
|
stdoutTail: session.stdoutTail,
|
|
67
72
|
stderrTail: session.stderrTail,
|
|
68
73
|
failure: session.failure
|
|
@@ -86,6 +91,46 @@ function appendTail(existing, nextChunk, maxLength = 2000) {
|
|
|
86
91
|
const combined = `${existing}${nextChunk}`;
|
|
87
92
|
return combined.length <= maxLength ? combined : combined.slice(combined.length - maxLength);
|
|
88
93
|
}
|
|
94
|
+
function compactText(text) {
|
|
95
|
+
return text.replace(/\s+/g, " ").trim();
|
|
96
|
+
}
|
|
97
|
+
function clipText(text, maxLength = 240) {
|
|
98
|
+
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 3)}...`;
|
|
99
|
+
}
|
|
100
|
+
function latestMeaningfulLine(text) {
|
|
101
|
+
const lines = text
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.map((line) => compactText(line))
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
if (lines.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
return clipText(lines.at(-1));
|
|
108
|
+
}
|
|
109
|
+
function fallbackCheckpoint(status, code, signal) {
|
|
110
|
+
switch (status) {
|
|
111
|
+
case "waiting_input":
|
|
112
|
+
return "needs input";
|
|
113
|
+
case "stalled":
|
|
114
|
+
return "no recent output";
|
|
115
|
+
case "completed":
|
|
116
|
+
return "completed";
|
|
117
|
+
case "failed":
|
|
118
|
+
if (code !== null)
|
|
119
|
+
return `exit code ${code}`;
|
|
120
|
+
if (signal)
|
|
121
|
+
return `terminated by ${signal}`;
|
|
122
|
+
return "failed";
|
|
123
|
+
case "killed":
|
|
124
|
+
return "terminated by parent agent";
|
|
125
|
+
default:
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function deriveCheckpoint(session) {
|
|
130
|
+
return (latestMeaningfulLine(session.stderrTail)
|
|
131
|
+
?? latestMeaningfulLine(session.stdoutTail)
|
|
132
|
+
?? fallbackCheckpoint(session.status, session.lastExitCode, session.lastSignal));
|
|
133
|
+
}
|
|
89
134
|
function isSpawnCodingResult(value) {
|
|
90
135
|
return typeof value === "object" && value !== null && "process" in value;
|
|
91
136
|
}
|
|
@@ -123,6 +168,7 @@ class CodingSessionManager {
|
|
|
123
168
|
maxRestarts;
|
|
124
169
|
defaultStallThresholdMs;
|
|
125
170
|
stateFilePath;
|
|
171
|
+
artifactDirPath;
|
|
126
172
|
existsSync;
|
|
127
173
|
readFileSync;
|
|
128
174
|
writeFileSync;
|
|
@@ -142,6 +188,8 @@ class CodingSessionManager {
|
|
|
142
188
|
this.pidAlive = options.pidAlive ?? isPidAlive;
|
|
143
189
|
this.agentName = options.agentName ?? safeAgentName();
|
|
144
190
|
this.stateFilePath = options.stateFilePath ?? defaultStateFilePath(this.agentName);
|
|
191
|
+
this.artifactDirPath = options.artifactDirPath
|
|
192
|
+
?? (options.stateFilePath ? path.dirname(options.stateFilePath) : defaultArtifactDirPath(this.agentName));
|
|
145
193
|
this.loadPersistedState();
|
|
146
194
|
}
|
|
147
195
|
async spawnSession(request) {
|
|
@@ -162,6 +210,8 @@ class CodingSessionManager {
|
|
|
162
210
|
obligationId: normalizedRequest.obligationId,
|
|
163
211
|
scopeFile: normalizedRequest.scopeFile,
|
|
164
212
|
stateFile: normalizedRequest.stateFile,
|
|
213
|
+
checkpoint: null,
|
|
214
|
+
artifactPath: this.artifactPathFor(id),
|
|
165
215
|
status: "spawning",
|
|
166
216
|
stdoutTail: "",
|
|
167
217
|
stderrTail: "",
|
|
@@ -248,6 +298,7 @@ class CodingSessionManager {
|
|
|
248
298
|
record.process.kill("SIGTERM");
|
|
249
299
|
record.process = null;
|
|
250
300
|
record.session.status = "killed";
|
|
301
|
+
record.session.checkpoint = "terminated by parent agent";
|
|
251
302
|
record.session.endedAt = this.nowIso();
|
|
252
303
|
(0, runtime_1.emitNervesEvent)({
|
|
253
304
|
component: "repertoire",
|
|
@@ -270,6 +321,7 @@ class CodingSessionManager {
|
|
|
270
321
|
continue;
|
|
271
322
|
stalled += 1;
|
|
272
323
|
record.session.status = "stalled";
|
|
324
|
+
record.session.checkpoint = deriveCheckpoint(record.session);
|
|
273
325
|
(0, runtime_1.emitNervesEvent)({
|
|
274
326
|
level: "warn",
|
|
275
327
|
component: "repertoire",
|
|
@@ -295,6 +347,7 @@ class CodingSessionManager {
|
|
|
295
347
|
record.process = null;
|
|
296
348
|
if (record.session.status === "running" || record.session.status === "spawning") {
|
|
297
349
|
record.session.status = "killed";
|
|
350
|
+
record.session.checkpoint = "terminated during manager shutdown";
|
|
298
351
|
record.session.endedAt = this.nowIso();
|
|
299
352
|
}
|
|
300
353
|
}
|
|
@@ -339,6 +392,13 @@ class CodingSessionManager {
|
|
|
339
392
|
record.session.endedAt = this.nowIso();
|
|
340
393
|
updateKind = "completed";
|
|
341
394
|
}
|
|
395
|
+
const checkpoint = latestMeaningfulLine(text);
|
|
396
|
+
if (checkpoint) {
|
|
397
|
+
record.session.checkpoint = checkpoint;
|
|
398
|
+
}
|
|
399
|
+
else if (!record.session.checkpoint) {
|
|
400
|
+
record.session.checkpoint = deriveCheckpoint(record.session);
|
|
401
|
+
}
|
|
342
402
|
(0, runtime_1.emitNervesEvent)({
|
|
343
403
|
component: "repertoire",
|
|
344
404
|
event: "repertoire.coding_session_output",
|
|
@@ -362,12 +422,14 @@ class CodingSessionManager {
|
|
|
362
422
|
record.session.lastSignal = signal;
|
|
363
423
|
if (record.session.status === "killed" || record.session.status === "completed") {
|
|
364
424
|
record.session.endedAt = this.nowIso();
|
|
425
|
+
record.session.checkpoint = deriveCheckpoint(record.session);
|
|
365
426
|
this.persistState();
|
|
366
427
|
return;
|
|
367
428
|
}
|
|
368
429
|
if (code === 0) {
|
|
369
430
|
record.session.status = "completed";
|
|
370
431
|
record.session.endedAt = this.nowIso();
|
|
432
|
+
record.session.checkpoint = deriveCheckpoint(record.session);
|
|
371
433
|
this.persistState();
|
|
372
434
|
this.notifyListeners(record.session.id, { kind: "completed", session: cloneSession(record.session) });
|
|
373
435
|
return;
|
|
@@ -379,6 +441,7 @@ class CodingSessionManager {
|
|
|
379
441
|
record.session.status = "failed";
|
|
380
442
|
record.session.endedAt = this.nowIso();
|
|
381
443
|
record.session.failure = defaultFailureDiagnostics(code, signal, record.command, record.args, record.stdoutTail, record.stderrTail);
|
|
444
|
+
record.session.checkpoint = deriveCheckpoint(record.session);
|
|
382
445
|
(0, runtime_1.emitNervesEvent)({
|
|
383
446
|
level: "error",
|
|
384
447
|
component: "repertoire",
|
|
@@ -404,6 +467,7 @@ class CodingSessionManager {
|
|
|
404
467
|
record.session.lastActivityAt = this.nowIso();
|
|
405
468
|
record.session.endedAt = null;
|
|
406
469
|
record.session.failure = null;
|
|
470
|
+
record.session.checkpoint = `restarted after ${reason}`;
|
|
407
471
|
this.attachProcessListeners(record);
|
|
408
472
|
(0, runtime_1.emitNervesEvent)({
|
|
409
473
|
level: "warn",
|
|
@@ -498,6 +562,8 @@ class CodingSessionManager {
|
|
|
498
562
|
failure: session.failure ?? null,
|
|
499
563
|
stdoutTail: session.stdoutTail ?? session.failure?.stdoutTail ?? "",
|
|
500
564
|
stderrTail: session.stderrTail ?? session.failure?.stderrTail ?? "",
|
|
565
|
+
checkpoint: typeof session.checkpoint === "string" ? session.checkpoint : null,
|
|
566
|
+
artifactPath: typeof session.artifactPath === "string" ? session.artifactPath : this.artifactPathFor(session.id),
|
|
501
567
|
};
|
|
502
568
|
if (typeof normalizedSession.pid === "number") {
|
|
503
569
|
const alive = this.pidAlive(normalizedSession.pid);
|
|
@@ -510,6 +576,7 @@ class CodingSessionManager {
|
|
|
510
576
|
normalizedSession.pid = null;
|
|
511
577
|
}
|
|
512
578
|
}
|
|
579
|
+
normalizedSession.checkpoint = normalizedSession.checkpoint ?? deriveCheckpoint(normalizedSession);
|
|
513
580
|
this.records.set(normalizedSession.id, {
|
|
514
581
|
request: normalizedRequest,
|
|
515
582
|
session: normalizedSession,
|
|
@@ -549,6 +616,80 @@ class CodingSessionManager {
|
|
|
549
616
|
meta: { path: this.stateFilePath, reason: error instanceof Error ? error.message : String(error) },
|
|
550
617
|
});
|
|
551
618
|
}
|
|
619
|
+
this.persistArtifacts();
|
|
620
|
+
}
|
|
621
|
+
artifactPathFor(sessionId) {
|
|
622
|
+
return path.join(this.artifactDirPath, `${sessionId}.md`);
|
|
623
|
+
}
|
|
624
|
+
renderArtifact(record) {
|
|
625
|
+
const { request, session } = record;
|
|
626
|
+
const stdout = session.stdoutTail.trim() || "(empty)";
|
|
627
|
+
const stderr = session.stderrTail.trim() || "(empty)";
|
|
628
|
+
const lines = [
|
|
629
|
+
"# Coding Session Artifact",
|
|
630
|
+
"",
|
|
631
|
+
"## Session",
|
|
632
|
+
`id: ${session.id}`,
|
|
633
|
+
`runner: ${session.runner}`,
|
|
634
|
+
`status: ${session.status}`,
|
|
635
|
+
`taskRef: ${session.taskRef ?? "unassigned"}`,
|
|
636
|
+
`workdir: ${session.workdir}`,
|
|
637
|
+
`startedAt: ${session.startedAt}`,
|
|
638
|
+
`lastActivityAt: ${session.lastActivityAt}`,
|
|
639
|
+
`endedAt: ${session.endedAt ?? "active"}`,
|
|
640
|
+
`pid: ${session.pid ?? "none"}`,
|
|
641
|
+
`restarts: ${session.restartCount}`,
|
|
642
|
+
`checkpoint: ${session.checkpoint ?? "none"}`,
|
|
643
|
+
`scopeFile: ${session.scopeFile ?? "none"}`,
|
|
644
|
+
`stateFile: ${session.stateFile ?? "none"}`,
|
|
645
|
+
"",
|
|
646
|
+
"## Request",
|
|
647
|
+
request.prompt,
|
|
648
|
+
"",
|
|
649
|
+
"## Stdout Tail",
|
|
650
|
+
stdout,
|
|
651
|
+
"",
|
|
652
|
+
"## Stderr Tail",
|
|
653
|
+
stderr,
|
|
654
|
+
];
|
|
655
|
+
if (session.failure) {
|
|
656
|
+
lines.push("", "## Failure", `command: ${session.failure.command}`, `args: ${session.failure.args.join(" ") || "(none)"}`, `code: ${session.failure.code ?? "null"}`, `signal: ${session.failure.signal ?? "null"}`);
|
|
657
|
+
}
|
|
658
|
+
return `${lines.join("\n")}\n`;
|
|
659
|
+
}
|
|
660
|
+
persistArtifacts() {
|
|
661
|
+
try {
|
|
662
|
+
this.mkdirSync(this.artifactDirPath, { recursive: true });
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
(0, runtime_1.emitNervesEvent)({
|
|
666
|
+
level: "warn",
|
|
667
|
+
component: "repertoire",
|
|
668
|
+
event: "repertoire.coding_artifact_persist_error",
|
|
669
|
+
message: "failed preparing coding artifact directory",
|
|
670
|
+
meta: { path: this.artifactDirPath, reason: error instanceof Error ? error.message : String(error) },
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
for (const record of this.records.values()) {
|
|
675
|
+
try {
|
|
676
|
+
record.session.artifactPath = record.session.artifactPath ?? this.artifactPathFor(record.session.id);
|
|
677
|
+
this.writeFileSync(record.session.artifactPath, this.renderArtifact(record), "utf-8");
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
(0, runtime_1.emitNervesEvent)({
|
|
681
|
+
level: "warn",
|
|
682
|
+
component: "repertoire",
|
|
683
|
+
event: "repertoire.coding_artifact_persist_error",
|
|
684
|
+
message: "failed writing coding session artifact",
|
|
685
|
+
meta: {
|
|
686
|
+
id: record.session.id,
|
|
687
|
+
path: record.session.artifactPath ?? this.artifactPathFor(record.session.id),
|
|
688
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
552
693
|
}
|
|
553
694
|
}
|
|
554
695
|
exports.CodingSessionManager = CodingSessionManager;
|
|
@@ -72,20 +72,32 @@ function buildSpawnEnv(baseEnv, homeDir) {
|
|
|
72
72
|
PATH: pathEntries.join(path.delimiter),
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
+
function appendFileSection(sections, label, filePath, deps) {
|
|
76
|
+
if (!filePath || !deps.existsSync(filePath))
|
|
77
|
+
return;
|
|
78
|
+
const content = deps.readFileSync(filePath, "utf-8").trim();
|
|
79
|
+
if (content.length === 0)
|
|
80
|
+
return;
|
|
81
|
+
sections.push(`${label} (${filePath}):\n${content}`);
|
|
82
|
+
}
|
|
75
83
|
function buildPrompt(request, deps) {
|
|
76
84
|
const sections = [];
|
|
85
|
+
sections.push([
|
|
86
|
+
"Execution contract:",
|
|
87
|
+
"- You are a subordinate coding session launched by a parent Ouro agent.",
|
|
88
|
+
"- Execute the concrete request in the supplied workdir directly.",
|
|
89
|
+
"- Do not switch into planning/doing workflows, approval gates, or repo-management rituals unless the request explicitly asks for them.",
|
|
90
|
+
"- Treat the request, scope file, and state file as the authoritative briefing for this session.",
|
|
91
|
+
"- Prefer direct execution and verification over narration.",
|
|
92
|
+
].join("\n"));
|
|
77
93
|
sections.push([
|
|
78
94
|
"Coding session metadata:",
|
|
79
95
|
`sessionId: ${request.sessionId ?? "pending"}`,
|
|
80
96
|
`parentAgent: ${request.parentAgent ?? "unknown"}`,
|
|
81
97
|
`taskRef: ${request.taskRef ?? "unassigned"}`,
|
|
82
98
|
].join("\n"));
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (stateContent.length > 0) {
|
|
86
|
-
sections.push(`State file (${request.stateFile}):\n${stateContent}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
99
|
+
appendFileSection(sections, "Scope file", request.scopeFile, deps);
|
|
100
|
+
appendFileSection(sections, "State file", request.stateFile, deps);
|
|
89
101
|
sections.push(request.prompt);
|
|
90
102
|
return sections.join("\n\n---\n\n");
|
|
91
103
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.codingToolDefinitions = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
|
+
const context_pack_1 = require("./context-pack");
|
|
5
6
|
const identity_1 = require("../../heart/identity");
|
|
6
7
|
const obligations_1 = require("../../heart/obligations");
|
|
7
8
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -42,11 +43,13 @@ function matchesReusableCodingSession(session, request) {
|
|
|
42
43
|
if (session.status !== "spawning" && session.status !== "running" && session.status !== "waiting_input" && session.status !== "stalled") {
|
|
43
44
|
return false;
|
|
44
45
|
}
|
|
46
|
+
const scopeMatches = request.scopeFile ? session.scopeFile === request.scopeFile : true;
|
|
47
|
+
const stateMatches = request.stateFile ? session.stateFile === request.stateFile : true;
|
|
45
48
|
return (session.runner === request.runner &&
|
|
46
49
|
session.workdir === request.workdir &&
|
|
47
50
|
session.taskRef === request.taskRef &&
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
scopeMatches &&
|
|
52
|
+
stateMatches &&
|
|
50
53
|
session.obligationId === request.obligationId &&
|
|
51
54
|
sameOriginSession(request.originSession, session.originSession));
|
|
52
55
|
}
|
|
@@ -221,7 +224,8 @@ exports.codingToolDefinitions = [
|
|
|
221
224
|
if (stateFile)
|
|
222
225
|
request.stateFile = stateFile;
|
|
223
226
|
const manager = (0, index_1.getCodingSessionManager)();
|
|
224
|
-
const
|
|
227
|
+
const existingSessions = manager.listSessions();
|
|
228
|
+
const existingSession = findReusableCodingSession(existingSessions, request);
|
|
225
229
|
if (existingSession) {
|
|
226
230
|
(0, runtime_1.emitNervesEvent)({
|
|
227
231
|
component: "repertoire",
|
|
@@ -241,6 +245,17 @@ exports.codingToolDefinitions = [
|
|
|
241
245
|
});
|
|
242
246
|
request.obligationId = created.id;
|
|
243
247
|
}
|
|
248
|
+
if (!request.scopeFile || !request.stateFile) {
|
|
249
|
+
const generated = (0, context_pack_1.prepareCodingContextPack)({
|
|
250
|
+
request: { ...request },
|
|
251
|
+
existingSessions,
|
|
252
|
+
activeWorkFrame: ctx?.activeWorkFrame,
|
|
253
|
+
});
|
|
254
|
+
if (!request.scopeFile)
|
|
255
|
+
request.scopeFile = generated.scopeFile;
|
|
256
|
+
if (!request.stateFile)
|
|
257
|
+
request.stateFile = generated.stateFile;
|
|
258
|
+
}
|
|
244
259
|
const session = await manager.spawnSession(request);
|
|
245
260
|
if (session.obligationId) {
|
|
246
261
|
(0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), session.obligationId, {
|
|
@@ -116,6 +116,14 @@ async function recallSessionSafely(options) {
|
|
|
116
116
|
return { kind: "missing" };
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
+
async function searchSessionSafely(options) {
|
|
120
|
+
try {
|
|
121
|
+
return await (0, session_recall_1.searchSessionTranscript)(options);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { kind: "missing" };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
119
127
|
function normalizeProgressOutcome(text) {
|
|
120
128
|
const trimmed = text.trim();
|
|
121
129
|
/* v8 ignore next -- defensive: normalizeProgressOutcome null branch @preserve */
|
|
@@ -993,7 +1001,7 @@ exports.baseToolDefinitions = [
|
|
|
993
1001
|
type: "function",
|
|
994
1002
|
function: {
|
|
995
1003
|
name: "query_session",
|
|
996
|
-
description: "
|
|
1004
|
+
description: "inspect another session. use transcript for recent context, status for self/inner progress, or search to find older history by query.",
|
|
997
1005
|
parameters: {
|
|
998
1006
|
type: "object",
|
|
999
1007
|
properties: {
|
|
@@ -1001,7 +1009,12 @@ exports.baseToolDefinitions = [
|
|
|
1001
1009
|
channel: { type: "string", description: "the channel: cli, teams, or inner" },
|
|
1002
1010
|
key: { type: "string", description: "session key (defaults to 'session')" },
|
|
1003
1011
|
messageCount: { type: "string", description: "how many recent messages to return (default 20)" },
|
|
1004
|
-
mode: {
|
|
1012
|
+
mode: {
|
|
1013
|
+
type: "string",
|
|
1014
|
+
enum: ["transcript", "status", "search"],
|
|
1015
|
+
description: "transcript (default), lightweight status for self/inner checks, or search for older history",
|
|
1016
|
+
},
|
|
1017
|
+
query: { type: "string", description: "required when mode=search; search term for older session history" },
|
|
1005
1018
|
},
|
|
1006
1019
|
required: ["friendId", "channel"],
|
|
1007
1020
|
},
|
|
@@ -1021,6 +1034,33 @@ exports.baseToolDefinitions = [
|
|
|
1021
1034
|
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
1022
1035
|
return renderInnerProgressStatus((0, thoughts_1.readInnerDialogStatus)(sessionPath, pendingDir));
|
|
1023
1036
|
}
|
|
1037
|
+
if (mode === "search") {
|
|
1038
|
+
const query = (args.query || "").trim();
|
|
1039
|
+
if (!query) {
|
|
1040
|
+
return "search mode requires a non-empty query.";
|
|
1041
|
+
}
|
|
1042
|
+
const search = await searchSessionSafely({
|
|
1043
|
+
sessionPath: (0, config_1.resolveSessionPath)(friendId, channel, key),
|
|
1044
|
+
friendId,
|
|
1045
|
+
channel,
|
|
1046
|
+
key,
|
|
1047
|
+
query,
|
|
1048
|
+
});
|
|
1049
|
+
if (search.kind === "missing") {
|
|
1050
|
+
return NO_SESSION_FOUND_MESSAGE;
|
|
1051
|
+
}
|
|
1052
|
+
if (search.kind === "empty") {
|
|
1053
|
+
return EMPTY_SESSION_MESSAGE;
|
|
1054
|
+
}
|
|
1055
|
+
if (search.kind === "no_match") {
|
|
1056
|
+
return `no matches for "${search.query}" in that session.\n\n${search.snapshot}`;
|
|
1057
|
+
}
|
|
1058
|
+
return [
|
|
1059
|
+
`history search: "${search.query}"`,
|
|
1060
|
+
search.snapshot,
|
|
1061
|
+
...search.matches.map((match, index) => `match ${index + 1}\n${match}`),
|
|
1062
|
+
].join("\n\n");
|
|
1063
|
+
}
|
|
1024
1064
|
const sessFile = (0, config_1.resolveSessionPath)(friendId, channel, key);
|
|
1025
1065
|
const recall = await recallSessionSafely({
|
|
1026
1066
|
sessionPath: sessFile,
|
|
@@ -629,7 +629,13 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
|
|
|
629
629
|
continuityIngressTexts: getBlueBubblesContinuityIngressTexts(event),
|
|
630
630
|
callbacks,
|
|
631
631
|
friendResolver: { resolve: () => Promise.resolve(context) },
|
|
632
|
-
sessionLoader: {
|
|
632
|
+
sessionLoader: {
|
|
633
|
+
loadOrCreate: () => Promise.resolve({
|
|
634
|
+
messages: sessionMessages,
|
|
635
|
+
sessionPath: sessPath,
|
|
636
|
+
state: existing?.state,
|
|
637
|
+
}),
|
|
638
|
+
},
|
|
633
639
|
pendingDir,
|
|
634
640
|
friendStore: store,
|
|
635
641
|
provider: "imessage-handle",
|
package/dist/senses/cli.js
CHANGED
|
@@ -827,7 +827,13 @@ async function main(agentName, options) {
|
|
|
827
827
|
continuityIngressTexts: getCliContinuityIngressTexts(userInput),
|
|
828
828
|
callbacks,
|
|
829
829
|
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
830
|
-
sessionLoader: {
|
|
830
|
+
sessionLoader: {
|
|
831
|
+
loadOrCreate: () => Promise.resolve({
|
|
832
|
+
messages,
|
|
833
|
+
sessionPath: sessPath,
|
|
834
|
+
state: sessionState,
|
|
835
|
+
}),
|
|
836
|
+
},
|
|
831
837
|
pendingDir,
|
|
832
838
|
friendStore,
|
|
833
839
|
provider: "local",
|
|
@@ -459,7 +459,10 @@ async function runInnerDialogTurn(options) {
|
|
|
459
459
|
const sessionLoader = {
|
|
460
460
|
loadOrCreate: async () => {
|
|
461
461
|
if (existingMessages.length > 0) {
|
|
462
|
-
return {
|
|
462
|
+
return {
|
|
463
|
+
messages: existingMessages,
|
|
464
|
+
sessionPath: sessionFilePath,
|
|
465
|
+
};
|
|
463
466
|
}
|
|
464
467
|
// Fresh session: build system prompt
|
|
465
468
|
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true, mcpManager });
|
package/dist/senses/teams.js
CHANGED
|
@@ -564,7 +564,11 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
564
564
|
? existing.messages
|
|
565
565
|
: [{ role: "system", content: await (0, prompt_1.buildSystem)("teams", { mcpManager }, resolvedContext) }];
|
|
566
566
|
(0, core_1.repairOrphanedToolCalls)(messages);
|
|
567
|
-
return {
|
|
567
|
+
return {
|
|
568
|
+
messages,
|
|
569
|
+
sessionPath: sessPath,
|
|
570
|
+
state: existing?.state,
|
|
571
|
+
};
|
|
568
572
|
},
|
|
569
573
|
},
|
|
570
574
|
pendingDir,
|