@ouro.bot/cli 0.1.0-alpha.102 → 0.1.0-alpha.104
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 +14 -0
- package/dist/heart/active-work.js +55 -3
- package/dist/heart/core.js +59 -9
- package/dist/heart/providers/anthropic.js +1 -1
- package/dist/heart/providers/azure.js +1 -1
- package/dist/heart/providers/github-copilot.js +2 -2
- package/dist/heart/providers/minimax.js +1 -1
- package/dist/heart/providers/openai-codex.js +1 -1
- package/dist/heart/streaming.js +11 -5
- package/dist/mind/obligation-steering.js +242 -17
- package/dist/mind/prompt.js +21 -3
- package/dist/repertoire/coding/feedback.js +12 -8
- package/dist/repertoire/coding/tools.js +10 -0
- package/dist/senses/pipeline.js +41 -12
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
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.104",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Family-scoped status checks now keep the five-line header anchored on the current live conversation while appending an `other active sessions:` block for every other live lane the agent is actively working.",
|
|
8
|
+
"The inbound pipeline, active-work frame, prompt steering, and status retry logic now preserve non-current live coding lanes and obligations so family status answers stop hiding parallel work in other sessions."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.103",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Status-check turns now answer in a fixed live-state shape that names the current conversation, active lane, artifact, latest checkpoint, and next action instead of drifting into broad mission summaries.",
|
|
15
|
+
"Those status turns now hold final streaming until the exact reply is ready and turn report-backs into same-thread obligation updates, so 'I'll report back here' comes back as a real in-thread follow-up instead of hidden background work."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.102",
|
|
6
20
|
"changes": [
|
|
@@ -63,6 +63,24 @@ function formatObligationSurface(obligation) {
|
|
|
63
63
|
return ` (${obligation.currentSurface.label})`;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
function mergeArtifactFallback(obligation) {
|
|
67
|
+
const trimmed = obligation.content.trim();
|
|
68
|
+
if (!trimmed)
|
|
69
|
+
return "the fix";
|
|
70
|
+
const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
|
|
71
|
+
return stripped || "the fix";
|
|
72
|
+
}
|
|
73
|
+
function formatMergeArtifact(obligation) {
|
|
74
|
+
const currentArtifact = obligation.currentArtifact?.trim();
|
|
75
|
+
if (currentArtifact)
|
|
76
|
+
return currentArtifact;
|
|
77
|
+
if (obligation.currentSurface?.kind === "merge") {
|
|
78
|
+
const surfaceLabel = obligation.currentSurface.label.trim();
|
|
79
|
+
if (surfaceLabel)
|
|
80
|
+
return surfaceLabel;
|
|
81
|
+
}
|
|
82
|
+
return mergeArtifactFallback(obligation);
|
|
83
|
+
}
|
|
66
84
|
function findPrimaryOpenObligation(frame) {
|
|
67
85
|
return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled")
|
|
68
86
|
?? (frame.pendingObligations ?? []).find(obligations_1.isOpenObligation)
|
|
@@ -79,6 +97,9 @@ function formatActiveLane(frame, obligation) {
|
|
|
79
97
|
if (frame.inner?.job?.status === "running") {
|
|
80
98
|
return "inner dialog";
|
|
81
99
|
}
|
|
100
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0 && frame.currentSession) {
|
|
101
|
+
return "this same thread";
|
|
102
|
+
}
|
|
82
103
|
return null;
|
|
83
104
|
}
|
|
84
105
|
function formatCurrentArtifact(frame, obligation) {
|
|
@@ -91,6 +112,9 @@ function formatCurrentArtifact(frame, obligation) {
|
|
|
91
112
|
if ((frame.codingSessions ?? []).length > 0) {
|
|
92
113
|
return "no PR or merge artifact yet";
|
|
93
114
|
}
|
|
115
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
|
|
116
|
+
return "no artifact yet";
|
|
117
|
+
}
|
|
94
118
|
return null;
|
|
95
119
|
}
|
|
96
120
|
function formatNextAction(frame, obligation) {
|
|
@@ -108,8 +132,7 @@ function formatNextAction(frame, obligation) {
|
|
|
108
132
|
return "finish the coding pass and bring the result back here";
|
|
109
133
|
}
|
|
110
134
|
if (obligation?.status === "waiting_for_merge") {
|
|
111
|
-
|
|
112
|
-
return `wait for checks, merge ${artifact}, then update runtime`;
|
|
135
|
+
return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
|
|
113
136
|
}
|
|
114
137
|
if (obligation?.status === "updating_runtime") {
|
|
115
138
|
return "update runtime, verify version/changelog, then re-observe";
|
|
@@ -117,6 +140,9 @@ function formatNextAction(frame, obligation) {
|
|
|
117
140
|
if (obligation) {
|
|
118
141
|
return "continue the active loop and bring the result back here";
|
|
119
142
|
}
|
|
143
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
|
|
144
|
+
return `work on "${frame.currentObligation.trim()}" and bring back a concrete artifact`;
|
|
145
|
+
}
|
|
120
146
|
return null;
|
|
121
147
|
}
|
|
122
148
|
function suggestBridgeForActiveWork(input) {
|
|
@@ -177,6 +203,9 @@ function buildActiveWorkFrame(input) {
|
|
|
177
203
|
const activeBridgePresent = input.bridges.some(isActiveBridge);
|
|
178
204
|
const openObligations = activeObligationCount(input.pendingObligations);
|
|
179
205
|
const liveCodingSessions = input.codingSessions ?? [];
|
|
206
|
+
const allOtherLiveSessions = [...input.friendActivity].sort(compareActivity);
|
|
207
|
+
const otherCodingSessions = input.otherCodingSessions ?? [];
|
|
208
|
+
const pendingObligations = input.pendingObligations ?? [];
|
|
180
209
|
const centerOfGravity = activeBridgePresent
|
|
181
210
|
? "shared-work"
|
|
182
211
|
: (input.inner.status === "running" || input.inner.hasPending || input.mustResolveBeforeHandoff || openObligations > 0 || liveCodingSessions.length > 0)
|
|
@@ -197,9 +226,11 @@ function buildActiveWorkFrame(input) {
|
|
|
197
226
|
friendActivity: {
|
|
198
227
|
freshestForCurrentFriend: friendSessions[0] ?? null,
|
|
199
228
|
otherLiveSessionsForCurrentFriend: friendSessions,
|
|
229
|
+
allOtherLiveSessions,
|
|
200
230
|
},
|
|
201
231
|
codingSessions: liveCodingSessions,
|
|
202
|
-
|
|
232
|
+
otherCodingSessions,
|
|
233
|
+
pendingObligations,
|
|
203
234
|
targetCandidates: input.targetCandidates ?? [],
|
|
204
235
|
bridgeSuggestion: suggestBridgeForActiveWork({
|
|
205
236
|
currentSession: input.currentSession,
|
|
@@ -221,6 +252,8 @@ function buildActiveWorkFrame(input) {
|
|
|
221
252
|
liveTasks: frame.taskPressure.liveTaskNames.length,
|
|
222
253
|
liveSessions: frame.friendActivity.otherLiveSessionsForCurrentFriend.length,
|
|
223
254
|
codingSessions: frame.codingSessions.length,
|
|
255
|
+
otherLiveSessions: allOtherLiveSessions.length,
|
|
256
|
+
otherCodingSessions: otherCodingSessions.length,
|
|
224
257
|
pendingObligations: openObligations,
|
|
225
258
|
hasBridgeSuggestion: frame.bridgeSuggestion !== null,
|
|
226
259
|
},
|
|
@@ -233,6 +266,8 @@ function formatActiveWorkFrame(frame) {
|
|
|
233
266
|
const activeLane = formatActiveLane(frame, primaryObligation);
|
|
234
267
|
const currentArtifact = formatCurrentArtifact(frame, primaryObligation);
|
|
235
268
|
const nextAction = formatNextAction(frame, primaryObligation);
|
|
269
|
+
const otherCodingSessions = frame.otherCodingSessions ?? [];
|
|
270
|
+
const otherLiveSessions = frame.friendActivity?.allOtherLiveSessions ?? [];
|
|
236
271
|
// Session line
|
|
237
272
|
if (frame.currentSession) {
|
|
238
273
|
let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
|
|
@@ -305,6 +340,23 @@ function formatActiveWorkFrame(frame) {
|
|
|
305
340
|
lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)}${describeCodingSessionScope(session, frame.currentSession)}`);
|
|
306
341
|
}
|
|
307
342
|
}
|
|
343
|
+
if (otherCodingSessions.length > 0) {
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push("## other live coding work");
|
|
346
|
+
for (const session of otherCodingSessions) {
|
|
347
|
+
const origin = session.originSession
|
|
348
|
+
? `${session.originSession.friendId}/${session.originSession.channel}/${session.originSession.key}`
|
|
349
|
+
: "another session";
|
|
350
|
+
lines.push(`- [${session.status}] ${formatCodingLaneLabel(session)} for ${origin}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (otherLiveSessions.length > 0) {
|
|
354
|
+
lines.push("");
|
|
355
|
+
lines.push("## other live sessions");
|
|
356
|
+
for (const session of otherLiveSessions) {
|
|
357
|
+
lines.push(`- ${session.friendName}/${session.channel}/${session.key}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
308
360
|
// Task pressure
|
|
309
361
|
if ((frame.taskPressure?.liveTaskNames ?? []).length > 0) {
|
|
310
362
|
lines.push("");
|
package/dist/heart/core.js
CHANGED
|
@@ -27,6 +27,7 @@ const azure_1 = require("./providers/azure");
|
|
|
27
27
|
const minimax_1 = require("./providers/minimax");
|
|
28
28
|
const openai_codex_1 = require("./providers/openai-codex");
|
|
29
29
|
const github_copilot_1 = require("./providers/github-copilot");
|
|
30
|
+
const obligation_steering_1 = require("../mind/obligation-steering");
|
|
30
31
|
const pending_1 = require("../mind/pending");
|
|
31
32
|
const identity_2 = require("./identity");
|
|
32
33
|
const socket_client_1 = require("./daemon/socket-client");
|
|
@@ -272,6 +273,45 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
|
|
|
272
273
|
}
|
|
273
274
|
return null;
|
|
274
275
|
}
|
|
276
|
+
function hasExactStatusReplyShape(answer, statusCheckScope) {
|
|
277
|
+
const lines = answer.trimEnd().split(/\r?\n/);
|
|
278
|
+
const hasFiveLineHeader = (/^live conversation:\s+\S.+$/i.test(lines[0])
|
|
279
|
+
&& /^active lane:\s+\S.+$/i.test(lines[1])
|
|
280
|
+
&& /^current artifact:\s+\S.+$/i.test(lines[2])
|
|
281
|
+
&& /^latest checkpoint:\s+\S.+$/i.test(lines[3])
|
|
282
|
+
&& /^next action:\s+\S.+$/i.test(lines[4]));
|
|
283
|
+
if (!hasFiveLineHeader)
|
|
284
|
+
return false;
|
|
285
|
+
if (statusCheckScope !== "all-sessions-family") {
|
|
286
|
+
return lines.length === 5;
|
|
287
|
+
}
|
|
288
|
+
if (lines.length < 7)
|
|
289
|
+
return false;
|
|
290
|
+
if (!/^other active sessions:\s*$/i.test(lines[5]))
|
|
291
|
+
return false;
|
|
292
|
+
return lines.slice(6).every((line) => /^-\s+\S.+$/i.test(line));
|
|
293
|
+
}
|
|
294
|
+
function extractLatestCheckpoint(answer) {
|
|
295
|
+
const latestLine = answer.trimEnd().split(/\r?\n/)[3];
|
|
296
|
+
return latestLine.replace(/^latest checkpoint:\s*/i, "").trim();
|
|
297
|
+
}
|
|
298
|
+
function getStatusReplyRetryError(answer, statusCheckRequested, activeWorkFrame, statusCheckScope) {
|
|
299
|
+
if (!statusCheckRequested || answer == null)
|
|
300
|
+
return null;
|
|
301
|
+
if (hasExactStatusReplyShape(answer, statusCheckScope))
|
|
302
|
+
return null;
|
|
303
|
+
if (!activeWorkFrame) {
|
|
304
|
+
return `the user asked for current status. call final_answer again using exactly these five non-empty lines and nothing else:
|
|
305
|
+
live conversation: ...
|
|
306
|
+
active lane: ...
|
|
307
|
+
current artifact: ...
|
|
308
|
+
latest checkpoint: ...
|
|
309
|
+
next action: ...`;
|
|
310
|
+
}
|
|
311
|
+
return `the user asked for current status right now.
|
|
312
|
+
${(0, obligation_steering_1.renderExactStatusReplyContract)(activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(activeWorkFrame), statusCheckScope)}
|
|
313
|
+
call final_answer again using that exact status shape and nothing else.`;
|
|
314
|
+
}
|
|
275
315
|
// Re-export kick utilities for backward compat
|
|
276
316
|
var kicks_1 = require("./kicks");
|
|
277
317
|
Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
|
|
@@ -551,6 +591,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
551
591
|
traceId,
|
|
552
592
|
toolChoiceRequired,
|
|
553
593
|
reasoningEffort: currentReasoningEffort,
|
|
594
|
+
eagerFinalAnswerStreaming: !options?.statusCheckRequested,
|
|
554
595
|
});
|
|
555
596
|
// Track usage from the latest API call
|
|
556
597
|
if (result.usage)
|
|
@@ -599,15 +640,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
599
640
|
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
600
641
|
const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
|
|
601
642
|
const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
|
|
643
|
+
const statusReplyRetryError = getStatusReplyRetryError(answer, options?.statusCheckRequested, options?.activeWorkFrame, options?.statusCheckScope);
|
|
644
|
+
const exactStatusReplyAccepted = Boolean(options?.statusCheckRequested) && !statusReplyRetryError && answer != null;
|
|
645
|
+
const deliveredAnswer = exactStatusReplyAccepted && options?.activeWorkFrame
|
|
646
|
+
? (0, obligation_steering_1.buildExactStatusReply)(options.activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(options.activeWorkFrame), extractLatestCheckpoint(answer), options?.statusCheckScope)
|
|
647
|
+
: answer;
|
|
602
648
|
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
603
649
|
const validTerminalIntent = intent === "complete" || intent === "blocked";
|
|
604
|
-
const validClosure =
|
|
605
|
-
&& !retryError
|
|
606
|
-
&&
|
|
650
|
+
const validClosure = deliveredAnswer != null
|
|
651
|
+
&& (exactStatusReplyAccepted || !retryError)
|
|
652
|
+
&& !statusReplyRetryError
|
|
653
|
+
&& (exactStatusReplyAccepted || !mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
|
|
607
654
|
if (validClosure) {
|
|
608
655
|
completion = {
|
|
609
|
-
answer,
|
|
610
|
-
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
656
|
+
answer: deliveredAnswer,
|
|
657
|
+
intent: validDirectReply && !exactStatusReplyAccepted ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
611
658
|
};
|
|
612
659
|
if (result.finalAnswerStreamed) {
|
|
613
660
|
// The streaming layer already parsed and emitted the answer
|
|
@@ -619,10 +666,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
619
666
|
callbacks.onClearText?.();
|
|
620
667
|
// Emit the answer through the callback pipeline so channels receive it.
|
|
621
668
|
// Never truncate -- channel adapters handle splitting long messages.
|
|
622
|
-
callbacks.onTextChunk(
|
|
669
|
+
callbacks.onTextChunk(deliveredAnswer);
|
|
623
670
|
}
|
|
624
671
|
messages.push(msg);
|
|
625
|
-
if (validDirectReply) {
|
|
672
|
+
if (validDirectReply && !exactStatusReplyAccepted) {
|
|
626
673
|
const resumeWork = "direct reply delivered. resume the unresolved obligation now and keep working until you can finish or clearly report that you are blocked.";
|
|
627
674
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
628
675
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
@@ -641,8 +688,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
641
688
|
// assistant msg + error tool result and let the model try again.
|
|
642
689
|
callbacks.onClearText?.();
|
|
643
690
|
messages.push(msg);
|
|
644
|
-
|
|
645
|
-
|
|
691
|
+
const toolRetryMessage = statusReplyRetryError
|
|
692
|
+
?? retryError
|
|
693
|
+
?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
|
|
694
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
|
|
695
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
|
|
646
696
|
}
|
|
647
697
|
continue;
|
|
648
698
|
}
|
|
@@ -238,7 +238,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
238
238
|
const toolCalls = new Map();
|
|
239
239
|
const thinkingBlocks = new Map();
|
|
240
240
|
const redactedBlocks = new Map();
|
|
241
|
-
const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks);
|
|
241
|
+
const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks, request.eagerFinalAnswerStreaming);
|
|
242
242
|
try {
|
|
243
243
|
for await (const event of response) {
|
|
244
244
|
if (request.signal?.aborted)
|
|
@@ -136,7 +136,7 @@ function createAzureProviderRuntime() {
|
|
|
136
136
|
params.metadata = { trace_id: request.traceId };
|
|
137
137
|
if (request.toolChoiceRequired)
|
|
138
138
|
params.tool_choice = "required";
|
|
139
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
|
|
139
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
|
|
140
140
|
for (const item of result.outputItems)
|
|
141
141
|
nativeInput.push(item);
|
|
142
142
|
return result;
|
|
@@ -88,7 +88,7 @@ function createGithubCopilotProviderRuntime() {
|
|
|
88
88
|
if (request.toolChoiceRequired)
|
|
89
89
|
params.tool_choice = "required";
|
|
90
90
|
try {
|
|
91
|
-
return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
|
|
91
|
+
return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
|
|
92
92
|
}
|
|
93
93
|
catch (error) {
|
|
94
94
|
throw withAuthGuidance(error);
|
|
@@ -135,7 +135,7 @@ function createGithubCopilotProviderRuntime() {
|
|
|
135
135
|
if (request.toolChoiceRequired)
|
|
136
136
|
params.tool_choice = "required";
|
|
137
137
|
try {
|
|
138
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
|
|
138
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
|
|
139
139
|
for (const item of result.outputItems)
|
|
140
140
|
nativeInput.push(item);
|
|
141
141
|
return result;
|
|
@@ -51,7 +51,7 @@ function createMinimaxProviderRuntime() {
|
|
|
51
51
|
params.metadata = { trace_id: request.traceId };
|
|
52
52
|
if (request.toolChoiceRequired)
|
|
53
53
|
params.tool_choice = "required";
|
|
54
|
-
return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
|
|
54
|
+
return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
|
|
55
55
|
},
|
|
56
56
|
};
|
|
57
57
|
}
|
|
@@ -158,7 +158,7 @@ function createOpenAICodexProviderRuntime() {
|
|
|
158
158
|
if (request.toolChoiceRequired)
|
|
159
159
|
params.tool_choice = "required";
|
|
160
160
|
try {
|
|
161
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
|
|
161
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
|
|
162
162
|
for (const item of result.outputItems)
|
|
163
163
|
nativeInput.push(item);
|
|
164
164
|
return result;
|
package/dist/heart/streaming.js
CHANGED
|
@@ -84,13 +84,17 @@ class FinalAnswerStreamer {
|
|
|
84
84
|
parser = new FinalAnswerParser();
|
|
85
85
|
_detected = false;
|
|
86
86
|
callbacks;
|
|
87
|
-
|
|
87
|
+
enabled;
|
|
88
|
+
constructor(callbacks, enabled = true) {
|
|
88
89
|
this.callbacks = callbacks;
|
|
90
|
+
this.enabled = enabled;
|
|
89
91
|
}
|
|
90
92
|
get detected() { return this._detected; }
|
|
91
93
|
get streamed() { return this.parser.active; }
|
|
92
94
|
/** Mark final_answer as detected. Calls onClearText on the callbacks. */
|
|
93
95
|
activate() {
|
|
96
|
+
if (!this.enabled)
|
|
97
|
+
return;
|
|
94
98
|
if (this._detected)
|
|
95
99
|
return;
|
|
96
100
|
this._detected = true;
|
|
@@ -98,6 +102,8 @@ class FinalAnswerStreamer {
|
|
|
98
102
|
}
|
|
99
103
|
/** Feed an argument delta through the parser. Emits text via onTextChunk. */
|
|
100
104
|
processDelta(delta) {
|
|
105
|
+
if (!this.enabled)
|
|
106
|
+
return;
|
|
101
107
|
if (!this._detected)
|
|
102
108
|
return;
|
|
103
109
|
const text = this.parser.process(delta);
|
|
@@ -227,7 +233,7 @@ function toResponsesTools(ccTools) {
|
|
|
227
233
|
strict: false,
|
|
228
234
|
}));
|
|
229
235
|
}
|
|
230
|
-
async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
236
|
+
async function streamChatCompletion(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
|
|
231
237
|
(0, runtime_1.emitNervesEvent)({
|
|
232
238
|
component: "engine",
|
|
233
239
|
event: "engine.stream_start",
|
|
@@ -241,7 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
241
247
|
let toolCalls = {};
|
|
242
248
|
let streamStarted = false;
|
|
243
249
|
let usage;
|
|
244
|
-
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
250
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
|
|
245
251
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
246
252
|
let contentBuf = "";
|
|
247
253
|
let inThinkTag = false;
|
|
@@ -387,7 +393,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal) {
|
|
|
387
393
|
finalAnswerStreamed: answerStreamer.streamed,
|
|
388
394
|
};
|
|
389
395
|
}
|
|
390
|
-
async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
396
|
+
async function streamResponsesApi(client, createParams, callbacks, signal, eagerFinalAnswerStreaming = true) {
|
|
391
397
|
(0, runtime_1.emitNervesEvent)({
|
|
392
398
|
component: "engine",
|
|
393
399
|
event: "engine.stream_start",
|
|
@@ -402,7 +408,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal) {
|
|
|
402
408
|
const outputItems = [];
|
|
403
409
|
let currentToolCall = null;
|
|
404
410
|
let usage;
|
|
405
|
-
const answerStreamer = new FinalAnswerStreamer(callbacks);
|
|
411
|
+
const answerStreamer = new FinalAnswerStreamer(callbacks, eagerFinalAnswerStreaming);
|
|
406
412
|
let functionCallCount = 0;
|
|
407
413
|
for await (const event of response) {
|
|
408
414
|
if (signal?.aborted)
|
|
@@ -1,14 +1,139 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findActivePersistentObligation = findActivePersistentObligation;
|
|
4
|
+
exports.findStatusObligation = findStatusObligation;
|
|
4
5
|
exports.renderActiveObligationSteering = renderActiveObligationSteering;
|
|
5
6
|
exports.renderConcreteStatusGuidance = renderConcreteStatusGuidance;
|
|
7
|
+
exports.renderLiveThreadStatusShape = renderLiveThreadStatusShape;
|
|
8
|
+
exports.buildExactStatusReply = buildExactStatusReply;
|
|
9
|
+
exports.renderExactStatusReplyContract = renderExactStatusReplyContract;
|
|
6
10
|
const runtime_1 = require("../nerves/runtime");
|
|
7
11
|
function findActivePersistentObligation(frame) {
|
|
8
12
|
if (!frame)
|
|
9
13
|
return null;
|
|
10
14
|
return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled") ?? null;
|
|
11
15
|
}
|
|
16
|
+
function obligationTimestampMs(obligation) {
|
|
17
|
+
return Date.parse(obligation.updatedAt ?? obligation.createdAt);
|
|
18
|
+
}
|
|
19
|
+
function newestObligationFirst(left, right) {
|
|
20
|
+
return obligationTimestampMs(right) - obligationTimestampMs(left);
|
|
21
|
+
}
|
|
22
|
+
function matchesCurrentSession(frame, obligation) {
|
|
23
|
+
return matchesSessionOrigin(frame, obligation.origin);
|
|
24
|
+
}
|
|
25
|
+
function matchesSessionOrigin(frame, origin) {
|
|
26
|
+
return Boolean(frame.currentSession
|
|
27
|
+
&& origin.friendId === frame.currentSession.friendId
|
|
28
|
+
&& origin.channel === frame.currentSession.channel
|
|
29
|
+
&& origin.key === frame.currentSession.key);
|
|
30
|
+
}
|
|
31
|
+
function sessionOriginKey(origin) {
|
|
32
|
+
return `${origin.friendId}/${origin.channel}/${origin.key}`;
|
|
33
|
+
}
|
|
34
|
+
function codingSessionTimestampMs(session) {
|
|
35
|
+
return Date.parse(session.lastActivityAt ?? session.startedAt);
|
|
36
|
+
}
|
|
37
|
+
function formatCodingLaneLabel(session) {
|
|
38
|
+
return `${session.runner} ${session.id}`;
|
|
39
|
+
}
|
|
40
|
+
function formatOtherSessionArtifact(obligation, codingSession) {
|
|
41
|
+
if (obligation?.currentArtifact?.trim())
|
|
42
|
+
return obligation.currentArtifact.trim();
|
|
43
|
+
if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
|
|
44
|
+
return obligation.currentSurface.label.trim();
|
|
45
|
+
}
|
|
46
|
+
if (codingSession)
|
|
47
|
+
return "no PR or merge artifact yet";
|
|
48
|
+
return "no artifact yet";
|
|
49
|
+
}
|
|
50
|
+
function formatOtherSessionNextAction(obligation, codingSession) {
|
|
51
|
+
if (obligation?.nextAction?.trim())
|
|
52
|
+
return obligation.nextAction.trim();
|
|
53
|
+
if (obligation?.status === "waiting_for_merge") {
|
|
54
|
+
return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
|
|
55
|
+
}
|
|
56
|
+
if (obligation?.status === "updating_runtime") {
|
|
57
|
+
return "update runtime, verify version/changelog, then re-observe";
|
|
58
|
+
}
|
|
59
|
+
if (codingSession?.status === "waiting_input") {
|
|
60
|
+
return `answer ${formatCodingLaneLabel(codingSession)} and continue`;
|
|
61
|
+
}
|
|
62
|
+
if (codingSession?.status === "stalled") {
|
|
63
|
+
return `unstick ${formatCodingLaneLabel(codingSession)} and continue`;
|
|
64
|
+
}
|
|
65
|
+
if (codingSession) {
|
|
66
|
+
return "finish the coding pass and bring the result back there";
|
|
67
|
+
}
|
|
68
|
+
if (obligation) {
|
|
69
|
+
return "continue the active loop and bring the result back there";
|
|
70
|
+
}
|
|
71
|
+
return "check this session and bring back the latest concrete state";
|
|
72
|
+
}
|
|
73
|
+
function findFriendNameForOrigin(frame, origin) {
|
|
74
|
+
return (frame.friendActivity?.allOtherLiveSessions ?? []).find((entry) => entry.friendId === origin.friendId
|
|
75
|
+
&& entry.channel === origin.channel
|
|
76
|
+
&& entry.key === origin.key)?.friendName ?? origin.friendId;
|
|
77
|
+
}
|
|
78
|
+
function buildOtherActiveSessionLines(frame) {
|
|
79
|
+
const originMap = new Map();
|
|
80
|
+
for (const session of frame.friendActivity?.allOtherLiveSessions ?? []) {
|
|
81
|
+
originMap.set(sessionOriginKey(session), {
|
|
82
|
+
friendId: session.friendId,
|
|
83
|
+
channel: session.channel,
|
|
84
|
+
key: session.key,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
for (const session of frame.otherCodingSessions ?? []) {
|
|
88
|
+
if (!session.originSession || matchesSessionOrigin(frame, session.originSession))
|
|
89
|
+
continue;
|
|
90
|
+
originMap.set(sessionOriginKey(session.originSession), session.originSession);
|
|
91
|
+
}
|
|
92
|
+
for (const obligation of frame.pendingObligations ?? []) {
|
|
93
|
+
if (obligation.status === "fulfilled" || matchesSessionOrigin(frame, obligation.origin))
|
|
94
|
+
continue;
|
|
95
|
+
originMap.set(sessionOriginKey(obligation.origin), obligation.origin);
|
|
96
|
+
}
|
|
97
|
+
const summaries = [...originMap.values()].map((origin) => {
|
|
98
|
+
const obligation = [...(frame.pendingObligations ?? [])]
|
|
99
|
+
.filter((candidate) => candidate.status !== "fulfilled" && sessionOriginKey(candidate.origin) === sessionOriginKey(origin))
|
|
100
|
+
.sort(newestObligationFirst)[0] ?? null;
|
|
101
|
+
const codingSession = [...(frame.otherCodingSessions ?? [])]
|
|
102
|
+
.filter((candidate) => candidate.originSession && sessionOriginKey(candidate.originSession) === sessionOriginKey(origin))
|
|
103
|
+
.sort((left, right) => codingSessionTimestampMs(right) - codingSessionTimestampMs(left))[0] ?? null;
|
|
104
|
+
const liveSession = (frame.friendActivity?.allOtherLiveSessions ?? []).find((candidate) => sessionOriginKey(candidate) === sessionOriginKey(origin)) ?? null;
|
|
105
|
+
const timestampMs = Math.max(liveSession?.lastActivityMs ?? 0, codingSession ? codingSessionTimestampMs(codingSession) : 0, obligation ? obligationTimestampMs(obligation) : 0);
|
|
106
|
+
const activeLane = codingSession
|
|
107
|
+
? formatCodingLaneLabel(codingSession)
|
|
108
|
+
: obligation?.currentSurface?.label?.trim() || "this live thread";
|
|
109
|
+
const artifact = formatOtherSessionArtifact(obligation, codingSession);
|
|
110
|
+
const nextAction = formatOtherSessionNextAction(obligation, codingSession);
|
|
111
|
+
const status = obligation?.status ?? codingSession?.status ?? "active";
|
|
112
|
+
const friendName = findFriendNameForOrigin(frame, origin);
|
|
113
|
+
return {
|
|
114
|
+
timestampMs,
|
|
115
|
+
line: `- ${friendName}/${origin.channel}/${origin.key}: [${status}] ${activeLane}; artifact ${artifact}; next ${nextAction}`,
|
|
116
|
+
};
|
|
117
|
+
}).sort((left, right) => right.timestampMs - left.timestampMs);
|
|
118
|
+
return summaries.length > 0 ? summaries.map((entry) => entry.line) : ["- none"];
|
|
119
|
+
}
|
|
120
|
+
function findStatusObligation(frame) {
|
|
121
|
+
if (!frame)
|
|
122
|
+
return null;
|
|
123
|
+
const openObligations = [...(frame.pendingObligations ?? [])]
|
|
124
|
+
.filter((obligation) => obligation.status !== "fulfilled")
|
|
125
|
+
.sort(newestObligationFirst);
|
|
126
|
+
const sameSession = openObligations.find((obligation) => matchesCurrentSession(frame, obligation));
|
|
127
|
+
if (sameSession)
|
|
128
|
+
return sameSession;
|
|
129
|
+
return openObligations[0] ?? null;
|
|
130
|
+
}
|
|
131
|
+
function findCurrentSessionStatusObligation(frame) {
|
|
132
|
+
const openObligations = [...(frame.pendingObligations ?? [])]
|
|
133
|
+
.filter((obligation) => obligation.status !== "fulfilled")
|
|
134
|
+
.sort(newestObligationFirst);
|
|
135
|
+
return openObligations.find((obligation) => matchesCurrentSession(frame, obligation)) ?? null;
|
|
136
|
+
}
|
|
12
137
|
function renderActiveObligationSteering(obligation) {
|
|
13
138
|
(0, runtime_1.emitNervesEvent)({
|
|
14
139
|
component: "mind",
|
|
@@ -30,6 +155,24 @@ i'm already working on something i owe ${name}.${surfaceLine}
|
|
|
30
155
|
|
|
31
156
|
i should close that loop before i act like this is a fresh blank turn.`;
|
|
32
157
|
}
|
|
158
|
+
function mergeArtifactFallback(obligation) {
|
|
159
|
+
const trimmed = obligation.content.trim();
|
|
160
|
+
if (!trimmed)
|
|
161
|
+
return "the fix";
|
|
162
|
+
const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
|
|
163
|
+
return stripped || "the fix";
|
|
164
|
+
}
|
|
165
|
+
function formatMergeArtifact(obligation) {
|
|
166
|
+
const currentArtifact = obligation.currentArtifact?.trim();
|
|
167
|
+
if (currentArtifact)
|
|
168
|
+
return currentArtifact;
|
|
169
|
+
if (obligation.currentSurface?.kind === "merge") {
|
|
170
|
+
const surfaceLabel = obligation.currentSurface.label.trim();
|
|
171
|
+
if (surfaceLabel)
|
|
172
|
+
return surfaceLabel;
|
|
173
|
+
}
|
|
174
|
+
return mergeArtifactFallback(obligation);
|
|
175
|
+
}
|
|
33
176
|
function formatActiveLane(frame, obligation) {
|
|
34
177
|
const liveCodingSession = frame.codingSessions?.[0];
|
|
35
178
|
if (liveCodingSession) {
|
|
@@ -44,16 +187,31 @@ function formatActiveLane(frame, obligation) {
|
|
|
44
187
|
? `${liveCodingSession.runner} ${liveCodingSession.id} for ${liveCodingSession.originSession.channel}/${liveCodingSession.originSession.key}`
|
|
45
188
|
: `${liveCodingSession.runner} ${liveCodingSession.id}`;
|
|
46
189
|
}
|
|
47
|
-
return obligation.currentSurface?.label
|
|
190
|
+
return obligation.currentSurface?.label
|
|
191
|
+
|| (frame.currentObligation?.trim() ? "this same thread" : "this live loop");
|
|
48
192
|
}
|
|
49
193
|
function formatCurrentArtifact(frame, obligation) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
194
|
+
if (obligation?.currentArtifact)
|
|
195
|
+
return obligation.currentArtifact;
|
|
196
|
+
if (obligation?.currentSurface?.kind === "merge")
|
|
197
|
+
return obligation.currentSurface.label;
|
|
198
|
+
if (frame.currentObligation?.trim())
|
|
199
|
+
return "no artifact yet";
|
|
200
|
+
if ((frame.codingSessions ?? []).length > 0)
|
|
201
|
+
return "no PR or merge artifact yet";
|
|
202
|
+
return obligation ? "no explicit artifact yet" : "";
|
|
203
|
+
}
|
|
204
|
+
function isStatusCheckPrompt(text) {
|
|
205
|
+
const trimmed = text?.trim();
|
|
206
|
+
if (!trimmed)
|
|
207
|
+
return false;
|
|
208
|
+
return /^(what are you doing|what(?:'|’)s your status|status|status update|what changed|where are you at|where things stand)\??$/i.test(trimmed);
|
|
53
209
|
}
|
|
54
210
|
function formatNextAction(frame, obligation) {
|
|
55
|
-
if (obligation
|
|
211
|
+
if (obligation?.nextAction)
|
|
56
212
|
return obligation.nextAction;
|
|
213
|
+
const currentObligation = frame.currentObligation?.trim() ?? "";
|
|
214
|
+
const statusCheckPrompt = isStatusCheckPrompt(currentObligation);
|
|
57
215
|
const liveCodingSession = frame.codingSessions?.[0];
|
|
58
216
|
if (liveCodingSession?.status === "waiting_input") {
|
|
59
217
|
return `answer ${liveCodingSession.runner} ${liveCodingSession.id} and continue`;
|
|
@@ -64,24 +222,91 @@ function formatNextAction(frame, obligation) {
|
|
|
64
222
|
if (liveCodingSession) {
|
|
65
223
|
return "finish the coding pass and bring the result back here";
|
|
66
224
|
}
|
|
67
|
-
if (obligation
|
|
68
|
-
return `wait for checks, merge ${
|
|
225
|
+
if (obligation?.status === "waiting_for_merge") {
|
|
226
|
+
return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
|
|
69
227
|
}
|
|
70
|
-
if (obligation
|
|
228
|
+
if (obligation?.status === "updating_runtime") {
|
|
71
229
|
return "update runtime, verify version/changelog, then re-observe";
|
|
72
230
|
}
|
|
73
|
-
|
|
231
|
+
if (currentObligation && !statusCheckPrompt) {
|
|
232
|
+
return `work on "${currentObligation}" and bring back a concrete artifact`;
|
|
233
|
+
}
|
|
234
|
+
return obligation ? "continue the active loop and bring the result back here" : "";
|
|
74
235
|
}
|
|
75
236
|
function renderConcreteStatusGuidance(frame, obligation) {
|
|
76
|
-
|
|
77
|
-
return "";
|
|
78
|
-
const activeLane = formatActiveLane(frame, obligation);
|
|
237
|
+
const activeLane = obligation ? formatActiveLane(frame, obligation) : (frame.currentObligation?.trim() ? "this same thread" : "");
|
|
79
238
|
const currentArtifact = formatCurrentArtifact(frame, obligation);
|
|
80
239
|
const nextAction = formatNextAction(frame, obligation);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
240
|
+
const liveConversation = frame.currentSession
|
|
241
|
+
? `${frame.currentSession.channel}/${frame.currentSession.key}`
|
|
242
|
+
: "";
|
|
243
|
+
if (!activeLane && !currentArtifact && !nextAction)
|
|
244
|
+
return "";
|
|
245
|
+
return `if someone asks what i'm doing or for status mid-task, i answer from these live facts instead of copying a canned block.
|
|
246
|
+
the live conversation is ${liveConversation || "not in a live conversation"}.
|
|
247
|
+
the active lane is ${activeLane}.
|
|
248
|
+
the current artifact is ${currentArtifact}.
|
|
249
|
+
if i just finished or verified something concrete in this live lane, i name that as the latest checkpoint.
|
|
250
|
+
the next action is ${nextAction}.
|
|
251
|
+
|
|
252
|
+
i use those facts to answer naturally unless this turn is an explicit direct status check, where the separate exact five-line contract applies.`;
|
|
253
|
+
}
|
|
254
|
+
function renderLiveThreadStatusShape(frame) {
|
|
255
|
+
if (!frame.currentSession)
|
|
256
|
+
return "";
|
|
257
|
+
return `if someone asks what i'm doing or for status mid-task in this live thread, i answer in these exact lines, in order, with no intro paragraph:
|
|
258
|
+
live conversation: ${frame.currentSession.channel}/${frame.currentSession.key}
|
|
259
|
+
active lane: this same thread
|
|
260
|
+
current artifact: <actual artifact or "no artifact yet">
|
|
261
|
+
latest checkpoint: <freshest concrete thing i just finished or verified>
|
|
262
|
+
next action: <smallest concrete next step i'm taking now>
|
|
85
263
|
|
|
86
|
-
|
|
264
|
+
no recap paragraph before those lines.
|
|
265
|
+
no option list.
|
|
266
|
+
present tense only.
|
|
267
|
+
if a finished step matters, i label it "just finished" instead of presenting it as current work.`;
|
|
268
|
+
}
|
|
269
|
+
function buildExactStatusReply(frame, obligation, latestCheckpoint, statusCheckScope) {
|
|
270
|
+
const headerObligation = statusCheckScope === "all-sessions-family"
|
|
271
|
+
? findCurrentSessionStatusObligation(frame)
|
|
272
|
+
: obligation;
|
|
273
|
+
const liveConversation = frame.currentSession
|
|
274
|
+
? `${frame.currentSession.channel}/${frame.currentSession.key}`
|
|
275
|
+
: "not in a live conversation";
|
|
276
|
+
const activeLane = headerObligation
|
|
277
|
+
? formatActiveLane(frame, headerObligation)
|
|
278
|
+
: (frame.currentSession ? "this same thread" : "this live loop");
|
|
279
|
+
const currentArtifact = formatCurrentArtifact(frame, headerObligation) || "no artifact yet";
|
|
280
|
+
const nextAction = formatNextAction(frame, headerObligation) || "continue the active loop and bring the result back here";
|
|
281
|
+
const latest = latestCheckpoint.trim() || "<freshest concrete thing i just finished or verified>";
|
|
282
|
+
const lines = [
|
|
283
|
+
`live conversation: ${liveConversation}`,
|
|
284
|
+
`active lane: ${activeLane}`,
|
|
285
|
+
`current artifact: ${currentArtifact}`,
|
|
286
|
+
`latest checkpoint: ${latest}`,
|
|
287
|
+
`next action: ${nextAction}`,
|
|
288
|
+
];
|
|
289
|
+
if (statusCheckScope === "all-sessions-family") {
|
|
290
|
+
lines.push("other active sessions:");
|
|
291
|
+
lines.push(...buildOtherActiveSessionLines(frame));
|
|
292
|
+
}
|
|
293
|
+
return lines.join("\n");
|
|
294
|
+
}
|
|
295
|
+
function renderExactStatusReplyContract(frame, obligation, statusCheckScope) {
|
|
296
|
+
const headerObligation = statusCheckScope === "all-sessions-family"
|
|
297
|
+
? findCurrentSessionStatusObligation(frame)
|
|
298
|
+
: obligation;
|
|
299
|
+
if (statusCheckScope === "all-sessions-family") {
|
|
300
|
+
return `reply using exactly this status shape and nothing else:
|
|
301
|
+
live conversation: ${frame.currentSession ? `${frame.currentSession.channel}/${frame.currentSession.key}` : "not in a live conversation"}
|
|
302
|
+
active lane: ${headerObligation ? formatActiveLane(frame, headerObligation) : (frame.currentSession ? "this same thread" : "this live loop")}
|
|
303
|
+
current artifact: ${formatCurrentArtifact(frame, headerObligation) || "no artifact yet"}
|
|
304
|
+
latest checkpoint: <freshest concrete thing i just finished or verified>
|
|
305
|
+
next action: ${formatNextAction(frame, headerObligation) || "continue the active loop and bring the result back here"}
|
|
306
|
+
other active sessions:
|
|
307
|
+
- <session label>: <what i'm doing there right now>`;
|
|
308
|
+
}
|
|
309
|
+
return `reply using exactly these five lines and nothing else:
|
|
310
|
+
${buildExactStatusReply(frame, obligation, "<freshest concrete thing i just finished or verified>")}
|
|
311
|
+
`;
|
|
87
312
|
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -464,15 +464,18 @@ function centerOfGravitySteeringSection(channel, options) {
|
|
|
464
464
|
if (!frame)
|
|
465
465
|
return "";
|
|
466
466
|
const cog = frame.centerOfGravity;
|
|
467
|
-
if (cog === "local-turn")
|
|
468
|
-
return "";
|
|
469
467
|
const job = frame.inner?.job;
|
|
470
468
|
const activeObligation = (0, obligation_steering_1.findActivePersistentObligation)(frame);
|
|
469
|
+
const statusObligation = (0, obligation_steering_1.findStatusObligation)(frame);
|
|
470
|
+
const genericConcreteStatus = (0, obligation_steering_1.renderConcreteStatusGuidance)(frame, statusObligation);
|
|
471
|
+
if (cog === "local-turn") {
|
|
472
|
+
return genericConcreteStatus || (0, obligation_steering_1.renderLiveThreadStatusShape)(frame);
|
|
473
|
+
}
|
|
471
474
|
if (cog === "inward-work") {
|
|
472
475
|
if (activeObligation) {
|
|
473
476
|
return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}
|
|
474
477
|
|
|
475
|
-
${
|
|
478
|
+
${genericConcreteStatus}`;
|
|
476
479
|
}
|
|
477
480
|
if (job?.status === "queued" || job?.status === "running") {
|
|
478
481
|
const originClause = job.origin
|
|
@@ -514,6 +517,9 @@ i already have coding work running in ${liveCodingSession.runner} ${liveCodingSe
|
|
|
514
517
|
|
|
515
518
|
i should orient around that live lane first, then decide what still needs to come back here.`;
|
|
516
519
|
}
|
|
520
|
+
if (genericConcreteStatus) {
|
|
521
|
+
return genericConcreteStatus;
|
|
522
|
+
}
|
|
517
523
|
return `## where my attention is
|
|
518
524
|
i have unfinished work that needs attention before i move on.
|
|
519
525
|
|
|
@@ -529,6 +535,17 @@ i should keep the different sides aligned. what i learn here may matter there, a
|
|
|
529
535
|
/* v8 ignore next -- unreachable: all center-of-gravity modes covered above @preserve */
|
|
530
536
|
return "";
|
|
531
537
|
}
|
|
538
|
+
function statusCheckSection(channel, options) {
|
|
539
|
+
if (channel === "inner" || !options?.statusCheckRequested)
|
|
540
|
+
return "";
|
|
541
|
+
const frame = options.activeWorkFrame;
|
|
542
|
+
if (!frame)
|
|
543
|
+
return "";
|
|
544
|
+
const activeObligation = (0, obligation_steering_1.findStatusObligation)(frame);
|
|
545
|
+
return `## status question on this turn
|
|
546
|
+
the user is asking for current status right now.
|
|
547
|
+
${(0, obligation_steering_1.renderExactStatusReplyContract)(frame, activeObligation, options.statusCheckScope)}`;
|
|
548
|
+
}
|
|
532
549
|
function commitmentsSection(options) {
|
|
533
550
|
if (!options?.activeWorkFrame)
|
|
534
551
|
return "";
|
|
@@ -736,6 +753,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
736
753
|
skillsSection(),
|
|
737
754
|
taskBoardSection(),
|
|
738
755
|
activeWorkSection(options),
|
|
756
|
+
statusCheckSection(channel, options),
|
|
739
757
|
centerOfGravitySteeringSection(channel, options),
|
|
740
758
|
commitmentsSection(options),
|
|
741
759
|
delegationHintSection(options),
|
|
@@ -115,16 +115,20 @@ function deriveObligationMilestone(update) {
|
|
|
115
115
|
return null;
|
|
116
116
|
}
|
|
117
117
|
function isSafeProgressSnippet(snippet) {
|
|
118
|
+
const normalized = snippet.trim();
|
|
118
119
|
const wordCount = snippet.split(/\s+/).filter(Boolean).length;
|
|
119
|
-
return (
|
|
120
|
+
return (normalized.length <= 80
|
|
121
|
+
&& wordCount >= 2
|
|
120
122
|
&& wordCount <= 8
|
|
121
|
-
&&
|
|
122
|
-
&& !
|
|
123
|
-
&&
|
|
124
|
-
&&
|
|
125
|
-
&& !/^
|
|
126
|
-
&& !/^
|
|
127
|
-
&& !/^
|
|
123
|
+
&& /[A-Za-z]{3,}/.test(normalized)
|
|
124
|
+
&& !normalized.includes(":")
|
|
125
|
+
&& !/[{}\[\]();]/.test(normalized)
|
|
126
|
+
&& !normalized.startsWith("**")
|
|
127
|
+
&& !/^Respond with\b/i.test(normalized)
|
|
128
|
+
&& !/^Coding session metadata\b/i.test(normalized)
|
|
129
|
+
&& !/^sessionId\b/i.test(normalized)
|
|
130
|
+
&& !/^taskRef\b/i.test(normalized)
|
|
131
|
+
&& !/^parentAgent\b/i.test(normalized));
|
|
128
132
|
}
|
|
129
133
|
function pickUpdateSnippet(update) {
|
|
130
134
|
return (lastMeaningfulLine(update.text)
|
|
@@ -99,6 +99,9 @@ function selectCodingStatusSessions(sessions, currentSession) {
|
|
|
99
99
|
}
|
|
100
100
|
return [...sessions].sort(latestSessionFirst);
|
|
101
101
|
}
|
|
102
|
+
function buildCodingObligationContent(taskRef) {
|
|
103
|
+
return `finish ${taskRef} and bring the result back`;
|
|
104
|
+
}
|
|
102
105
|
const codingSpawnTool = {
|
|
103
106
|
type: "function",
|
|
104
107
|
function: {
|
|
@@ -231,6 +234,13 @@ exports.codingToolDefinitions = [
|
|
|
231
234
|
}
|
|
232
235
|
return JSON.stringify({ ...existingSession, reused: true });
|
|
233
236
|
}
|
|
237
|
+
if (request.originSession && !request.obligationId) {
|
|
238
|
+
const created = (0, obligations_1.createObligation)((0, identity_1.getAgentRoot)(), {
|
|
239
|
+
origin: request.originSession,
|
|
240
|
+
content: buildCodingObligationContent(taskRef),
|
|
241
|
+
});
|
|
242
|
+
request.obligationId = created.id;
|
|
243
|
+
}
|
|
234
244
|
const session = await manager.spawnSession(request);
|
|
235
245
|
if (session.obligationId) {
|
|
236
246
|
(0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), session.obligationId, {
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -38,6 +38,29 @@ function emptyTaskBoard() {
|
|
|
38
38
|
activeBridges: [],
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
+
const STATUS_CHECK_PATTERNS = [
|
|
42
|
+
/^\s*what are you doing\??\s*$/i,
|
|
43
|
+
/^\s*what'?s your status\??\s*$/i,
|
|
44
|
+
/^\s*status\??\s*$/i,
|
|
45
|
+
/^\s*status update\??\s*$/i,
|
|
46
|
+
/^\s*what changed\??\s*$/i,
|
|
47
|
+
/^\s*where (?:are you at|things stand)\??\s*$/i,
|
|
48
|
+
];
|
|
49
|
+
function isStatusCheckRequested(ingressTexts) {
|
|
50
|
+
const latest = ingressTexts
|
|
51
|
+
?.map((text) => text.trim())
|
|
52
|
+
.filter((text) => text.length > 0)
|
|
53
|
+
.at(-1);
|
|
54
|
+
if (!latest)
|
|
55
|
+
return false;
|
|
56
|
+
return STATUS_CHECK_PATTERNS.some((pattern) => pattern.test(latest));
|
|
57
|
+
}
|
|
58
|
+
function isLiveCodingSessionStatus(status) {
|
|
59
|
+
return status === "spawning"
|
|
60
|
+
|| status === "running"
|
|
61
|
+
|| status === "waiting_input"
|
|
62
|
+
|| status === "stalled";
|
|
63
|
+
}
|
|
41
64
|
function readInnerWorkState() {
|
|
42
65
|
const defaultJob = {
|
|
43
66
|
status: "idle",
|
|
@@ -188,23 +211,21 @@ async function handleInboundTurn(input) {
|
|
|
188
211
|
pendingObligations = [];
|
|
189
212
|
}
|
|
190
213
|
let codingSessions = [];
|
|
214
|
+
let otherCodingSessions = [];
|
|
191
215
|
try {
|
|
192
|
-
|
|
216
|
+
const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
|
|
193
217
|
.listSessions()
|
|
194
|
-
.filter((session) =>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return session.originSession.friendId === currentSession.friendId
|
|
202
|
-
&& session.originSession.channel === currentSession.channel
|
|
203
|
-
&& session.originSession.key === currentSession.key;
|
|
204
|
-
});
|
|
218
|
+
.filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
|
|
219
|
+
codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
|
|
220
|
+
&& session.originSession.channel === currentSession.channel
|
|
221
|
+
&& session.originSession.key === currentSession.key);
|
|
222
|
+
otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
|
|
223
|
+
&& session.originSession.channel === currentSession.channel
|
|
224
|
+
&& session.originSession.key === currentSession.key));
|
|
205
225
|
}
|
|
206
226
|
catch {
|
|
207
227
|
codingSessions = [];
|
|
228
|
+
otherCodingSessions = [];
|
|
208
229
|
}
|
|
209
230
|
const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
|
|
210
231
|
currentSession,
|
|
@@ -213,6 +234,7 @@ async function handleInboundTurn(input) {
|
|
|
213
234
|
inner: readInnerWorkState(),
|
|
214
235
|
bridges: activeBridges,
|
|
215
236
|
codingSessions,
|
|
237
|
+
otherCodingSessions,
|
|
216
238
|
pendingObligations,
|
|
217
239
|
taskBoard: (() => {
|
|
218
240
|
try {
|
|
@@ -271,6 +293,10 @@ async function handleInboundTurn(input) {
|
|
|
271
293
|
}
|
|
272
294
|
// Step 5: runAgent
|
|
273
295
|
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
296
|
+
const statusCheckRequested = isStatusCheckRequested(input.continuityIngressTexts);
|
|
297
|
+
const statusCheckScope = statusCheckRequested && resolvedContext.friend.trustLevel === "family"
|
|
298
|
+
? "all-sessions-family"
|
|
299
|
+
: undefined;
|
|
274
300
|
const runAgentOptions = {
|
|
275
301
|
...input.runAgentOptions,
|
|
276
302
|
bridgeContext,
|
|
@@ -278,6 +304,9 @@ async function handleInboundTurn(input) {
|
|
|
278
304
|
delegationDecision,
|
|
279
305
|
currentSessionKey: currentSession.key,
|
|
280
306
|
currentObligation,
|
|
307
|
+
statusCheckRequested,
|
|
308
|
+
statusCheckScope,
|
|
309
|
+
toolChoiceRequired: statusCheckRequested ? true : input.runAgentOptions?.toolChoiceRequired,
|
|
281
310
|
mustResolveBeforeHandoff,
|
|
282
311
|
setMustResolveBeforeHandoff: (value) => {
|
|
283
312
|
mustResolveBeforeHandoff = value;
|