@promptbook/cli 0.112.0-113 → 0.112.0-114
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/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/stream/route.ts +85 -56
- package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatHistorySyncEffects.ts +7 -13
- package/apps/agents-server/src/database/migrations/2026-06-1300-user-chat-active-read-indexes.sql +7 -0
- package/apps/agents-server/src/utils/agentRouting/resolveAgentRouteTarget.ts +38 -0
- package/apps/agents-server/src/utils/userChat/createImmediateUserChatAnswerModelRequirements.ts +15 -12
- package/apps/agents-server/src/utils/userChat/createUserChatDetailPayload.ts +33 -18
- package/apps/agents-server/src/utils/userChat/hasPotentiallyPendingAssistantMessages.ts +26 -0
- package/apps/agents-server/src/utils/userChat/runImmediateUserChatAnswer.ts +1 -1
- package/esm/index.es.js +60 -24
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/runGoScript/printLiveScriptChunk.d.ts +4 -0
- package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +10 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/book-3.0/LiteAgent.ts +15 -10
- package/src/cli/cli-commands/agent/agentCliOptions.ts +33 -4
- package/src/cli/cli-commands/agent/chat.ts +2 -2
- package/src/cli/cli-commands/agent/exec.ts +2 -2
- package/src/cli/cli-commands/agent.ts +0 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +767 -853
- package/src/version.ts +2 -2
- package/src/versions.txt +1 -0
- package/umd/index.umd.js +60 -24
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/runGoScript/printLiveScriptChunk.d.ts +4 -0
- package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +10 -1
- package/umd/src/version.d.ts +1 -1
package/apps/agents-server/src/app/agents/[agentName]/api/user-chats/[chatId]/stream/route.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { CHAT_STREAM_KEEP_ALIVE_INTERVAL_MS } from '@/src/constants/streaming';
|
|
2
2
|
import { isPrivateModeEnabledFromRequest } from '@/src/utils/privateMode';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
createUserChatDetailPayload,
|
|
5
|
+
getUserChat,
|
|
6
|
+
isFrozenUserChatSource,
|
|
7
|
+
listUserChatJobs,
|
|
8
|
+
} from '@/src/utils/userChat';
|
|
9
|
+
import { hasPotentiallyPendingAssistantMessages } from '@/src/utils/userChat/hasPotentiallyPendingAssistantMessages';
|
|
10
|
+
import { listUserChatTimeouts } from '@/src/utils/userChatTimeout/userChatTimeoutStore';
|
|
5
11
|
import { NextResponse } from 'next/server';
|
|
6
12
|
import { resolveUserChatScope } from '../../resolveUserChatScope';
|
|
7
13
|
|
|
@@ -136,17 +142,17 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
136
142
|
return false;
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
const
|
|
140
|
-
const nextSignature = createUserChatDetailSignature(payload);
|
|
145
|
+
const livePollState = await loadLiveUserChatPollingState(currentChat);
|
|
141
146
|
|
|
142
|
-
if (
|
|
143
|
-
|
|
147
|
+
if (livePollState.signature !== lastSnapshotSignature) {
|
|
148
|
+
const payload = await createUserChatDetailPayload(currentChat);
|
|
149
|
+
lastSnapshotSignature = createUserChatPollingSignatureFromDetailPayload(payload);
|
|
144
150
|
if (!enqueueFrame({ type: 'snapshot', payload })) {
|
|
145
151
|
return false;
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
return !isFrozenUserChatSource(
|
|
155
|
+
return !isFrozenUserChatSource(currentChat.source) && livePollState.hasActiveJobs;
|
|
150
156
|
};
|
|
151
157
|
|
|
152
158
|
/**
|
|
@@ -211,69 +217,92 @@ export async function GET(request: Request, { params }: { params: Promise<{ agen
|
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
/**
|
|
214
|
-
*
|
|
220
|
+
* Minimal live poll state used to avoid rebuilding the full chat payload when nothing visible changed.
|
|
215
221
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
draftMessage: payload.draftMessage || '',
|
|
221
|
-
messages: payload.messages.map(createUserChatMessageSignature),
|
|
222
|
-
activeJobs: payload.activeJobs.map((job) => ({
|
|
223
|
-
id: job.id,
|
|
224
|
-
status: job.status,
|
|
225
|
-
cancelRequestedAt: job.cancelRequestedAt,
|
|
226
|
-
})),
|
|
227
|
-
activeTimeouts: payload.activeTimeouts.map((timeout) => ({
|
|
228
|
-
id: timeout.id,
|
|
229
|
-
status: timeout.status,
|
|
230
|
-
dueAt: timeout.dueAt,
|
|
231
|
-
cancelRequestedAt: timeout.cancelRequestedAt,
|
|
232
|
-
})),
|
|
233
|
-
});
|
|
234
|
-
}
|
|
222
|
+
type LiveUserChatPollState = {
|
|
223
|
+
signature: string;
|
|
224
|
+
hasActiveJobs: boolean;
|
|
225
|
+
};
|
|
235
226
|
|
|
236
227
|
/**
|
|
237
|
-
*
|
|
228
|
+
* Loads the lightweight state needed to decide whether the active chat payload changed.
|
|
229
|
+
*
|
|
230
|
+
* The expensive detail payload does job reconciliation, local-runner synchronization, and full
|
|
231
|
+
* transcript serialization. Polling first with the persisted timestamps + active resource state
|
|
232
|
+
* keeps long-lived chat streams cheap when the conversation is idle.
|
|
233
|
+
*
|
|
234
|
+
* @param chat - Current scoped chat record.
|
|
235
|
+
* @returns Poll signature plus whether the chat should keep using the active-job cadence.
|
|
238
236
|
*/
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
async function loadLiveUserChatPollingState(chat: NonNullable<Awaited<ReturnType<typeof getUserChat>>>): Promise<LiveUserChatPollState> {
|
|
238
|
+
const shouldInspectActiveJobs = hasPotentiallyPendingAssistantMessages(chat.messages);
|
|
239
|
+
const [activeJobs, activeTimeouts] = await Promise.all([
|
|
240
|
+
shouldInspectActiveJobs
|
|
241
|
+
? listUserChatJobs({
|
|
242
|
+
userId: chat.userId,
|
|
243
|
+
agentPermanentId: chat.agentPermanentId,
|
|
244
|
+
chatId: chat.id,
|
|
245
|
+
onlyActive: true,
|
|
246
|
+
})
|
|
247
|
+
: Promise.resolve([]),
|
|
248
|
+
listUserChatTimeouts({
|
|
249
|
+
userId: chat.userId,
|
|
250
|
+
agentPermanentId: chat.agentPermanentId,
|
|
251
|
+
chatId: chat.id,
|
|
252
|
+
onlyActive: true,
|
|
253
|
+
}),
|
|
254
|
+
]);
|
|
255
|
+
|
|
242
256
|
return {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
replyingTo: message.replyingTo ? JSON.stringify(message.replyingTo) : null,
|
|
251
|
-
progressCard: message.progressCard ? JSON.stringify(message.progressCard) : null,
|
|
252
|
-
ongoingToolCalls: createToolCallsSignature(message.ongoingToolCalls),
|
|
253
|
-
completedToolCalls: createToolCallsSignature(message.completedToolCalls),
|
|
254
|
-
toolCalls: createToolCallsSignature(message.toolCalls),
|
|
257
|
+
signature: createUserChatPollingSignature({
|
|
258
|
+
chatUpdatedAt: chat.updatedAt,
|
|
259
|
+
draftMessage: chat.draftMessage,
|
|
260
|
+
activeJobs,
|
|
261
|
+
activeTimeouts,
|
|
262
|
+
}),
|
|
263
|
+
hasActiveJobs: activeJobs.length > 0,
|
|
255
264
|
};
|
|
256
265
|
}
|
|
257
266
|
|
|
258
267
|
/**
|
|
259
|
-
*
|
|
268
|
+
* Builds the polling signature for a fully hydrated detail payload.
|
|
260
269
|
*/
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
function createUserChatPollingSignatureFromDetailPayload(
|
|
271
|
+
payload: Awaited<ReturnType<typeof createUserChatDetailPayload>>,
|
|
272
|
+
): string {
|
|
273
|
+
return createUserChatPollingSignature({
|
|
274
|
+
chatUpdatedAt: payload.chat.updatedAt,
|
|
275
|
+
draftMessage: payload.draftMessage,
|
|
276
|
+
activeJobs: payload.activeJobs,
|
|
277
|
+
activeTimeouts: payload.activeTimeouts,
|
|
278
|
+
});
|
|
270
279
|
}
|
|
271
280
|
|
|
272
281
|
/**
|
|
273
|
-
*
|
|
282
|
+
* Builds a stable signature for the user-visible state that changes stream snapshots.
|
|
274
283
|
*/
|
|
275
|
-
function
|
|
276
|
-
|
|
284
|
+
function createUserChatPollingSignature(options: {
|
|
285
|
+
chatUpdatedAt: string;
|
|
286
|
+
draftMessage: string | null;
|
|
287
|
+
activeJobs: ReadonlyArray<Awaited<ReturnType<typeof createUserChatDetailPayload>>['activeJobs'][number]>;
|
|
288
|
+
activeTimeouts: ReadonlyArray<Awaited<ReturnType<typeof createUserChatDetailPayload>>['activeTimeouts'][number]>;
|
|
289
|
+
}): string {
|
|
290
|
+
return JSON.stringify({
|
|
291
|
+
updatedAt: options.chatUpdatedAt,
|
|
292
|
+
draftMessage: options.draftMessage || '',
|
|
293
|
+
activeJobs: options.activeJobs.map((job) => ({
|
|
294
|
+
id: job.id,
|
|
295
|
+
status: job.status,
|
|
296
|
+
cancelRequestedAt: job.cancelRequestedAt,
|
|
297
|
+
})),
|
|
298
|
+
activeTimeouts: options.activeTimeouts.map((timeout) => ({
|
|
299
|
+
id: timeout.id,
|
|
300
|
+
status: timeout.status,
|
|
301
|
+
dueAt: timeout.dueAt,
|
|
302
|
+
cancelRequestedAt: timeout.cancelRequestedAt,
|
|
303
|
+
pausedAt: timeout.pausedAt,
|
|
304
|
+
})),
|
|
305
|
+
});
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
/**
|
package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatHistorySyncEffects.ts
CHANGED
|
@@ -22,13 +22,6 @@ const USER_CHAT_STREAM_RECONNECT_DELAY_MS = 1_500;
|
|
|
22
22
|
*/
|
|
23
23
|
const DISCONNECTED_CHAT_REFRESH_INTERVAL_MS = 4_000;
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Periodic sidebar/list refresh cadence while the active chat stream is healthy.
|
|
27
|
-
*
|
|
28
|
-
* @private function of useAgentChatHistoryClientState
|
|
29
|
-
*/
|
|
30
|
-
const CHAT_LIST_REFRESH_INTERVAL_MS = 20_000;
|
|
31
|
-
|
|
32
25
|
/**
|
|
33
26
|
* Inputs required to register side effects for durable chat-history synchronization.
|
|
34
27
|
*
|
|
@@ -504,7 +497,7 @@ function keepActiveChatStreamConnected(params: {
|
|
|
504
497
|
}
|
|
505
498
|
|
|
506
499
|
/**
|
|
507
|
-
* Refreshes the selected chat
|
|
500
|
+
* Refreshes the selected chat on disconnect polling and on tab visibility/focus changes.
|
|
508
501
|
*
|
|
509
502
|
* @private function of useAgentChatHistoryClientState
|
|
510
503
|
*/
|
|
@@ -529,9 +522,6 @@ function registerActiveChatRefreshPolling(params: {
|
|
|
529
522
|
return undefined;
|
|
530
523
|
}
|
|
531
524
|
|
|
532
|
-
const pollIntervalMs = isActiveChatStreamConnected
|
|
533
|
-
? CHAT_LIST_REFRESH_INTERVAL_MS
|
|
534
|
-
: DISCONNECTED_CHAT_REFRESH_INTERVAL_MS;
|
|
535
525
|
const runRefresh = () => {
|
|
536
526
|
if (typeof document !== 'undefined' && document.hidden) {
|
|
537
527
|
return;
|
|
@@ -540,7 +530,9 @@ function registerActiveChatRefreshPolling(params: {
|
|
|
540
530
|
void refreshActiveChat({ preserveDirtyDraft: true });
|
|
541
531
|
};
|
|
542
532
|
|
|
543
|
-
const interval =
|
|
533
|
+
const interval = isActiveChatStreamConnected
|
|
534
|
+
? null
|
|
535
|
+
: window.setInterval(runRefresh, DISCONNECTED_CHAT_REFRESH_INTERVAL_MS);
|
|
544
536
|
const handleVisibilityChange = () => {
|
|
545
537
|
if (typeof document !== 'undefined' && !document.hidden) {
|
|
546
538
|
runRefresh();
|
|
@@ -554,7 +546,9 @@ function registerActiveChatRefreshPolling(params: {
|
|
|
554
546
|
window.addEventListener('focus', handleFocus);
|
|
555
547
|
|
|
556
548
|
return () => {
|
|
557
|
-
|
|
549
|
+
if (interval !== null) {
|
|
550
|
+
window.clearInterval(interval);
|
|
551
|
+
}
|
|
558
552
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
559
553
|
window.removeEventListener('focus', handleFocus);
|
|
560
554
|
};
|
package/apps/agents-server/src/database/migrations/2026-06-1300-user-chat-active-read-indexes.sql
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
CREATE INDEX IF NOT EXISTS "prefix_UserChatJob_chatId_userId_agentPermanentId_active_createdAt_idx"
|
|
2
|
+
ON "prefix_UserChatJob" ("chatId", "userId", "agentPermanentId", "createdAt" ASC)
|
|
3
|
+
WHERE "status" IN ('QUEUED', 'RUNNING');
|
|
4
|
+
|
|
5
|
+
CREATE INDEX IF NOT EXISTS "prefix_UserChatTimeout_chatId_userId_agentPermanentId_active_dueAt_idx"
|
|
6
|
+
ON "prefix_UserChatTimeout" ("chatId", "userId", "agentPermanentId", "dueAt" ASC, "createdAt" ASC)
|
|
7
|
+
WHERE "status" IN ('QUEUED', 'RUNNING') AND "pausedAt" IS NULL;
|
|
@@ -102,6 +102,13 @@ async function resolveAgentRouteTargetUncached(
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
if (!options?.forceRefresh) {
|
|
106
|
+
const fastLocalRouteTarget = await resolveFastLocalAgentRouteTarget(normalizedReference, localServerUrl);
|
|
107
|
+
if (fastLocalRouteTarget) {
|
|
108
|
+
return fastLocalRouteTarget;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
const resolver = await $provideAgentReferenceResolver({ forceRefresh: options?.forceRefresh });
|
|
106
113
|
let resolvedUrlValue: string;
|
|
107
114
|
|
|
@@ -145,6 +152,37 @@ async function resolveAgentRouteTargetUncached(
|
|
|
145
152
|
};
|
|
146
153
|
}
|
|
147
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Resolves the common local-agent case without constructing the heavier shared reference resolver.
|
|
157
|
+
*
|
|
158
|
+
* Normal page and chat routes almost always target one local agent by its stored name or permanent id.
|
|
159
|
+
* Handling that case up front keeps route resolution cheap while still falling back to the full TEAM/federation
|
|
160
|
+
* resolver for aliases, remote agents, and fuzzy matches.
|
|
161
|
+
*
|
|
162
|
+
* @param reference - Normalized route/reference text.
|
|
163
|
+
* @param localServerUrl - Normalized URL of the current Agents Server instance.
|
|
164
|
+
* @returns Local route target or `null` when a direct lookup does not match.
|
|
165
|
+
*/
|
|
166
|
+
async function resolveFastLocalAgentRouteTarget(
|
|
167
|
+
reference: string,
|
|
168
|
+
localServerUrl: string,
|
|
169
|
+
): Promise<AgentRouteTarget | null> {
|
|
170
|
+
const collection = await $provideAgentCollectionForServer();
|
|
171
|
+
const directMatch = await collection.findAgentBasicInformation(reference);
|
|
172
|
+
|
|
173
|
+
if (!directMatch) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const canonicalAgentId = directMatch.permanentId || directMatch.agentName;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
kind: 'local',
|
|
181
|
+
canonicalAgentId,
|
|
182
|
+
canonicalUrl: `${localServerUrl}${AGENT_PATH_PREFIX}${encodeURIComponent(canonicalAgentId)}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
148
186
|
/**
|
|
149
187
|
* Memoized route-target resolver used for normal page rendering.
|
|
150
188
|
*/
|
package/apps/agents-server/src/utils/userChat/createImmediateUserChatAnswerModelRequirements.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { parseAgentSourceWithCommitments } from '../../../../../src/book-2.0/age
|
|
|
5
5
|
import type { string_book } from '../../../../../src/book-2.0/agent-source/string_book';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Commitments safe to use in the immediate answer without triggering slow tools, knowledge, imports, or memory.
|
|
8
|
+
* Commitments safe to use in the immediate pre-answer without triggering slow tools, knowledge, imports, or memory.
|
|
9
9
|
*/
|
|
10
10
|
const IMMEDIATE_USER_CHAT_ANSWER_INSTRUCTION_COMMITMENTS = new Set<string>([
|
|
11
11
|
'PERSONA',
|
|
@@ -26,27 +26,30 @@ const IMMEDIATE_USER_CHAT_ANSWER_INSTRUCTION_COMMITMENTS = new Set<string>([
|
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Prefix added to the immediate answer system message.
|
|
29
|
+
* Prefix added to the immediate pre-answer system message.
|
|
30
30
|
*/
|
|
31
31
|
const IMMEDIATE_USER_CHAT_ANSWER_SYSTEM_PREAMBLE = spaceTrim(`
|
|
32
|
-
You are preparing a
|
|
33
|
-
This response is not the final answer.
|
|
34
|
-
These immediate-answer rules override any agent instruction below that would make the answer sound final.
|
|
32
|
+
You are preparing a short in-progress confirmation for the user while a slower full agent run continues separately.
|
|
33
|
+
This response is not the final answer. It is only a confirmation that the task is being handled.
|
|
34
|
+
These immediate-answer rules override any agent instruction below that would make the answer sound final or complete.
|
|
35
35
|
|
|
36
36
|
At the start of every response, clearly say in the user's language:
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- The final answer
|
|
37
|
+
- You understood what the user wants.
|
|
38
|
+
- You are working on it now or the job has already started.
|
|
39
|
+
- The final answer will arrive after the background processing finishes.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Keep the whole response short, preferably one or two sentences.
|
|
42
|
+
Do not provide any part of the final answer yet.
|
|
43
|
+
Do not include code snippets, detailed steps, calculations, drafted content, likely conclusions, or partial deliverables.
|
|
44
|
+
If helpful, briefly name the kind of work being done, such as writing code, checking something, preparing an answer, or generating an image.
|
|
42
45
|
Answer directly and use only the instructions, conversation, attachments, and general model knowledge available in this request.
|
|
43
46
|
Do not use or claim to have used external tools, memory, knowledge bases, web browsing, search, calendar, email, projects, or teammate agents.
|
|
44
|
-
Never present this
|
|
45
|
-
If the user asks for something that clearly requires
|
|
47
|
+
Never present this message as complete, definitive, or ready to use.
|
|
48
|
+
If the user asks for something that clearly requires unavailable capabilities, simply say the checked final answer is still being prepared.
|
|
46
49
|
`);
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
|
-
* Creates the lightweight model requirements used by the immediate answer path.
|
|
52
|
+
* Creates the lightweight model requirements used by the immediate pre-answer path.
|
|
50
53
|
*/
|
|
51
54
|
export function createImmediateUserChatAnswerModelRequirements(
|
|
52
55
|
agentSource: string_book,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { UserChatRecord } from './UserChatRecord';
|
|
2
2
|
import { synchronizeLocalUserChatJobsForChat } from '../localChatRunner/synchronizeLocalUserChatJobs';
|
|
3
|
+
import { hasPotentiallyPendingAssistantMessages } from './hasPotentiallyPendingAssistantMessages';
|
|
3
4
|
import { createUserChatTimeoutActivity } from '../userChatTimeout/createUserChatTimeoutActivity';
|
|
4
5
|
import { listUserChatTimeouts } from '../userChatTimeout/userChatTimeoutStore';
|
|
5
6
|
import { createUserChatSummary } from './createUserChatSummary';
|
|
@@ -18,31 +19,45 @@ export async function createUserChatDetailPayload(chat: UserChatRecord): Promise
|
|
|
18
19
|
activeTimeouts: Awaited<ReturnType<typeof listUserChatTimeouts>>;
|
|
19
20
|
}> {
|
|
20
21
|
let currentChat = chat;
|
|
21
|
-
const hasSynchronizedLocalJobs = await synchronizeLocalUserChatJobsForChat(currentChat);
|
|
22
|
-
|
|
23
|
-
if (hasSynchronizedLocalJobs) {
|
|
24
|
-
const refreshedChat = await getUserChat({
|
|
25
|
-
userId: currentChat.userId,
|
|
26
|
-
agentPermanentId: currentChat.agentPermanentId,
|
|
27
|
-
chatId: currentChat.id,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (refreshedChat) {
|
|
31
|
-
currentChat = refreshedChat;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
22
|
let activeJobs = await listUserChatJobs({
|
|
36
23
|
userId: currentChat.userId,
|
|
37
24
|
agentPermanentId: currentChat.agentPermanentId,
|
|
38
25
|
chatId: currentChat.id,
|
|
39
26
|
onlyActive: true,
|
|
40
27
|
});
|
|
28
|
+
const shouldInspectJobState =
|
|
29
|
+
activeJobs.length > 0 || hasPotentiallyPendingAssistantMessages(currentChat.messages);
|
|
41
30
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
if (shouldInspectJobState) {
|
|
32
|
+
const hasSynchronizedLocalJobs = await synchronizeLocalUserChatJobsForChat(currentChat);
|
|
33
|
+
|
|
34
|
+
if (hasSynchronizedLocalJobs) {
|
|
35
|
+
const refreshedChat = await getUserChat({
|
|
36
|
+
userId: currentChat.userId,
|
|
37
|
+
agentPermanentId: currentChat.agentPermanentId,
|
|
38
|
+
chatId: currentChat.id,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (refreshedChat) {
|
|
42
|
+
currentChat = refreshedChat;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
activeJobs = await listUserChatJobs({
|
|
46
|
+
userId: currentChat.userId,
|
|
47
|
+
agentPermanentId: currentChat.agentPermanentId,
|
|
48
|
+
chatId: currentChat.id,
|
|
49
|
+
onlyActive: true,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hasReconciledJobs =
|
|
55
|
+
activeJobs.length > 0
|
|
56
|
+
? await reconcileUserChatActiveJobs({
|
|
57
|
+
chat: currentChat,
|
|
58
|
+
activeJobs,
|
|
59
|
+
})
|
|
60
|
+
: false;
|
|
46
61
|
|
|
47
62
|
if (hasReconciledJobs) {
|
|
48
63
|
const refreshedChat = await getUserChat({
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ChatMessage } from '@promptbook-local/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns `true` when the current transcript still suggests unfinished assistant work.
|
|
5
|
+
*
|
|
6
|
+
* This is intentionally message-based so readers can avoid heavier reconciliation work for
|
|
7
|
+
* settled chats while still inspecting threads that contain incomplete placeholders.
|
|
8
|
+
*
|
|
9
|
+
* @param messages - Current persisted chat transcript.
|
|
10
|
+
* @returns Whether the transcript still looks unfinished from the UI perspective.
|
|
11
|
+
* @private internal utility of `userChat`
|
|
12
|
+
*/
|
|
13
|
+
export function hasPotentiallyPendingAssistantMessages(messages: ReadonlyArray<ChatMessage>): boolean {
|
|
14
|
+
return messages.some((message) => {
|
|
15
|
+
const sender = String(message.sender || '').toUpperCase();
|
|
16
|
+
if (sender !== 'AGENT' && sender !== 'MODEL') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
message.isComplete === false ||
|
|
22
|
+
message.lifecycleState === 'queued' ||
|
|
23
|
+
message.lifecycleState === 'running'
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -44,7 +44,7 @@ type RunImmediateUserChatAnswerOptions = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Runs a fast local LLM answer into the incomplete assistant placeholder while the external runner is still working.
|
|
47
|
+
* Runs a fast local LLM pre-answer into the incomplete assistant placeholder while the external runner is still working.
|
|
48
48
|
*/
|
|
49
49
|
export async function runImmediateUserChatAnswer(
|
|
50
50
|
job: UserChatJobRecord,
|