@rubytech/taskmaster 1.0.11 → 1.0.12
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/dist/agents/pi-embedded-helpers/errors.js +6 -7
- package/dist/agents/pi-embedded-runner/compact.js +0 -1
- package/dist/agents/pi-embedded-runner/run/attempt.js +0 -1
- package/dist/agents/pi-embedded-runner/run/payloads.js +4 -4
- package/dist/agents/pi-embedded-runner/run.js +4 -4
- package/dist/agents/pi-embedded-runner/system-prompt.js +0 -1
- package/dist/agents/system-prompt.js +5 -17
- package/dist/agents/taskmaster-tools.js +4 -0
- package/dist/agents/tool-policy.js +1 -0
- package/dist/agents/tools/memory-reindex-tool.js +67 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +17 -40
- package/dist/auto-reply/reply/agent-runner.js +14 -24
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/session.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/control-ui/assets/{index-DKEqjln4.js → index-D8ayJUWC.js} +176 -152
- package/dist/control-ui/assets/index-D8ayJUWC.js.map +1 -0
- package/dist/control-ui/assets/{index-QrR3OWrg.css → index-dMMqL7A5.css} +1 -1
- package/dist/control-ui/index.html +2 -2
- package/dist/gateway/protocol/schema/sessions-transcript.js +1 -0
- package/dist/gateway/server-methods/memory.js +62 -0
- package/dist/gateway/server-methods/sessions-transcript.js +10 -9
- package/dist/gateway/server-methods.js +5 -1
- package/dist/memory/manager.js +17 -1
- package/dist/memory/memory-schema.js +23 -1
- package/dist/web/auto-reply/monitor.js +1 -1
- package/package.json +1 -1
- package/taskmaster-docs/USER-GUIDE.md +3 -1
- package/templates/customer/agents/admin/AGENTS.md +6 -5
- package/templates/taskmaster/agents/admin/AGENTS.md +7 -5
- package/templates/tradesupport/agents/admin/AGENTS.md +6 -5
- package/dist/control-ui/assets/index-DKEqjln4.js.map +0 -1
|
@@ -248,10 +248,10 @@ export function formatAssistantErrorText(msg, opts) {
|
|
|
248
248
|
return ("Context overflow: prompt too large for the model. " +
|
|
249
249
|
"Try again with less input or a larger-context model.");
|
|
250
250
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
// Conversation history corruption — role ordering, orphaned tool results, etc.
|
|
252
|
+
// The session is stuck; every subsequent message will fail until reset.
|
|
253
|
+
if (/incorrect role information|roles must alternate|400.*role|"message".*role.*information|tool_use_id|tool_result.*tool_use|tool_use.*tool_result/i.test(raw)) {
|
|
254
|
+
return "This conversation got into a bad state. Use /new to start fresh.";
|
|
255
255
|
}
|
|
256
256
|
const invalidRequest = raw.match(/"type":"invalid_request_error".*?"message":"([^"]+)"/);
|
|
257
257
|
if (invalidRequest?.[1]) {
|
|
@@ -276,9 +276,8 @@ export function sanitizeUserFacingText(text) {
|
|
|
276
276
|
const trimmed = stripped.trim();
|
|
277
277
|
if (!trimmed)
|
|
278
278
|
return stripped;
|
|
279
|
-
if (/incorrect role information|roles must alternate/i.test(trimmed)) {
|
|
280
|
-
return
|
|
281
|
-
"If this persists, use /new to start a fresh session.");
|
|
279
|
+
if (/incorrect role information|roles must alternate|tool_use_id|tool_result.*tool_use|tool_use.*tool_result/i.test(trimmed)) {
|
|
280
|
+
return "This conversation got into a bad state. Use /new to start fresh.";
|
|
282
281
|
}
|
|
283
282
|
if (isContextOverflowError(trimmed)) {
|
|
284
283
|
return ("Context overflow: prompt too large for the model. " +
|
|
@@ -259,7 +259,6 @@ export async function compactEmbeddedPiSessionDirect(params) {
|
|
|
259
259
|
extraSystemPrompt: params.extraSystemPrompt,
|
|
260
260
|
ownerNumbers: params.ownerNumbers,
|
|
261
261
|
reasoningTagHint,
|
|
262
|
-
finalTagHint: !reasoningTagHint,
|
|
263
262
|
heartbeatPrompt: isDefaultAgent
|
|
264
263
|
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
|
265
264
|
: undefined,
|
|
@@ -284,7 +284,6 @@ export async function runEmbeddedAttempt(params) {
|
|
|
284
284
|
extraSystemPrompt: params.extraSystemPrompt,
|
|
285
285
|
ownerNumbers: params.ownerNumbers,
|
|
286
286
|
reasoningTagHint,
|
|
287
|
-
finalTagHint: !reasoningTagHint,
|
|
288
287
|
heartbeatPrompt: isDefaultAgent
|
|
289
288
|
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
|
290
289
|
: undefined,
|
|
@@ -30,10 +30,10 @@ export function buildEmbeddedRunPayloads(params) {
|
|
|
30
30
|
: null;
|
|
31
31
|
const normalizedErrorText = errorText ? normalizeTextForComparison(errorText) : null;
|
|
32
32
|
const genericErrorText = "The AI service returned an error. Please try again.";
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
replyItems.push({ text:
|
|
33
|
+
// Public-facing agents: silently swallow errors — no error text of any kind.
|
|
34
|
+
// Admin agent: show the formatted error from formatAssistantErrorText.
|
|
35
|
+
if (errorText && !params.suppressRawErrors) {
|
|
36
|
+
replyItems.push({ text: errorText, isError: true });
|
|
37
37
|
}
|
|
38
38
|
const inlineToolResults = params.inlineToolResultsAllowed && params.verboseLevel !== "off" && params.toolMetas.length > 0;
|
|
39
39
|
if (inlineToolResults) {
|
|
@@ -331,13 +331,13 @@ export async function runEmbeddedPiAgent(params) {
|
|
|
331
331
|
},
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
|
-
// Handle
|
|
335
|
-
|
|
334
|
+
// Handle session corruption errors (role ordering, orphaned tool results)
|
|
335
|
+
// Returning kind: "role_ordering" triggers auto-recovery (session reset) upstream.
|
|
336
|
+
if (/incorrect role information|roles must alternate|tool_use_id|tool_result.*tool_use|tool_use.*tool_result/i.test(errorText)) {
|
|
336
337
|
return {
|
|
337
338
|
payloads: [
|
|
338
339
|
{
|
|
339
|
-
text: "
|
|
340
|
-
"If this persists, use /new to start a fresh session.",
|
|
340
|
+
text: "This conversation got into a bad state — resetting now.",
|
|
341
341
|
isError: true,
|
|
342
342
|
},
|
|
343
343
|
],
|
|
@@ -8,7 +8,6 @@ export function buildEmbeddedSystemPrompt(params) {
|
|
|
8
8
|
extraSystemPrompt: params.extraSystemPrompt,
|
|
9
9
|
ownerNumbers: params.ownerNumbers,
|
|
10
10
|
reasoningTagHint: params.reasoningTagHint,
|
|
11
|
-
finalTagHint: params.finalTagHint,
|
|
12
11
|
heartbeatPrompt: params.heartbeatPrompt,
|
|
13
12
|
skillsPrompt: params.skillsPrompt,
|
|
14
13
|
docsPath: params.docsPath,
|
|
@@ -217,15 +217,6 @@ export function buildAgentSystemPrompt(params) {
|
|
|
217
217
|
"<final>Hey there! What would you like to do next?</final>",
|
|
218
218
|
].join(" ")
|
|
219
219
|
: undefined;
|
|
220
|
-
// For providers with native thinking (e.g. Anthropic), we only need the <final> instruction
|
|
221
|
-
// to prevent internal reasoning/planning text from leaking to the user.
|
|
222
|
-
const finalOnlyHint = !reasoningHint && params.finalTagHint
|
|
223
|
-
? [
|
|
224
|
-
"Wrap every user-facing reply in <final>...</final> tags.",
|
|
225
|
-
"Only text inside <final> is shown to the user; everything outside is discarded.",
|
|
226
|
-
"Do not narrate your plan or next steps outside <final> — the user cannot see it.",
|
|
227
|
-
].join(" ")
|
|
228
|
-
: undefined;
|
|
229
220
|
const reasoningLevel = params.reasoningLevel ?? "off";
|
|
230
221
|
const userTimezone = params.userTimezone?.trim();
|
|
231
222
|
const skillsPrompt = params.skillsPrompt?.trim();
|
|
@@ -287,11 +278,11 @@ export function buildAgentSystemPrompt(params) {
|
|
|
287
278
|
"If a task is more complex or takes longer, spawn a sub-agent. It will do the work for you and ping you when it's done. You can always check up on it.",
|
|
288
279
|
"",
|
|
289
280
|
"## Tool Call Style",
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"Never
|
|
281
|
+
"Every text block you produce is delivered as a separate message. Unnecessary text blocks = unnecessary messages sent to the user.",
|
|
282
|
+
"Call tools silently by default. Do not announce what you are about to do, explain what you just did, or summarise completed steps.",
|
|
283
|
+
"If a task requires multiple tool calls, execute them without commentary. The user sees only your final response.",
|
|
284
|
+
"Narrate only when the user needs to know why there is a delay, or when an action has consequences they should be aware of (e.g. a deletion or sending on their behalf).",
|
|
285
|
+
"Never produce process narration like 'I'll check...', 'Let me look up...', 'Perfect — found it', 'Done — I've sent...'. These become unwanted messages.",
|
|
295
286
|
"",
|
|
296
287
|
"## Taskmaster CLI Quick Reference",
|
|
297
288
|
"Taskmaster is controlled via subcommands. Do not invent commands.",
|
|
@@ -432,9 +423,6 @@ export function buildAgentSystemPrompt(params) {
|
|
|
432
423
|
if (reasoningHint) {
|
|
433
424
|
lines.push("## Reasoning Format", reasoningHint, "");
|
|
434
425
|
}
|
|
435
|
-
else if (finalOnlyHint) {
|
|
436
|
-
lines.push("## Output Format", finalOnlyHint, "");
|
|
437
|
-
}
|
|
438
426
|
const contextFiles = params.contextFiles ?? [];
|
|
439
427
|
if (contextFiles.length > 0) {
|
|
440
428
|
const hasSoulFile = contextFiles.some((file) => {
|
|
@@ -8,6 +8,7 @@ import { createDocumentTool } from "./tools/document-tool.js";
|
|
|
8
8
|
import { createGatewayTool } from "./tools/gateway-tool.js";
|
|
9
9
|
import { createImageTool } from "./tools/image-tool.js";
|
|
10
10
|
import { createMemoryGetTool, createMemorySaveMediaTool, createMemorySearchTool, createMemoryWriteTool, } from "./tools/memory-tool.js";
|
|
11
|
+
import { createMemoryReindexTool } from "./tools/memory-reindex-tool.js";
|
|
11
12
|
import { createMessageTool } from "./tools/message-tool.js";
|
|
12
13
|
import { createNodesTool } from "./tools/nodes-tool.js";
|
|
13
14
|
import { createSessionStatusTool } from "./tools/session-status-tool.js";
|
|
@@ -142,6 +143,7 @@ export function createTaskmasterTools(options) {
|
|
|
142
143
|
const memoryGetTool = createMemoryGetTool(memoryToolOptions);
|
|
143
144
|
const memoryWriteTool = createMemoryWriteTool(memoryToolOptions);
|
|
144
145
|
const memorySaveMediaTool = createMemorySaveMediaTool(memoryToolOptions);
|
|
146
|
+
const memoryReindexTool = createMemoryReindexTool(memoryToolOptions);
|
|
145
147
|
if (memorySearchTool)
|
|
146
148
|
tools.push(memorySearchTool);
|
|
147
149
|
if (memoryGetTool)
|
|
@@ -150,6 +152,8 @@ export function createTaskmasterTools(options) {
|
|
|
150
152
|
tools.push(memoryWriteTool);
|
|
151
153
|
if (memorySaveMediaTool)
|
|
152
154
|
tools.push(memorySaveMediaTool);
|
|
155
|
+
if (memoryReindexTool)
|
|
156
|
+
tools.push(memoryReindexTool);
|
|
153
157
|
const pluginTools = resolvePluginTools({
|
|
154
158
|
context: {
|
|
155
159
|
config: options?.config,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { getMemorySearchManager } from "../../memory/index.js";
|
|
3
|
+
import { resolveSessionAgentId } from "../agent-scope.js";
|
|
4
|
+
import { resolveMemorySearchConfig } from "../memory-search.js";
|
|
5
|
+
import { jsonResult, readStringParam } from "./common.js";
|
|
6
|
+
const MemoryReindexSchema = Type.Object({
|
|
7
|
+
action: Type.String({
|
|
8
|
+
description: "Action to perform: 'reindex' (force full reindex) or 'status' (return index stats)",
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
export function createMemoryReindexTool(options) {
|
|
12
|
+
const cfg = options.config;
|
|
13
|
+
if (!cfg)
|
|
14
|
+
return null;
|
|
15
|
+
const agentId = resolveSessionAgentId({
|
|
16
|
+
sessionKey: options.agentSessionKey,
|
|
17
|
+
config: cfg,
|
|
18
|
+
});
|
|
19
|
+
if (!resolveMemorySearchConfig(cfg, agentId))
|
|
20
|
+
return null;
|
|
21
|
+
return {
|
|
22
|
+
label: "Memory Reindex",
|
|
23
|
+
name: "memory_reindex",
|
|
24
|
+
description: "Trigger a memory index rebuild or check index status. " +
|
|
25
|
+
"Use action='reindex' to force a full reindex of all memory files and sessions. " +
|
|
26
|
+
"Use action='status' to return current index stats (file counts, provider, sources).",
|
|
27
|
+
parameters: MemoryReindexSchema,
|
|
28
|
+
execute: async (_toolCallId, params) => {
|
|
29
|
+
const action = readStringParam(params, "action", { required: true });
|
|
30
|
+
const { manager, error } = await getMemorySearchManager({ cfg, agentId });
|
|
31
|
+
if (!manager) {
|
|
32
|
+
return jsonResult({ ok: false, error: error ?? "memory index unavailable" });
|
|
33
|
+
}
|
|
34
|
+
if (action === "status") {
|
|
35
|
+
const status = manager.status();
|
|
36
|
+
return jsonResult({
|
|
37
|
+
ok: true,
|
|
38
|
+
action: "status",
|
|
39
|
+
files: status.files,
|
|
40
|
+
chunks: status.chunks,
|
|
41
|
+
sources: status.sources,
|
|
42
|
+
sourceCounts: status.sourceCounts,
|
|
43
|
+
provider: status.provider,
|
|
44
|
+
model: status.model,
|
|
45
|
+
dirty: status.dirty,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Default: reindex
|
|
49
|
+
try {
|
|
50
|
+
await manager.sync({ reason: "agent-tool", force: true });
|
|
51
|
+
const status = manager.status();
|
|
52
|
+
return jsonResult({
|
|
53
|
+
ok: true,
|
|
54
|
+
action: "reindex",
|
|
55
|
+
files: status.files,
|
|
56
|
+
chunks: status.chunks,
|
|
57
|
+
sources: status.sources,
|
|
58
|
+
sourceCounts: status.sourceCounts,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
return jsonResult({ ok: false, action: "reindex", error: message });
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import { resolveAgentModelFallbacksOverride, resolveDefaultAgentId, } from "../../agents/agent-scope.js";
|
|
4
3
|
import { runCliAgent } from "../../agents/cli-runner.js";
|
|
5
4
|
import { getCliSessionId } from "../../agents/cli-session.js";
|
|
@@ -7,7 +6,7 @@ import { runWithModelFallback } from "../../agents/model-fallback.js";
|
|
|
7
6
|
import { isCliProvider } from "../../agents/model-selection.js";
|
|
8
7
|
import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
|
|
9
8
|
import { isCompactionFailureError, isContextOverflowError, isLikelyContextOverflowError, sanitizeUserFacingText, } from "../../agents/pi-embedded-helpers.js";
|
|
10
|
-
import { resolveAgentIdFromSessionKey, resolveGroupSessionKey,
|
|
9
|
+
import { resolveAgentIdFromSessionKey, resolveGroupSessionKey, } from "../../config/sessions.js";
|
|
11
10
|
import { logVerbose } from "../../globals.js";
|
|
12
11
|
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
|
13
12
|
import { defaultRuntime } from "../../runtime.js";
|
|
@@ -198,7 +197,9 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
198
197
|
ownerNumbers: params.followupRun.run.ownerNumbers,
|
|
199
198
|
enforceFinalTag: resolveEnforceFinalTag(params.followupRun.run, provider),
|
|
200
199
|
senderE164: params.sessionCtx.SenderE164,
|
|
201
|
-
// Suppress raw API errors for non-default agents (public-facing)
|
|
200
|
+
// Suppress raw API errors for non-default agents (public-facing).
|
|
201
|
+
// Default (admin) agent gets formatted error text from formatAssistantErrorText
|
|
202
|
+
// which provides human-readable messages for known error types.
|
|
202
203
|
suppressRawErrors: (() => {
|
|
203
204
|
const cfg = params.followupRun.run.config;
|
|
204
205
|
if (!cfg)
|
|
@@ -405,7 +406,7 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
405
406
|
return {
|
|
406
407
|
kind: "final",
|
|
407
408
|
payload: {
|
|
408
|
-
text: "⚠️
|
|
409
|
+
text: "⚠️ Conversation got into a bad state. I've reset it - please try again.",
|
|
409
410
|
},
|
|
410
411
|
};
|
|
411
412
|
}
|
|
@@ -417,7 +418,7 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
417
418
|
const isContextOverflow = isLikelyContextOverflowError(message);
|
|
418
419
|
const isCompactionFailure = isCompactionFailureError(message);
|
|
419
420
|
const isSessionCorruption = /function call turn comes immediately after/i.test(message);
|
|
420
|
-
const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
|
|
421
|
+
const isRoleOrderingError = /incorrect role information|roles must alternate|tool_use_id|tool_result.*tool_use|tool_use.*tool_result/i.test(message);
|
|
421
422
|
if (isCompactionFailure &&
|
|
422
423
|
!didResetAfterCompactionFailure &&
|
|
423
424
|
(await params.resetSessionAfterCompactionFailure(message))) {
|
|
@@ -435,52 +436,28 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
435
436
|
return {
|
|
436
437
|
kind: "final",
|
|
437
438
|
payload: {
|
|
438
|
-
text: "⚠️
|
|
439
|
+
text: "⚠️ Conversation got into a bad state. I've reset it - please try again.",
|
|
439
440
|
},
|
|
440
441
|
};
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
// Auto-recover from Gemini session corruption by resetting the session
|
|
444
|
-
if (isSessionCorruption
|
|
445
|
-
params.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (corruptedSessionId) {
|
|
454
|
-
const transcriptPath = resolveSessionTranscriptPath(corruptedSessionId);
|
|
455
|
-
try {
|
|
456
|
-
fs.unlinkSync(transcriptPath);
|
|
457
|
-
}
|
|
458
|
-
catch {
|
|
459
|
-
// Ignore if file doesn't exist
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
// Keep the in-memory snapshot consistent with the on-disk store reset.
|
|
463
|
-
delete params.activeSessionStore[sessionKey];
|
|
464
|
-
// Remove session entry from store using a fresh, locked snapshot.
|
|
465
|
-
await updateSessionStore(params.storePath, (store) => {
|
|
466
|
-
delete store[sessionKey];
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
catch (cleanupErr) {
|
|
470
|
-
defaultRuntime.error(`Failed to reset corrupted session ${params.sessionKey}: ${String(cleanupErr)}`);
|
|
445
|
+
if (isSessionCorruption) {
|
|
446
|
+
const didReset = await params.resetSessionAfterRoleOrderingConflict(message);
|
|
447
|
+
if (didReset) {
|
|
448
|
+
return {
|
|
449
|
+
kind: "final",
|
|
450
|
+
payload: {
|
|
451
|
+
text: "⚠️ Conversation got into a bad state. I've reset it - please try again.",
|
|
452
|
+
},
|
|
453
|
+
};
|
|
471
454
|
}
|
|
472
|
-
return {
|
|
473
|
-
kind: "final",
|
|
474
|
-
payload: {
|
|
475
|
-
text: "⚠️ Session history was corrupted. I've reset the conversation - please try again!",
|
|
476
|
-
},
|
|
477
|
-
};
|
|
478
455
|
}
|
|
479
456
|
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
|
480
457
|
const fallbackText = isContextOverflow
|
|
481
458
|
? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
|
|
482
459
|
: isRoleOrderingError
|
|
483
|
-
? "⚠️
|
|
460
|
+
? "⚠️ Conversation got into a bad state. Use /new to start a fresh session."
|
|
484
461
|
: "Sorry, I'm having trouble right now. Please try again in a few minutes.";
|
|
485
462
|
return {
|
|
486
463
|
kind: "final",
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import { lookupContextTokens } from "../../agents/context.js";
|
|
4
3
|
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
|
|
5
4
|
import { resolveModelAuthMode } from "../../agents/model-auth.js";
|
|
6
5
|
import { isCliProvider } from "../../agents/model-selection.js";
|
|
7
6
|
import { queueEmbeddedPiMessage } from "../../agents/pi-embedded.js";
|
|
8
7
|
import { hasNonzeroUsage } from "../../agents/usage.js";
|
|
9
|
-
import { resolveAgentIdFromSessionKey,
|
|
8
|
+
import { resolveAgentIdFromSessionKey, resolveSessionTranscriptPath, updateSessionStore, updateSessionStoreEntry, } from "../../config/sessions.js";
|
|
10
9
|
import { defaultRuntime } from "../../runtime.js";
|
|
11
10
|
import { estimateUsageCost, resolveModelCostConfig } from "../../utils/usage-format.js";
|
|
12
11
|
import { resolveResponseUsageMode } from "../thinking.js";
|
|
@@ -128,20 +127,29 @@ export async function runReplyAgent(params) {
|
|
|
128
127
|
agentCfgContextTokens,
|
|
129
128
|
});
|
|
130
129
|
let responseUsageLine;
|
|
131
|
-
const resetSession = async ({ failureLabel, buildLogMessage,
|
|
130
|
+
const resetSession = async ({ failureLabel, buildLogMessage, }) => {
|
|
132
131
|
if (!sessionKey || !activeSessionStore || !storePath)
|
|
133
132
|
return false;
|
|
134
133
|
const prevEntry = activeSessionStore[sessionKey] ?? activeSessionEntry;
|
|
135
134
|
if (!prevEntry)
|
|
136
135
|
return false;
|
|
137
|
-
const prevSessionId = cleanupTranscripts ? prevEntry.sessionId : undefined;
|
|
138
136
|
const nextSessionId = crypto.randomUUID();
|
|
137
|
+
// Archive the previous session so it remains accessible via sessions_history.
|
|
138
|
+
const prevArchive = prevEntry.sessionId && prevEntry.sessionId !== nextSessionId
|
|
139
|
+
? {
|
|
140
|
+
sessionId: prevEntry.sessionId,
|
|
141
|
+
sessionFile: prevEntry.sessionFile,
|
|
142
|
+
endedAt: Date.now(),
|
|
143
|
+
}
|
|
144
|
+
: undefined;
|
|
145
|
+
const existingArchive = prevEntry.previousSessions ?? [];
|
|
139
146
|
const nextEntry = {
|
|
140
147
|
...prevEntry,
|
|
141
148
|
sessionId: nextSessionId,
|
|
142
149
|
updatedAt: Date.now(),
|
|
143
150
|
systemSent: false,
|
|
144
151
|
abortedLastRun: false,
|
|
152
|
+
previousSessions: prevArchive ? [...existingArchive, prevArchive] : existingArchive,
|
|
145
153
|
};
|
|
146
154
|
const agentId = resolveAgentIdFromSessionKey(sessionKey);
|
|
147
155
|
const nextSessionFile = resolveSessionTranscriptPath(nextSessionId, agentId, sessionCtx.MessageThreadId);
|
|
@@ -160,21 +168,6 @@ export async function runReplyAgent(params) {
|
|
|
160
168
|
activeSessionEntry = nextEntry;
|
|
161
169
|
activeIsNewSession = true;
|
|
162
170
|
defaultRuntime.error(buildLogMessage(nextSessionId));
|
|
163
|
-
if (cleanupTranscripts && prevSessionId) {
|
|
164
|
-
const transcriptCandidates = new Set();
|
|
165
|
-
const resolved = resolveSessionFilePath(prevSessionId, prevEntry, { agentId });
|
|
166
|
-
if (resolved)
|
|
167
|
-
transcriptCandidates.add(resolved);
|
|
168
|
-
transcriptCandidates.add(resolveSessionTranscriptPath(prevSessionId, agentId));
|
|
169
|
-
for (const candidate of transcriptCandidates) {
|
|
170
|
-
try {
|
|
171
|
-
fs.unlinkSync(candidate);
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
// Best-effort cleanup.
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
171
|
return true;
|
|
179
172
|
};
|
|
180
173
|
const resetSessionAfterCompactionFailure = async (reason) => resetSession({
|
|
@@ -182,9 +175,8 @@ export async function runReplyAgent(params) {
|
|
|
182
175
|
buildLogMessage: (nextSessionId) => `Auto-compaction failed (${reason}). Restarting session ${sessionKey} -> ${nextSessionId} and retrying.`,
|
|
183
176
|
});
|
|
184
177
|
const resetSessionAfterRoleOrderingConflict = async (reason) => resetSession({
|
|
185
|
-
failureLabel: "
|
|
186
|
-
buildLogMessage: (nextSessionId) => `
|
|
187
|
-
cleanupTranscripts: true,
|
|
178
|
+
failureLabel: "session corruption",
|
|
179
|
+
buildLogMessage: (nextSessionId) => `Session corruption (${reason}). Restarting session ${sessionKey} -> ${nextSessionId}.`,
|
|
188
180
|
});
|
|
189
181
|
try {
|
|
190
182
|
const runStartedAt = Date.now();
|
|
@@ -207,8 +199,6 @@ export async function runReplyAgent(params) {
|
|
|
207
199
|
isHeartbeat,
|
|
208
200
|
sessionKey,
|
|
209
201
|
getActiveSessionEntry: () => activeSessionEntry,
|
|
210
|
-
activeSessionStore,
|
|
211
|
-
storePath,
|
|
212
202
|
resolvedVerboseLevel,
|
|
213
203
|
});
|
|
214
204
|
if (runOutcome.kind === "final") {
|
|
@@ -9,6 +9,7 @@ import { resolveGroupSessionKey, resolveSessionFilePath, } from "../../config/se
|
|
|
9
9
|
import { logVerbose } from "../../globals.js";
|
|
10
10
|
import { clearCommandLane, getQueueSize } from "../../process/command-queue.js";
|
|
11
11
|
import { normalizeMainKey } from "../../routing/session-key.js";
|
|
12
|
+
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
|
|
12
13
|
import { hasControlCommand } from "../command-detection.js";
|
|
13
14
|
import { buildInboundMediaNote } from "../media-note.js";
|
|
14
15
|
import { formatXHighModelHint, normalizeThinkLevel, supportsXHighThinking, } from "../thinking.js";
|
|
@@ -256,7 +257,7 @@ export async function runPreparedReply(params) {
|
|
|
256
257
|
blockReplyBreak: resolvedBlockStreamingBreak,
|
|
257
258
|
ownerNumbers: command.ownerList.length > 0 ? command.ownerList : undefined,
|
|
258
259
|
extraSystemPrompt: extraSystemPrompt || undefined,
|
|
259
|
-
enforceFinalTag: true,
|
|
260
|
+
...(isReasoningTagProvider(provider) ? { enforceFinalTag: true } : {}),
|
|
260
261
|
skipMessageDelivery: opts?.skipMessageDelivery,
|
|
261
262
|
},
|
|
262
263
|
};
|
|
@@ -260,7 +260,7 @@ export async function initSessionState(params) {
|
|
|
260
260
|
sessionFile: previousSessionEntry.sessionFile,
|
|
261
261
|
endedAt: Date.now(),
|
|
262
262
|
};
|
|
263
|
-
const existing =
|
|
263
|
+
const existing = entry?.previousSessions ?? [];
|
|
264
264
|
sessionEntry.previousSessions = [...existing, prev];
|
|
265
265
|
}
|
|
266
266
|
// Preserve per-session overrides while resetting compaction state on /new.
|
package/dist/build-info.json
CHANGED