@ouro.bot/cli 0.1.0-alpha.101 → 0.1.0-alpha.103
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 +102 -0
- package/dist/heart/core.js +51 -9
- package/dist/heart/obligations.js +6 -0
- 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 +162 -0
- package/dist/mind/prompt.js +23 -3
- package/dist/repertoire/coding/feedback.js +92 -12
- package/dist/repertoire/coding/tools.js +10 -0
- package/dist/senses/pipeline.js +20 -0
- 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.103",
|
|
6
|
+
"changes": [
|
|
7
|
+
"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.",
|
|
8
|
+
"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."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"version": "0.1.0-alpha.102",
|
|
13
|
+
"changes": [
|
|
14
|
+
"Live obligations now persist a concrete current artifact and next action, so status answers can anchor on the actual active lane, artifact, and immediate step instead of drifting into broad mission statements.",
|
|
15
|
+
"Obligation-bound coding feedback now turns PR milestones into structured report-backs and lifecycle updates, which keeps the originating live conversation informed when child work opens a PR, waits for merge, or needs a runtime update."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.101",
|
|
6
20
|
"changes": [
|
|
@@ -63,6 +63,88 @@ 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
|
+
}
|
|
84
|
+
function findPrimaryOpenObligation(frame) {
|
|
85
|
+
return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled")
|
|
86
|
+
?? (frame.pendingObligations ?? []).find(obligations_1.isOpenObligation)
|
|
87
|
+
?? null;
|
|
88
|
+
}
|
|
89
|
+
function formatActiveLane(frame, obligation) {
|
|
90
|
+
const liveCodingSession = frame.codingSessions?.[0];
|
|
91
|
+
if (liveCodingSession) {
|
|
92
|
+
return `${formatCodingLaneLabel(liveCodingSession)}${describeCodingSessionScope(liveCodingSession, frame.currentSession)}`;
|
|
93
|
+
}
|
|
94
|
+
if (obligation?.currentSurface?.label) {
|
|
95
|
+
return obligation.currentSurface.label;
|
|
96
|
+
}
|
|
97
|
+
if (frame.inner?.job?.status === "running") {
|
|
98
|
+
return "inner dialog";
|
|
99
|
+
}
|
|
100
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0 && frame.currentSession) {
|
|
101
|
+
return "this same thread";
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function formatCurrentArtifact(frame, obligation) {
|
|
106
|
+
if (obligation?.currentArtifact?.trim()) {
|
|
107
|
+
return obligation.currentArtifact.trim();
|
|
108
|
+
}
|
|
109
|
+
if (obligation?.currentSurface?.kind === "merge" && obligation.currentSurface.label.trim()) {
|
|
110
|
+
return obligation.currentSurface.label.trim();
|
|
111
|
+
}
|
|
112
|
+
if ((frame.codingSessions ?? []).length > 0) {
|
|
113
|
+
return "no PR or merge artifact yet";
|
|
114
|
+
}
|
|
115
|
+
if (typeof frame.currentObligation === "string" && frame.currentObligation.trim().length > 0) {
|
|
116
|
+
return "no artifact yet";
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function formatNextAction(frame, obligation) {
|
|
121
|
+
if (obligation?.nextAction?.trim()) {
|
|
122
|
+
return obligation.nextAction.trim();
|
|
123
|
+
}
|
|
124
|
+
const liveCodingSession = frame.codingSessions?.[0];
|
|
125
|
+
if (liveCodingSession?.status === "waiting_input") {
|
|
126
|
+
return `answer ${formatCodingLaneLabel(liveCodingSession)} and continue`;
|
|
127
|
+
}
|
|
128
|
+
if (liveCodingSession?.status === "stalled") {
|
|
129
|
+
return `unstick ${formatCodingLaneLabel(liveCodingSession)} and continue`;
|
|
130
|
+
}
|
|
131
|
+
if (liveCodingSession) {
|
|
132
|
+
return "finish the coding pass and bring the result back here";
|
|
133
|
+
}
|
|
134
|
+
if (obligation?.status === "waiting_for_merge") {
|
|
135
|
+
return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
|
|
136
|
+
}
|
|
137
|
+
if (obligation?.status === "updating_runtime") {
|
|
138
|
+
return "update runtime, verify version/changelog, then re-observe";
|
|
139
|
+
}
|
|
140
|
+
if (obligation) {
|
|
141
|
+
return "continue the active loop and bring the result back here";
|
|
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
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
66
148
|
function suggestBridgeForActiveWork(input) {
|
|
67
149
|
const targetCandidates = (input.targetCandidates ?? [])
|
|
68
150
|
.filter((candidate) => {
|
|
@@ -173,6 +255,10 @@ function buildActiveWorkFrame(input) {
|
|
|
173
255
|
}
|
|
174
256
|
function formatActiveWorkFrame(frame) {
|
|
175
257
|
const lines = ["## what i'm holding"];
|
|
258
|
+
const primaryObligation = findPrimaryOpenObligation(frame);
|
|
259
|
+
const activeLane = formatActiveLane(frame, primaryObligation);
|
|
260
|
+
const currentArtifact = formatCurrentArtifact(frame, primaryObligation);
|
|
261
|
+
const nextAction = formatNextAction(frame, primaryObligation);
|
|
176
262
|
// Session line
|
|
177
263
|
if (frame.currentSession) {
|
|
178
264
|
let sessionLine = `i'm in a conversation on ${formatSessionLabel(frame.currentSession)}.`;
|
|
@@ -189,6 +275,22 @@ function formatActiveWorkFrame(frame) {
|
|
|
189
275
|
lines.push("");
|
|
190
276
|
lines.push("i'm not in a conversation right now.");
|
|
191
277
|
}
|
|
278
|
+
if (activeLane || currentArtifact || nextAction) {
|
|
279
|
+
lines.push("");
|
|
280
|
+
lines.push("## current concrete state");
|
|
281
|
+
if (frame.currentSession) {
|
|
282
|
+
lines.push(`- live conversation: ${formatSessionLabel(frame.currentSession)}`);
|
|
283
|
+
}
|
|
284
|
+
if (activeLane) {
|
|
285
|
+
lines.push(`- active lane: ${activeLane}`);
|
|
286
|
+
}
|
|
287
|
+
if (currentArtifact) {
|
|
288
|
+
lines.push(`- current artifact: ${currentArtifact}`);
|
|
289
|
+
}
|
|
290
|
+
if (nextAction) {
|
|
291
|
+
lines.push(`- next action: ${nextAction}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
192
294
|
// Inner status block
|
|
193
295
|
const job = frame.inner?.job;
|
|
194
296
|
if (job) {
|
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,37 @@ function getFinalAnswerRetryError(mustResolveBeforeHandoff, intent, sawSteeringF
|
|
|
272
273
|
}
|
|
273
274
|
return null;
|
|
274
275
|
}
|
|
276
|
+
function hasExactStatusReplyShape(answer) {
|
|
277
|
+
const lines = answer.trimEnd().split(/\r?\n/);
|
|
278
|
+
if (lines.length !== 5)
|
|
279
|
+
return false;
|
|
280
|
+
return (/^live conversation:\s+\S.+$/i.test(lines[0])
|
|
281
|
+
&& /^active lane:\s+\S.+$/i.test(lines[1])
|
|
282
|
+
&& /^current artifact:\s+\S.+$/i.test(lines[2])
|
|
283
|
+
&& /^latest checkpoint:\s+\S.+$/i.test(lines[3])
|
|
284
|
+
&& /^next action:\s+\S.+$/i.test(lines[4]));
|
|
285
|
+
}
|
|
286
|
+
function extractLatestCheckpoint(answer) {
|
|
287
|
+
const latestLine = answer.trimEnd().split(/\r?\n/)[3];
|
|
288
|
+
return latestLine.replace(/^latest checkpoint:\s*/i, "").trim();
|
|
289
|
+
}
|
|
290
|
+
function getStatusReplyRetryError(answer, statusCheckRequested, activeWorkFrame) {
|
|
291
|
+
if (!statusCheckRequested || answer == null)
|
|
292
|
+
return null;
|
|
293
|
+
if (hasExactStatusReplyShape(answer))
|
|
294
|
+
return null;
|
|
295
|
+
if (!activeWorkFrame) {
|
|
296
|
+
return `the user asked for current status. call final_answer again using exactly these five non-empty lines and nothing else:
|
|
297
|
+
live conversation: ...
|
|
298
|
+
active lane: ...
|
|
299
|
+
current artifact: ...
|
|
300
|
+
latest checkpoint: ...
|
|
301
|
+
next action: ...`;
|
|
302
|
+
}
|
|
303
|
+
return `the user asked for current status right now.
|
|
304
|
+
${(0, obligation_steering_1.renderExactStatusReplyContract)(activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(activeWorkFrame))}
|
|
305
|
+
call final_answer again using that exact five-line shape and nothing else.`;
|
|
306
|
+
}
|
|
275
307
|
// Re-export kick utilities for backward compat
|
|
276
308
|
var kicks_1 = require("./kicks");
|
|
277
309
|
Object.defineProperty(exports, "hasToolIntent", { enumerable: true, get: function () { return kicks_1.hasToolIntent; } });
|
|
@@ -551,6 +583,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
551
583
|
traceId,
|
|
552
584
|
toolChoiceRequired,
|
|
553
585
|
reasoningEffort: currentReasoningEffort,
|
|
586
|
+
eagerFinalAnswerStreaming: !options?.statusCheckRequested,
|
|
554
587
|
});
|
|
555
588
|
// Track usage from the latest API call
|
|
556
589
|
if (result.usage)
|
|
@@ -599,15 +632,21 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
599
632
|
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
600
633
|
const { answer, intent } = parseFinalAnswerPayload(result.toolCalls[0].arguments);
|
|
601
634
|
const retryError = getFinalAnswerRetryError(mustResolveBeforeHandoffActive, intent, sawSteeringFollowUp, options?.delegationDecision, sawSendMessageSelf, sawGoInward, sawQuerySession, options?.currentObligation ?? null, options?.activeWorkFrame?.inner?.job, sawExternalStateQuery);
|
|
635
|
+
const statusReplyRetryError = getStatusReplyRetryError(answer, options?.statusCheckRequested, options?.activeWorkFrame);
|
|
636
|
+
const exactStatusReplyAccepted = Boolean(options?.statusCheckRequested) && !statusReplyRetryError && answer != null;
|
|
637
|
+
const deliveredAnswer = exactStatusReplyAccepted && options?.activeWorkFrame
|
|
638
|
+
? (0, obligation_steering_1.buildExactStatusReply)(options.activeWorkFrame, (0, obligation_steering_1.findStatusObligation)(options.activeWorkFrame), extractLatestCheckpoint(answer))
|
|
639
|
+
: answer;
|
|
602
640
|
const validDirectReply = mustResolveBeforeHandoffActive && intent === "direct_reply" && sawSteeringFollowUp;
|
|
603
641
|
const validTerminalIntent = intent === "complete" || intent === "blocked";
|
|
604
|
-
const validClosure =
|
|
605
|
-
&& !retryError
|
|
606
|
-
&&
|
|
642
|
+
const validClosure = deliveredAnswer != null
|
|
643
|
+
&& (exactStatusReplyAccepted || !retryError)
|
|
644
|
+
&& !statusReplyRetryError
|
|
645
|
+
&& (exactStatusReplyAccepted || !mustResolveBeforeHandoffActive || validDirectReply || validTerminalIntent);
|
|
607
646
|
if (validClosure) {
|
|
608
647
|
completion = {
|
|
609
|
-
answer,
|
|
610
|
-
intent: validDirectReply ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
648
|
+
answer: deliveredAnswer,
|
|
649
|
+
intent: validDirectReply && !exactStatusReplyAccepted ? "direct_reply" : intent === "blocked" ? "blocked" : "complete",
|
|
611
650
|
};
|
|
612
651
|
if (result.finalAnswerStreamed) {
|
|
613
652
|
// The streaming layer already parsed and emitted the answer
|
|
@@ -619,10 +658,10 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
619
658
|
callbacks.onClearText?.();
|
|
620
659
|
// Emit the answer through the callback pipeline so channels receive it.
|
|
621
660
|
// Never truncate -- channel adapters handle splitting long messages.
|
|
622
|
-
callbacks.onTextChunk(
|
|
661
|
+
callbacks.onTextChunk(deliveredAnswer);
|
|
623
662
|
}
|
|
624
663
|
messages.push(msg);
|
|
625
|
-
if (validDirectReply) {
|
|
664
|
+
if (validDirectReply && !exactStatusReplyAccepted) {
|
|
626
665
|
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
666
|
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: resumeWork });
|
|
628
667
|
providerRuntime.appendToolOutput(result.toolCalls[0].id, resumeWork);
|
|
@@ -641,8 +680,11 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
641
680
|
// assistant msg + error tool result and let the model try again.
|
|
642
681
|
callbacks.onClearText?.();
|
|
643
682
|
messages.push(msg);
|
|
644
|
-
|
|
645
|
-
|
|
683
|
+
const toolRetryMessage = statusReplyRetryError
|
|
684
|
+
?? retryError
|
|
685
|
+
?? "your final_answer was incomplete or malformed. call final_answer again with your complete response.";
|
|
686
|
+
messages.push({ role: "tool", tool_call_id: result.toolCalls[0].id, content: toolRetryMessage });
|
|
687
|
+
providerRuntime.appendToolOutput(result.toolCalls[0].id, toolRetryMessage);
|
|
646
688
|
}
|
|
647
689
|
continue;
|
|
648
690
|
}
|
|
@@ -140,6 +140,12 @@ function advanceObligation(agentRoot, obligationId, update) {
|
|
|
140
140
|
if (update.currentSurface) {
|
|
141
141
|
obligation.currentSurface = update.currentSurface;
|
|
142
142
|
}
|
|
143
|
+
if (typeof update.currentArtifact === "string") {
|
|
144
|
+
obligation.currentArtifact = update.currentArtifact;
|
|
145
|
+
}
|
|
146
|
+
if (typeof update.nextAction === "string") {
|
|
147
|
+
obligation.nextAction = update.nextAction;
|
|
148
|
+
}
|
|
143
149
|
if (typeof update.latestNote === "string") {
|
|
144
150
|
obligation.latestNote = update.latestNote;
|
|
145
151
|
}
|
|
@@ -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,13 +1,41 @@
|
|
|
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;
|
|
6
|
+
exports.renderConcreteStatusGuidance = renderConcreteStatusGuidance;
|
|
7
|
+
exports.renderLiveThreadStatusShape = renderLiveThreadStatusShape;
|
|
8
|
+
exports.buildExactStatusReply = buildExactStatusReply;
|
|
9
|
+
exports.renderExactStatusReplyContract = renderExactStatusReplyContract;
|
|
5
10
|
const runtime_1 = require("../nerves/runtime");
|
|
6
11
|
function findActivePersistentObligation(frame) {
|
|
7
12
|
if (!frame)
|
|
8
13
|
return null;
|
|
9
14
|
return (frame.pendingObligations ?? []).find((ob) => ob.status !== "pending" && ob.status !== "fulfilled") ?? null;
|
|
10
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 Boolean(frame.currentSession
|
|
24
|
+
&& obligation.origin.friendId === frame.currentSession.friendId
|
|
25
|
+
&& obligation.origin.channel === frame.currentSession.channel
|
|
26
|
+
&& obligation.origin.key === frame.currentSession.key);
|
|
27
|
+
}
|
|
28
|
+
function findStatusObligation(frame) {
|
|
29
|
+
if (!frame)
|
|
30
|
+
return null;
|
|
31
|
+
const openObligations = [...(frame.pendingObligations ?? [])]
|
|
32
|
+
.filter((obligation) => obligation.status !== "fulfilled")
|
|
33
|
+
.sort(newestObligationFirst);
|
|
34
|
+
const sameSession = openObligations.find((obligation) => matchesCurrentSession(frame, obligation));
|
|
35
|
+
if (sameSession)
|
|
36
|
+
return sameSession;
|
|
37
|
+
return openObligations[0] ?? null;
|
|
38
|
+
}
|
|
11
39
|
function renderActiveObligationSteering(obligation) {
|
|
12
40
|
(0, runtime_1.emitNervesEvent)({
|
|
13
41
|
component: "mind",
|
|
@@ -29,3 +57,137 @@ i'm already working on something i owe ${name}.${surfaceLine}
|
|
|
29
57
|
|
|
30
58
|
i should close that loop before i act like this is a fresh blank turn.`;
|
|
31
59
|
}
|
|
60
|
+
function mergeArtifactFallback(obligation) {
|
|
61
|
+
const trimmed = obligation.content.trim();
|
|
62
|
+
if (!trimmed)
|
|
63
|
+
return "the fix";
|
|
64
|
+
const stripped = trimmed.replace(/^merge(?:\s+|$)/i, "").trim();
|
|
65
|
+
return stripped || "the fix";
|
|
66
|
+
}
|
|
67
|
+
function formatMergeArtifact(obligation) {
|
|
68
|
+
const currentArtifact = obligation.currentArtifact?.trim();
|
|
69
|
+
if (currentArtifact)
|
|
70
|
+
return currentArtifact;
|
|
71
|
+
if (obligation.currentSurface?.kind === "merge") {
|
|
72
|
+
const surfaceLabel = obligation.currentSurface.label.trim();
|
|
73
|
+
if (surfaceLabel)
|
|
74
|
+
return surfaceLabel;
|
|
75
|
+
}
|
|
76
|
+
return mergeArtifactFallback(obligation);
|
|
77
|
+
}
|
|
78
|
+
function formatActiveLane(frame, obligation) {
|
|
79
|
+
const liveCodingSession = frame.codingSessions?.[0];
|
|
80
|
+
if (liveCodingSession) {
|
|
81
|
+
const sameThread = frame.currentSession
|
|
82
|
+
&& liveCodingSession.originSession
|
|
83
|
+
&& liveCodingSession.originSession.friendId === frame.currentSession.friendId
|
|
84
|
+
&& liveCodingSession.originSession.channel === frame.currentSession.channel
|
|
85
|
+
&& liveCodingSession.originSession.key === frame.currentSession.key;
|
|
86
|
+
return sameThread
|
|
87
|
+
? `${liveCodingSession.runner} ${liveCodingSession.id} for this same thread`
|
|
88
|
+
: liveCodingSession.originSession
|
|
89
|
+
? `${liveCodingSession.runner} ${liveCodingSession.id} for ${liveCodingSession.originSession.channel}/${liveCodingSession.originSession.key}`
|
|
90
|
+
: `${liveCodingSession.runner} ${liveCodingSession.id}`;
|
|
91
|
+
}
|
|
92
|
+
return obligation.currentSurface?.label
|
|
93
|
+
|| (frame.currentObligation?.trim() ? "this same thread" : "this live loop");
|
|
94
|
+
}
|
|
95
|
+
function formatCurrentArtifact(frame, obligation) {
|
|
96
|
+
if (obligation?.currentArtifact)
|
|
97
|
+
return obligation.currentArtifact;
|
|
98
|
+
if (obligation?.currentSurface?.kind === "merge")
|
|
99
|
+
return obligation.currentSurface.label;
|
|
100
|
+
if (frame.currentObligation?.trim())
|
|
101
|
+
return "no artifact yet";
|
|
102
|
+
if ((frame.codingSessions ?? []).length > 0)
|
|
103
|
+
return "no PR or merge artifact yet";
|
|
104
|
+
return obligation ? "no explicit artifact yet" : "";
|
|
105
|
+
}
|
|
106
|
+
function isStatusCheckPrompt(text) {
|
|
107
|
+
const trimmed = text?.trim();
|
|
108
|
+
if (!trimmed)
|
|
109
|
+
return false;
|
|
110
|
+
return /^(what are you doing|what(?:'|’)s your status|status|status update|what changed|where are you at|where things stand)\??$/i.test(trimmed);
|
|
111
|
+
}
|
|
112
|
+
function formatNextAction(frame, obligation) {
|
|
113
|
+
if (obligation?.nextAction)
|
|
114
|
+
return obligation.nextAction;
|
|
115
|
+
const currentObligation = frame.currentObligation?.trim() ?? "";
|
|
116
|
+
const statusCheckPrompt = isStatusCheckPrompt(currentObligation);
|
|
117
|
+
const liveCodingSession = frame.codingSessions?.[0];
|
|
118
|
+
if (liveCodingSession?.status === "waiting_input") {
|
|
119
|
+
return `answer ${liveCodingSession.runner} ${liveCodingSession.id} and continue`;
|
|
120
|
+
}
|
|
121
|
+
if (liveCodingSession?.status === "stalled") {
|
|
122
|
+
return `unstick ${liveCodingSession.runner} ${liveCodingSession.id} and continue`;
|
|
123
|
+
}
|
|
124
|
+
if (liveCodingSession) {
|
|
125
|
+
return "finish the coding pass and bring the result back here";
|
|
126
|
+
}
|
|
127
|
+
if (obligation?.status === "waiting_for_merge") {
|
|
128
|
+
return `wait for checks, merge ${formatMergeArtifact(obligation)}, then update runtime`;
|
|
129
|
+
}
|
|
130
|
+
if (obligation?.status === "updating_runtime") {
|
|
131
|
+
return "update runtime, verify version/changelog, then re-observe";
|
|
132
|
+
}
|
|
133
|
+
if (currentObligation && !statusCheckPrompt) {
|
|
134
|
+
return `work on "${currentObligation}" and bring back a concrete artifact`;
|
|
135
|
+
}
|
|
136
|
+
return obligation ? "continue the active loop and bring the result back here" : "";
|
|
137
|
+
}
|
|
138
|
+
function renderConcreteStatusGuidance(frame, obligation) {
|
|
139
|
+
const activeLane = obligation ? formatActiveLane(frame, obligation) : (frame.currentObligation?.trim() ? "this same thread" : "");
|
|
140
|
+
const currentArtifact = formatCurrentArtifact(frame, obligation);
|
|
141
|
+
const nextAction = formatNextAction(frame, obligation);
|
|
142
|
+
const liveConversation = frame.currentSession
|
|
143
|
+
? `${frame.currentSession.channel}/${frame.currentSession.key}`
|
|
144
|
+
: "";
|
|
145
|
+
if (!activeLane && !currentArtifact && !nextAction)
|
|
146
|
+
return "";
|
|
147
|
+
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.
|
|
148
|
+
the live conversation is ${liveConversation || "not in a live conversation"}.
|
|
149
|
+
the active lane is ${activeLane}.
|
|
150
|
+
the current artifact is ${currentArtifact}.
|
|
151
|
+
if i just finished or verified something concrete in this live lane, i name that as the latest checkpoint.
|
|
152
|
+
the next action is ${nextAction}.
|
|
153
|
+
|
|
154
|
+
i use those facts to answer naturally unless this turn is an explicit direct status check, where the separate exact five-line contract applies.`;
|
|
155
|
+
}
|
|
156
|
+
function renderLiveThreadStatusShape(frame) {
|
|
157
|
+
if (!frame.currentSession)
|
|
158
|
+
return "";
|
|
159
|
+
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:
|
|
160
|
+
live conversation: ${frame.currentSession.channel}/${frame.currentSession.key}
|
|
161
|
+
active lane: this same thread
|
|
162
|
+
current artifact: <actual artifact or "no artifact yet">
|
|
163
|
+
latest checkpoint: <freshest concrete thing i just finished or verified>
|
|
164
|
+
next action: <smallest concrete next step i'm taking now>
|
|
165
|
+
|
|
166
|
+
no recap paragraph before those lines.
|
|
167
|
+
no option list.
|
|
168
|
+
present tense only.
|
|
169
|
+
if a finished step matters, i label it "just finished" instead of presenting it as current work.`;
|
|
170
|
+
}
|
|
171
|
+
function buildExactStatusReply(frame, obligation, latestCheckpoint) {
|
|
172
|
+
const liveConversation = frame.currentSession
|
|
173
|
+
? `${frame.currentSession.channel}/${frame.currentSession.key}`
|
|
174
|
+
: "not in a live conversation";
|
|
175
|
+
const activeLane = obligation
|
|
176
|
+
? formatActiveLane(frame, obligation)
|
|
177
|
+
: (frame.currentSession ? "this same thread" : "this live loop");
|
|
178
|
+
const currentArtifact = formatCurrentArtifact(frame, obligation) || 'no artifact yet';
|
|
179
|
+
const nextAction = formatNextAction(frame, obligation) || "continue the active loop and bring the result back here";
|
|
180
|
+
const latest = latestCheckpoint.trim() || "<freshest concrete thing i just finished or verified>";
|
|
181
|
+
return [
|
|
182
|
+
`live conversation: ${liveConversation}`,
|
|
183
|
+
`active lane: ${activeLane}`,
|
|
184
|
+
`current artifact: ${currentArtifact}`,
|
|
185
|
+
`latest checkpoint: ${latest}`,
|
|
186
|
+
`next action: ${nextAction}`,
|
|
187
|
+
].join("\n");
|
|
188
|
+
}
|
|
189
|
+
function renderExactStatusReplyContract(frame, obligation) {
|
|
190
|
+
return `reply using exactly these five lines and nothing else:
|
|
191
|
+
${buildExactStatusReply(frame, obligation, "<freshest concrete thing i just finished or verified>")}
|
|
192
|
+
`;
|
|
193
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -464,13 +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
|
-
return (0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)
|
|
476
|
+
return `${(0, obligation_steering_1.renderActiveObligationSteering)(activeObligation)}
|
|
477
|
+
|
|
478
|
+
${genericConcreteStatus}`;
|
|
474
479
|
}
|
|
475
480
|
if (job?.status === "queued" || job?.status === "running") {
|
|
476
481
|
const originClause = job.origin
|
|
@@ -512,6 +517,9 @@ i already have coding work running in ${liveCodingSession.runner} ${liveCodingSe
|
|
|
512
517
|
|
|
513
518
|
i should orient around that live lane first, then decide what still needs to come back here.`;
|
|
514
519
|
}
|
|
520
|
+
if (genericConcreteStatus) {
|
|
521
|
+
return genericConcreteStatus;
|
|
522
|
+
}
|
|
515
523
|
return `## where my attention is
|
|
516
524
|
i have unfinished work that needs attention before i move on.
|
|
517
525
|
|
|
@@ -527,6 +535,17 @@ i should keep the different sides aligned. what i learn here may matter there, a
|
|
|
527
535
|
/* v8 ignore next -- unreachable: all center-of-gravity modes covered above @preserve */
|
|
528
536
|
return "";
|
|
529
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)}`;
|
|
548
|
+
}
|
|
530
549
|
function commitmentsSection(options) {
|
|
531
550
|
if (!options?.activeWorkFrame)
|
|
532
551
|
return "";
|
|
@@ -734,6 +753,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
734
753
|
skillsSection(),
|
|
735
754
|
taskBoardSection(),
|
|
736
755
|
activeWorkSection(options),
|
|
756
|
+
statusCheckSection(channel, options),
|
|
737
757
|
centerOfGravitySteeringSection(channel, options),
|
|
738
758
|
commitmentsSection(options),
|
|
739
759
|
delegationHintSection(options),
|
|
@@ -14,6 +14,8 @@ const OBLIGATION_WAKE_UPDATE_KINDS = new Set([
|
|
|
14
14
|
"failed",
|
|
15
15
|
"killed",
|
|
16
16
|
]);
|
|
17
|
+
const PULL_REQUEST_NUMBER_PATTERN = /\bPR\s*#(\d+)\b/i;
|
|
18
|
+
const PULL_REQUEST_URL_PATTERN = /\/pull\/(\d+)(?:\b|\/)?/i;
|
|
17
19
|
function clip(text, maxLength = 280) {
|
|
18
20
|
const trimmed = text.trim();
|
|
19
21
|
if (trimmed.length <= maxLength)
|
|
@@ -57,17 +59,76 @@ function formatSessionLabel(session) {
|
|
|
57
59
|
: "";
|
|
58
60
|
return `${session.runner} ${session.id}${origin}`;
|
|
59
61
|
}
|
|
62
|
+
function extractPullRequestLabel(snippet) {
|
|
63
|
+
if (!snippet)
|
|
64
|
+
return null;
|
|
65
|
+
const numberMatch = snippet.match(PULL_REQUEST_NUMBER_PATTERN);
|
|
66
|
+
if (numberMatch)
|
|
67
|
+
return `PR #${numberMatch[1]}`;
|
|
68
|
+
const urlMatch = snippet.match(PULL_REQUEST_URL_PATTERN);
|
|
69
|
+
if (urlMatch)
|
|
70
|
+
return `PR #${urlMatch[1]}`;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function isMergedPullRequestSnippet(snippet) {
|
|
74
|
+
return /\bmerged\b/i.test(snippet) || /\blanded\b/i.test(snippet);
|
|
75
|
+
}
|
|
76
|
+
function deriveObligationMilestone(update) {
|
|
77
|
+
const snippet = pickUpdateSnippet(update);
|
|
78
|
+
const pullRequest = extractPullRequestLabel(snippet);
|
|
79
|
+
if (update.kind === "completed" && snippet && pullRequest && isMergedPullRequestSnippet(snippet)) {
|
|
80
|
+
return {
|
|
81
|
+
status: "updating_runtime",
|
|
82
|
+
currentSurface: { kind: "runtime", label: "ouro up" },
|
|
83
|
+
currentArtifact: pullRequest,
|
|
84
|
+
nextAction: "update runtime, verify version/changelog, then re-observe",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (update.kind === "completed" && pullRequest) {
|
|
88
|
+
return {
|
|
89
|
+
status: "waiting_for_merge",
|
|
90
|
+
currentSurface: { kind: "merge", label: pullRequest },
|
|
91
|
+
currentArtifact: pullRequest,
|
|
92
|
+
nextAction: `wait for checks, merge ${pullRequest}, then update runtime`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (update.kind === "waiting_input") {
|
|
96
|
+
return {
|
|
97
|
+
status: "investigating",
|
|
98
|
+
currentSurface: { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
|
|
99
|
+
nextAction: `answer ${update.session.runner} ${update.session.id} and continue`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (update.kind === "stalled") {
|
|
103
|
+
return {
|
|
104
|
+
status: "investigating",
|
|
105
|
+
currentSurface: { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
|
|
106
|
+
nextAction: `unstick ${update.session.runner} ${update.session.id} and continue`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (update.kind === "progress" || update.kind === "spawned" || update.kind === "failed" || update.kind === "killed" || update.kind === "completed") {
|
|
110
|
+
return {
|
|
111
|
+
status: "investigating",
|
|
112
|
+
currentSurface: { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
60
117
|
function isSafeProgressSnippet(snippet) {
|
|
118
|
+
const normalized = snippet.trim();
|
|
61
119
|
const wordCount = snippet.split(/\s+/).filter(Boolean).length;
|
|
62
|
-
return (
|
|
120
|
+
return (normalized.length <= 80
|
|
121
|
+
&& wordCount >= 2
|
|
63
122
|
&& wordCount <= 8
|
|
64
|
-
&&
|
|
65
|
-
&& !
|
|
66
|
-
&&
|
|
67
|
-
&&
|
|
68
|
-
&& !/^
|
|
69
|
-
&& !/^
|
|
70
|
-
&& !/^
|
|
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));
|
|
71
132
|
}
|
|
72
133
|
function pickUpdateSnippet(update) {
|
|
73
134
|
return (lastMeaningfulLine(update.text)
|
|
@@ -94,6 +155,22 @@ function formatUpdateMessage(update) {
|
|
|
94
155
|
return `${label} started`;
|
|
95
156
|
}
|
|
96
157
|
}
|
|
158
|
+
function formatReportBackMessage(update, baseMessage) {
|
|
159
|
+
if (!baseMessage)
|
|
160
|
+
return null;
|
|
161
|
+
if (!update.session.obligationId || !update.session.originSession) {
|
|
162
|
+
return baseMessage;
|
|
163
|
+
}
|
|
164
|
+
const milestone = deriveObligationMilestone(update);
|
|
165
|
+
const extraLines = [];
|
|
166
|
+
if (milestone?.currentArtifact) {
|
|
167
|
+
extraLines.push(`current artifact: ${milestone.currentArtifact}`);
|
|
168
|
+
}
|
|
169
|
+
if (milestone?.nextAction) {
|
|
170
|
+
extraLines.push(`next: ${milestone.nextAction}`);
|
|
171
|
+
}
|
|
172
|
+
return extraLines.length > 0 ? `${baseMessage}\n${extraLines.join("\n")}` : baseMessage;
|
|
173
|
+
}
|
|
97
174
|
function obligationNoteFromUpdate(update) {
|
|
98
175
|
const snippet = pickUpdateSnippet(update);
|
|
99
176
|
switch (update.kind) {
|
|
@@ -121,10 +198,13 @@ function syncObligationFromUpdate(update) {
|
|
|
121
198
|
const obligationId = update.session.obligationId;
|
|
122
199
|
if (!obligationId)
|
|
123
200
|
return;
|
|
201
|
+
const milestone = deriveObligationMilestone(update);
|
|
124
202
|
try {
|
|
125
203
|
(0, obligations_1.advanceObligation)((0, identity_1.getAgentRoot)(), obligationId, {
|
|
126
|
-
status: "investigating",
|
|
127
|
-
currentSurface: { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
|
|
204
|
+
status: milestone?.status ?? "investigating",
|
|
205
|
+
currentSurface: milestone?.currentSurface ?? { kind: "coding", label: `${update.session.runner} ${update.session.id}` },
|
|
206
|
+
currentArtifact: milestone?.currentArtifact,
|
|
207
|
+
nextAction: milestone?.nextAction,
|
|
128
208
|
latestNote: obligationNoteFromUpdate(update) ?? undefined,
|
|
129
209
|
});
|
|
130
210
|
}
|
|
@@ -193,10 +273,10 @@ function attachCodingSessionFeedback(manager, session, target) {
|
|
|
193
273
|
};
|
|
194
274
|
const spawnedUpdate = { kind: "spawned", session };
|
|
195
275
|
syncObligationFromUpdate(spawnedUpdate);
|
|
196
|
-
sendMessage(formatUpdateMessage(spawnedUpdate));
|
|
276
|
+
sendMessage(formatReportBackMessage(spawnedUpdate, formatUpdateMessage(spawnedUpdate)));
|
|
197
277
|
unsubscribe = manager.subscribe(session.id, async (update) => {
|
|
198
278
|
syncObligationFromUpdate(update);
|
|
199
|
-
sendMessage(formatUpdateMessage(update));
|
|
279
|
+
sendMessage(formatReportBackMessage(update, formatUpdateMessage(update)));
|
|
200
280
|
await wakeInnerDialogForObligation(update);
|
|
201
281
|
if (TERMINAL_UPDATE_KINDS.has(update.kind)) {
|
|
202
282
|
closed = true;
|
|
@@ -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,23 @@ 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
|
+
}
|
|
41
58
|
function readInnerWorkState() {
|
|
42
59
|
const defaultJob = {
|
|
43
60
|
status: "idle",
|
|
@@ -271,6 +288,7 @@ async function handleInboundTurn(input) {
|
|
|
271
288
|
}
|
|
272
289
|
// Step 5: runAgent
|
|
273
290
|
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
291
|
+
const statusCheckRequested = isStatusCheckRequested(input.continuityIngressTexts);
|
|
274
292
|
const runAgentOptions = {
|
|
275
293
|
...input.runAgentOptions,
|
|
276
294
|
bridgeContext,
|
|
@@ -278,6 +296,8 @@ async function handleInboundTurn(input) {
|
|
|
278
296
|
delegationDecision,
|
|
279
297
|
currentSessionKey: currentSession.key,
|
|
280
298
|
currentObligation,
|
|
299
|
+
statusCheckRequested,
|
|
300
|
+
toolChoiceRequired: statusCheckRequested ? true : input.runAgentOptions?.toolChoiceRequired,
|
|
281
301
|
mustResolveBeforeHandoff,
|
|
282
302
|
setMustResolveBeforeHandoff: (value) => {
|
|
283
303
|
mustResolveBeforeHandoff = value;
|