@poncho-ai/cli 0.32.4 → 0.32.5
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +6 -0
- package/dist/{chunk-F6NA3N2R.js → chunk-LVWNWMNE.js} +744 -510
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +81 -3
- package/dist/index.js +3 -1
- package/dist/{run-interactive-ink-GPCI4L6G.js → run-interactive-ink-VGKSZJDO.js} +1 -1
- package/package.json +3 -3
- package/src/index.ts +745 -464
- package/src/web-ui-client.ts +70 -8
- package/test/run-orchestration.test.ts +171 -0
package/src/index.ts
CHANGED
|
@@ -119,6 +119,17 @@ const readRequestBody = async (request: IncomingMessage): Promise<unknown> => {
|
|
|
119
119
|
return body.length > 0 ? (JSON.parse(body) as unknown) : {};
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
const parseTelegramMessageThreadIdFromPlatformThreadId = (
|
|
123
|
+
platformThreadId: string | undefined,
|
|
124
|
+
chatId: string | undefined,
|
|
125
|
+
): number | undefined => {
|
|
126
|
+
if (!platformThreadId || !chatId) return undefined;
|
|
127
|
+
const parts = platformThreadId.split(":");
|
|
128
|
+
if (parts.length !== 3 || parts[0] !== chatId) return undefined;
|
|
129
|
+
const threadId = Number(parts[1]);
|
|
130
|
+
return Number.isInteger(threadId) ? threadId : undefined;
|
|
131
|
+
};
|
|
132
|
+
|
|
122
133
|
const MAX_UPLOAD_SIZE = 25 * 1024 * 1024; // 25MB per file
|
|
123
134
|
|
|
124
135
|
interface ParsedMultipart {
|
|
@@ -312,6 +323,305 @@ const normalizeMessageForClient = (message: Message): Message => {
|
|
|
312
323
|
return message;
|
|
313
324
|
};
|
|
314
325
|
|
|
326
|
+
const isMessageArray = (value: unknown): value is Message[] =>
|
|
327
|
+
Array.isArray(value) &&
|
|
328
|
+
value.every((entry) => {
|
|
329
|
+
if (!entry || typeof entry !== "object") return false;
|
|
330
|
+
const row = entry as Record<string, unknown>;
|
|
331
|
+
const role = row.role;
|
|
332
|
+
const content = row.content;
|
|
333
|
+
const roleOk = role === "system" || role === "user" || role === "assistant" || role === "tool";
|
|
334
|
+
const contentOk = typeof content === "string" || Array.isArray(content);
|
|
335
|
+
return roleOk && contentOk;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
type HistorySource = "harness" | "continuation" | "messages";
|
|
339
|
+
|
|
340
|
+
const loadCanonicalHistory = (
|
|
341
|
+
conversation: Conversation,
|
|
342
|
+
): { messages: Message[]; source: HistorySource } => {
|
|
343
|
+
if (isMessageArray(conversation._harnessMessages) && conversation._harnessMessages.length > 0) {
|
|
344
|
+
return { messages: [...conversation._harnessMessages], source: "harness" };
|
|
345
|
+
}
|
|
346
|
+
return { messages: [...conversation.messages], source: "messages" };
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const loadRunHistory = (
|
|
350
|
+
conversation: Conversation,
|
|
351
|
+
options?: { preferContinuation?: boolean },
|
|
352
|
+
): { messages: Message[]; source: HistorySource; shouldRebuildCanonical: boolean } => {
|
|
353
|
+
if (options?.preferContinuation && isMessageArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0) {
|
|
354
|
+
return {
|
|
355
|
+
messages: [...conversation._continuationMessages],
|
|
356
|
+
source: "continuation",
|
|
357
|
+
shouldRebuildCanonical: !isMessageArray(conversation._harnessMessages) || conversation._harnessMessages.length === 0,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const canonical = loadCanonicalHistory(conversation);
|
|
361
|
+
return {
|
|
362
|
+
...canonical,
|
|
363
|
+
shouldRebuildCanonical: canonical.source !== "harness",
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
type StoredApproval = NonNullable<Conversation["pendingApprovals"]>[number];
|
|
368
|
+
type PendingToolCall = { id: string; name: string; input: Record<string, unknown> };
|
|
369
|
+
type ApprovalEventItem = {
|
|
370
|
+
approvalId: string;
|
|
371
|
+
tool: string;
|
|
372
|
+
toolCallId?: string;
|
|
373
|
+
input: Record<string, unknown>;
|
|
374
|
+
};
|
|
375
|
+
type RunRequest = {
|
|
376
|
+
conversationId: string;
|
|
377
|
+
messages: Message[];
|
|
378
|
+
preferContinuation?: boolean;
|
|
379
|
+
};
|
|
380
|
+
type RunOutcome = {
|
|
381
|
+
source: HistorySource;
|
|
382
|
+
shouldRebuildCanonical: boolean;
|
|
383
|
+
messages: Message[];
|
|
384
|
+
};
|
|
385
|
+
type TurnSection = { type: "text" | "tools"; content: string | string[] };
|
|
386
|
+
type TurnDraftState = {
|
|
387
|
+
assistantResponse: string;
|
|
388
|
+
toolTimeline: string[];
|
|
389
|
+
sections: TurnSection[];
|
|
390
|
+
currentTools: string[];
|
|
391
|
+
currentText: string;
|
|
392
|
+
};
|
|
393
|
+
type ExecuteTurnResult = {
|
|
394
|
+
latestRunId: string;
|
|
395
|
+
runCancelled: boolean;
|
|
396
|
+
runContinuation: boolean;
|
|
397
|
+
runContinuationMessages?: Message[];
|
|
398
|
+
runHarnessMessages?: Message[];
|
|
399
|
+
runContextTokens: number;
|
|
400
|
+
runContextWindow: number;
|
|
401
|
+
runSteps: number;
|
|
402
|
+
runMaxSteps?: number;
|
|
403
|
+
draft: TurnDraftState;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const createTurnDraftState = (): TurnDraftState => ({
|
|
407
|
+
assistantResponse: "",
|
|
408
|
+
toolTimeline: [],
|
|
409
|
+
sections: [],
|
|
410
|
+
currentTools: [],
|
|
411
|
+
currentText: "",
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const cloneSections = (sections: TurnSection[]): TurnSection[] =>
|
|
415
|
+
sections.map((section) => ({
|
|
416
|
+
type: section.type,
|
|
417
|
+
content: Array.isArray(section.content) ? [...section.content] : section.content,
|
|
418
|
+
}));
|
|
419
|
+
|
|
420
|
+
const flushTurnDraft = (draft: TurnDraftState): void => {
|
|
421
|
+
if (draft.currentTools.length > 0) {
|
|
422
|
+
draft.sections.push({ type: "tools", content: draft.currentTools });
|
|
423
|
+
draft.currentTools = [];
|
|
424
|
+
}
|
|
425
|
+
if (draft.currentText.length > 0) {
|
|
426
|
+
draft.sections.push({ type: "text", content: draft.currentText });
|
|
427
|
+
draft.currentText = "";
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const recordStandardTurnEvent = (draft: TurnDraftState, event: AgentEvent): void => {
|
|
432
|
+
if (event.type === "model:chunk") {
|
|
433
|
+
if (draft.currentTools.length > 0) {
|
|
434
|
+
draft.sections.push({ type: "tools", content: draft.currentTools });
|
|
435
|
+
draft.currentTools = [];
|
|
436
|
+
if (draft.assistantResponse.length > 0 && !/\s$/.test(draft.assistantResponse)) {
|
|
437
|
+
draft.assistantResponse += " ";
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
draft.assistantResponse += event.content;
|
|
441
|
+
draft.currentText += event.content;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (event.type === "tool:started") {
|
|
445
|
+
if (draft.currentText.length > 0) {
|
|
446
|
+
draft.sections.push({ type: "text", content: draft.currentText });
|
|
447
|
+
draft.currentText = "";
|
|
448
|
+
}
|
|
449
|
+
const toolText = `- start \`${event.tool}\``;
|
|
450
|
+
draft.toolTimeline.push(toolText);
|
|
451
|
+
draft.currentTools.push(toolText);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (event.type === "tool:completed") {
|
|
455
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
456
|
+
draft.toolTimeline.push(toolText);
|
|
457
|
+
draft.currentTools.push(toolText);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (event.type === "tool:error") {
|
|
461
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
462
|
+
draft.toolTimeline.push(toolText);
|
|
463
|
+
draft.currentTools.push(toolText);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const buildAssistantMetadata = (
|
|
468
|
+
draft: TurnDraftState,
|
|
469
|
+
sectionsOverride?: TurnSection[],
|
|
470
|
+
): Message["metadata"] | undefined => {
|
|
471
|
+
const sections = sectionsOverride ?? cloneSections(draft.sections);
|
|
472
|
+
if (draft.toolTimeline.length === 0 && sections.length === 0) return undefined;
|
|
473
|
+
return {
|
|
474
|
+
toolActivity: [...draft.toolTimeline],
|
|
475
|
+
sections: sections.length > 0 ? sections : undefined,
|
|
476
|
+
} as Message["metadata"];
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const executeConversationTurn = async ({
|
|
480
|
+
harness,
|
|
481
|
+
runInput,
|
|
482
|
+
initialContextTokens = 0,
|
|
483
|
+
initialContextWindow = 0,
|
|
484
|
+
onEvent,
|
|
485
|
+
}: {
|
|
486
|
+
harness: AgentHarness;
|
|
487
|
+
runInput: Parameters<AgentHarness["runWithTelemetry"]>[0];
|
|
488
|
+
initialContextTokens?: number;
|
|
489
|
+
initialContextWindow?: number;
|
|
490
|
+
onEvent?: (event: AgentEvent, draft: TurnDraftState) => void | Promise<void>;
|
|
491
|
+
}): Promise<ExecuteTurnResult> => {
|
|
492
|
+
const draft = createTurnDraftState();
|
|
493
|
+
let latestRunId = "";
|
|
494
|
+
let runCancelled = false;
|
|
495
|
+
let runContinuation = false;
|
|
496
|
+
let runContinuationMessages: Message[] | undefined;
|
|
497
|
+
let runHarnessMessages: Message[] | undefined;
|
|
498
|
+
let runContextTokens = initialContextTokens;
|
|
499
|
+
let runContextWindow = initialContextWindow;
|
|
500
|
+
let runSteps = 0;
|
|
501
|
+
let runMaxSteps: number | undefined;
|
|
502
|
+
|
|
503
|
+
for await (const event of harness.runWithTelemetry(runInput)) {
|
|
504
|
+
recordStandardTurnEvent(draft, event);
|
|
505
|
+
if (event.type === "run:started") {
|
|
506
|
+
latestRunId = event.runId;
|
|
507
|
+
}
|
|
508
|
+
if (event.type === "run:cancelled") {
|
|
509
|
+
runCancelled = true;
|
|
510
|
+
}
|
|
511
|
+
if (event.type === "run:completed") {
|
|
512
|
+
runContinuation = event.result.continuation === true;
|
|
513
|
+
runContinuationMessages = event.result.continuationMessages;
|
|
514
|
+
runHarnessMessages = event.result.continuationMessages;
|
|
515
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
516
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
517
|
+
runSteps = event.result.steps;
|
|
518
|
+
if (typeof event.result.maxSteps === "number") {
|
|
519
|
+
runMaxSteps = event.result.maxSteps;
|
|
520
|
+
}
|
|
521
|
+
if (draft.assistantResponse.length === 0 && event.result.response) {
|
|
522
|
+
draft.assistantResponse = event.result.response;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (event.type === "run:error") {
|
|
526
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${event.error.message}]`;
|
|
527
|
+
}
|
|
528
|
+
if (onEvent) {
|
|
529
|
+
await onEvent(event, draft);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
latestRunId,
|
|
535
|
+
runCancelled,
|
|
536
|
+
runContinuation,
|
|
537
|
+
runContinuationMessages,
|
|
538
|
+
runHarnessMessages,
|
|
539
|
+
runContextTokens,
|
|
540
|
+
runContextWindow,
|
|
541
|
+
runSteps,
|
|
542
|
+
runMaxSteps,
|
|
543
|
+
draft,
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const normalizePendingToolCalls = (value: unknown): PendingToolCall[] => {
|
|
548
|
+
if (!Array.isArray(value)) return [];
|
|
549
|
+
return value
|
|
550
|
+
.filter((entry): entry is PendingToolCall => {
|
|
551
|
+
if (!entry || typeof entry !== "object") return false;
|
|
552
|
+
const row = entry as Record<string, unknown>;
|
|
553
|
+
return (
|
|
554
|
+
typeof row.id === "string" &&
|
|
555
|
+
typeof row.name === "string" &&
|
|
556
|
+
typeof row.input === "object" &&
|
|
557
|
+
row.input !== null
|
|
558
|
+
);
|
|
559
|
+
})
|
|
560
|
+
.map((entry) => ({ id: entry.id, name: entry.name, input: entry.input }));
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
const normalizeApprovalCheckpoint = (
|
|
564
|
+
approval: StoredApproval,
|
|
565
|
+
fallbackMessages: Message[],
|
|
566
|
+
): StoredApproval => ({
|
|
567
|
+
...approval,
|
|
568
|
+
checkpointMessages: isMessageArray(approval.checkpointMessages) ? approval.checkpointMessages : [...fallbackMessages],
|
|
569
|
+
baseMessageCount: typeof approval.baseMessageCount === "number" && approval.baseMessageCount >= 0
|
|
570
|
+
? approval.baseMessageCount
|
|
571
|
+
: 0,
|
|
572
|
+
pendingToolCalls: normalizePendingToolCalls(approval.pendingToolCalls),
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const buildApprovalCheckpoints = ({
|
|
576
|
+
approvals,
|
|
577
|
+
runId,
|
|
578
|
+
checkpointMessages,
|
|
579
|
+
baseMessageCount,
|
|
580
|
+
pendingToolCalls,
|
|
581
|
+
}: {
|
|
582
|
+
approvals: ApprovalEventItem[];
|
|
583
|
+
runId: string;
|
|
584
|
+
checkpointMessages: Message[];
|
|
585
|
+
baseMessageCount: number;
|
|
586
|
+
pendingToolCalls: PendingToolCall[];
|
|
587
|
+
}): NonNullable<Conversation["pendingApprovals"]> =>
|
|
588
|
+
approvals.map((approval) => ({
|
|
589
|
+
approvalId: approval.approvalId,
|
|
590
|
+
runId,
|
|
591
|
+
tool: approval.tool,
|
|
592
|
+
toolCallId: approval.toolCallId,
|
|
593
|
+
input: approval.input,
|
|
594
|
+
checkpointMessages,
|
|
595
|
+
baseMessageCount,
|
|
596
|
+
pendingToolCalls,
|
|
597
|
+
}));
|
|
598
|
+
|
|
599
|
+
const resolveRunRequest = (
|
|
600
|
+
conversation: Conversation,
|
|
601
|
+
request: RunRequest,
|
|
602
|
+
): RunOutcome => {
|
|
603
|
+
const resolved = loadRunHistory(conversation, {
|
|
604
|
+
preferContinuation: request.preferContinuation,
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
source: resolved.source,
|
|
608
|
+
shouldRebuildCanonical: resolved.shouldRebuildCanonical,
|
|
609
|
+
messages: resolved.messages.length > 0 ? resolved.messages : request.messages,
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
export const __internalRunOrchestration = {
|
|
614
|
+
isMessageArray,
|
|
615
|
+
loadCanonicalHistory,
|
|
616
|
+
loadRunHistory,
|
|
617
|
+
normalizeApprovalCheckpoint,
|
|
618
|
+
buildApprovalCheckpoints,
|
|
619
|
+
resolveRunRequest,
|
|
620
|
+
createTurnDraftState,
|
|
621
|
+
recordStandardTurnEvent,
|
|
622
|
+
executeConversationTurn,
|
|
623
|
+
};
|
|
624
|
+
|
|
315
625
|
const AGENT_TEMPLATE = (
|
|
316
626
|
name: string,
|
|
317
627
|
id: string,
|
|
@@ -1641,6 +1951,77 @@ export const createRequestHandler = async (options?: {
|
|
|
1641
1951
|
// separate copy, causing last-writer-wins when decisions overlap).
|
|
1642
1952
|
const approvalDecisionTracker = new Map<string, Map<string, boolean>>();
|
|
1643
1953
|
|
|
1954
|
+
const findPendingApproval = async (
|
|
1955
|
+
approvalId: string,
|
|
1956
|
+
owner: string,
|
|
1957
|
+
): Promise<{ conversation: Conversation; approval: StoredApproval } | undefined> => {
|
|
1958
|
+
const searchedConversationIds = new Set<string>();
|
|
1959
|
+
const scan = async (conversations: Conversation[]) => {
|
|
1960
|
+
for (const conv of conversations) {
|
|
1961
|
+
if (searchedConversationIds.has(conv.conversationId)) continue;
|
|
1962
|
+
searchedConversationIds.add(conv.conversationId);
|
|
1963
|
+
if (!Array.isArray(conv.pendingApprovals)) continue;
|
|
1964
|
+
const match = conv.pendingApprovals.find((approval) => approval.approvalId === approvalId);
|
|
1965
|
+
if (match) {
|
|
1966
|
+
return { conversation: conv, approval: match as StoredApproval };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return undefined;
|
|
1970
|
+
};
|
|
1971
|
+
|
|
1972
|
+
const ownerScoped = await scan(await conversationStore.list(owner));
|
|
1973
|
+
if (ownerScoped) return ownerScoped;
|
|
1974
|
+
|
|
1975
|
+
// In local-owner mode (default dev), historical conversations may exist
|
|
1976
|
+
// with mixed ownership keys; do one global fallback scan.
|
|
1977
|
+
if (owner === "local-owner") {
|
|
1978
|
+
return await scan(await conversationStore.list());
|
|
1979
|
+
}
|
|
1980
|
+
return undefined;
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
const hasRunningSubagentsForParent = async (
|
|
1984
|
+
parentConversationId: string,
|
|
1985
|
+
owner: string,
|
|
1986
|
+
): Promise<boolean> => {
|
|
1987
|
+
let hasRunning = Array.from(activeSubagentRuns.values()).some(
|
|
1988
|
+
(run) => run.parentConversationId === parentConversationId,
|
|
1989
|
+
);
|
|
1990
|
+
if (hasRunning) return true;
|
|
1991
|
+
|
|
1992
|
+
const summaries = await conversationStore.listSummaries(owner);
|
|
1993
|
+
for (const summary of summaries) {
|
|
1994
|
+
if (summary.parentConversationId !== parentConversationId) continue;
|
|
1995
|
+
const childConversation = await conversationStore.get(summary.conversationId);
|
|
1996
|
+
if (childConversation?.subagentMeta?.status === "running") {
|
|
1997
|
+
hasRunning = true;
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return hasRunning;
|
|
2002
|
+
};
|
|
2003
|
+
|
|
2004
|
+
const hasPendingSubagentWorkForParent = async (
|
|
2005
|
+
parentConversationId: string,
|
|
2006
|
+
owner: string,
|
|
2007
|
+
): Promise<boolean> => {
|
|
2008
|
+
if (await hasRunningSubagentsForParent(parentConversationId, owner)) {
|
|
2009
|
+
return true;
|
|
2010
|
+
}
|
|
2011
|
+
if (pendingCallbackNeeded.has(parentConversationId)) {
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
const parentConversation = await conversationStore.get(parentConversationId);
|
|
2015
|
+
if (!parentConversation) return false;
|
|
2016
|
+
if (Array.isArray(parentConversation.pendingSubagentResults) && parentConversation.pendingSubagentResults.length > 0) {
|
|
2017
|
+
return true;
|
|
2018
|
+
}
|
|
2019
|
+
if (typeof parentConversation.runningCallbackSince === "number" && parentConversation.runningCallbackSince > 0) {
|
|
2020
|
+
return true;
|
|
2021
|
+
}
|
|
2022
|
+
return false;
|
|
2023
|
+
};
|
|
2024
|
+
|
|
1644
2025
|
const getSubagentDepth = async (conversationId: string): Promise<number> => {
|
|
1645
2026
|
let depth = 0;
|
|
1646
2027
|
let current = await conversationStore.get(conversationId);
|
|
@@ -1692,7 +2073,9 @@ export const createRequestHandler = async (options?: {
|
|
|
1692
2073
|
const conv = await conversationStore.get(subagentId);
|
|
1693
2074
|
if (!conv || !conv.parentConversationId) return;
|
|
1694
2075
|
|
|
1695
|
-
const allApprovals = conv.pendingApprovals ?? []
|
|
2076
|
+
const allApprovals = (conv.pendingApprovals ?? []).map((approval) =>
|
|
2077
|
+
normalizeApprovalCheckpoint(approval, conv.messages),
|
|
2078
|
+
);
|
|
1696
2079
|
if (allApprovals.length === 0) return;
|
|
1697
2080
|
const allDecided = allApprovals.every(a => a.decision != null);
|
|
1698
2081
|
if (!allDecided) return;
|
|
@@ -1784,9 +2167,11 @@ export const createRequestHandler = async (options?: {
|
|
|
1784
2167
|
conversation.lastActivityAt = Date.now();
|
|
1785
2168
|
await conversationStore.update(conversation);
|
|
1786
2169
|
|
|
1787
|
-
const
|
|
1788
|
-
|
|
1789
|
-
:
|
|
2170
|
+
const runOutcome = resolveRunRequest(conversation, {
|
|
2171
|
+
conversationId: childConversationId,
|
|
2172
|
+
messages: conversation.messages,
|
|
2173
|
+
});
|
|
2174
|
+
const harnessMessages = [...runOutcome.messages];
|
|
1790
2175
|
|
|
1791
2176
|
for await (const event of childHarness.runWithTelemetry({
|
|
1792
2177
|
task,
|
|
@@ -1849,16 +2234,13 @@ export const createRequestHandler = async (options?: {
|
|
|
1849
2234
|
if (event.type === "tool:approval:checkpoint") {
|
|
1850
2235
|
const cpConv = await conversationStore.get(childConversationId);
|
|
1851
2236
|
if (cpConv) {
|
|
1852
|
-
const allCpData
|
|
1853
|
-
|
|
2237
|
+
const allCpData = buildApprovalCheckpoints({
|
|
2238
|
+
approvals: event.approvals,
|
|
1854
2239
|
runId: latestRunId,
|
|
1855
|
-
tool: a.tool,
|
|
1856
|
-
toolCallId: a.toolCallId,
|
|
1857
|
-
input: a.input,
|
|
1858
2240
|
checkpointMessages: [...harnessMessages, ...event.checkpointMessages],
|
|
1859
2241
|
baseMessageCount: 0,
|
|
1860
2242
|
pendingToolCalls: event.pendingToolCalls,
|
|
1861
|
-
})
|
|
2243
|
+
});
|
|
1862
2244
|
cpConv.pendingApprovals = allCpData;
|
|
1863
2245
|
cpConv.updatedAt = Date.now();
|
|
1864
2246
|
await conversationStore.update(cpConv);
|
|
@@ -1875,7 +2257,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1875
2257
|
}
|
|
1876
2258
|
});
|
|
1877
2259
|
|
|
1878
|
-
const checkpointRef = allCpData[0]
|
|
2260
|
+
const checkpointRef = normalizeApprovalCheckpoint(allCpData[0]!, [...harnessMessages]);
|
|
1879
2261
|
const toolContext = {
|
|
1880
2262
|
runId: checkpointRef.runId,
|
|
1881
2263
|
agentId: identity.id,
|
|
@@ -2008,6 +2390,8 @@ export const createRequestHandler = async (options?: {
|
|
|
2008
2390
|
}
|
|
2009
2391
|
if (runResult?.continuationMessages) {
|
|
2010
2392
|
conv._harnessMessages = runResult.continuationMessages;
|
|
2393
|
+
} else if (runOutcome.shouldRebuildCanonical) {
|
|
2394
|
+
conv._harnessMessages = conv.messages;
|
|
2011
2395
|
}
|
|
2012
2396
|
conv._toolResultArchive = childHarness.getToolResultArchive(childConversationId);
|
|
2013
2397
|
conv.lastActivityAt = Date.now();
|
|
@@ -2176,6 +2560,9 @@ export const createRequestHandler = async (options?: {
|
|
|
2176
2560
|
metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp } as Message["metadata"],
|
|
2177
2561
|
});
|
|
2178
2562
|
}
|
|
2563
|
+
// Callback-injected result messages must be visible to the next parent turn.
|
|
2564
|
+
// Keep canonical transcript in sync before resolving callback run history.
|
|
2565
|
+
conversation._harnessMessages = [...conversation.messages];
|
|
2179
2566
|
conversation.updatedAt = Date.now();
|
|
2180
2567
|
await conversationStore.update(conversation);
|
|
2181
2568
|
|
|
@@ -2210,120 +2597,74 @@ export const createRequestHandler = async (options?: {
|
|
|
2210
2597
|
});
|
|
2211
2598
|
}
|
|
2212
2599
|
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
: conversation.
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
let
|
|
2223
|
-
let runContextTokens = conversation.contextTokens ?? 0;
|
|
2224
|
-
let runContextWindow = conversation.contextWindow ?? 0;
|
|
2225
|
-
const toolTimeline: string[] = [];
|
|
2226
|
-
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
2227
|
-
let currentTools: string[] = [];
|
|
2228
|
-
let currentText = "";
|
|
2600
|
+
const historySelection = resolveRunRequest(conversation, {
|
|
2601
|
+
conversationId,
|
|
2602
|
+
messages: conversation.messages,
|
|
2603
|
+
preferContinuation: isContinuationResume,
|
|
2604
|
+
});
|
|
2605
|
+
const historyMessages = [...historySelection.messages];
|
|
2606
|
+
console.info(
|
|
2607
|
+
`[poncho][subagent-callback] conversation="${conversationId}" history_source=${historySelection.source}`,
|
|
2608
|
+
);
|
|
2609
|
+
let execution: ExecuteTurnResult | undefined;
|
|
2229
2610
|
|
|
2230
2611
|
try {
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
currentTools = [];
|
|
2250
|
-
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
2251
|
-
assistantResponse += " ";
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
assistantResponse += event.content;
|
|
2255
|
-
currentText += event.content;
|
|
2256
|
-
}
|
|
2257
|
-
if (event.type === "tool:started") {
|
|
2258
|
-
if (currentText.length > 0) {
|
|
2259
|
-
sections.push({ type: "text", content: currentText });
|
|
2260
|
-
currentText = "";
|
|
2261
|
-
}
|
|
2262
|
-
const toolText = `- start \`${event.tool}\``;
|
|
2263
|
-
toolTimeline.push(toolText);
|
|
2264
|
-
currentTools.push(toolText);
|
|
2265
|
-
}
|
|
2266
|
-
if (event.type === "tool:completed") {
|
|
2267
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
2268
|
-
toolTimeline.push(toolText);
|
|
2269
|
-
currentTools.push(toolText);
|
|
2270
|
-
}
|
|
2271
|
-
if (event.type === "tool:error") {
|
|
2272
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
2273
|
-
toolTimeline.push(toolText);
|
|
2274
|
-
currentTools.push(toolText);
|
|
2275
|
-
}
|
|
2276
|
-
if (event.type === "run:completed") {
|
|
2277
|
-
if (assistantResponse.length === 0 && event.result.response) {
|
|
2278
|
-
assistantResponse = event.result.response;
|
|
2279
|
-
}
|
|
2280
|
-
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
2281
|
-
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
2282
|
-
if (event.result.continuationMessages) {
|
|
2283
|
-
runHarnessMessages = event.result.continuationMessages;
|
|
2284
|
-
}
|
|
2285
|
-
if (event.result.continuation) {
|
|
2286
|
-
runContinuation = true;
|
|
2287
|
-
if (event.result.continuationMessages) {
|
|
2288
|
-
runContinuationMessages = event.result.continuationMessages;
|
|
2289
|
-
}
|
|
2612
|
+
execution = await executeConversationTurn({
|
|
2613
|
+
harness,
|
|
2614
|
+
runInput: {
|
|
2615
|
+
task: undefined,
|
|
2616
|
+
conversationId,
|
|
2617
|
+
parameters: withToolResultArchiveParam({
|
|
2618
|
+
__activeConversationId: conversationId,
|
|
2619
|
+
__ownerId: conversation.ownerId,
|
|
2620
|
+
}, conversation),
|
|
2621
|
+
messages: historyMessages,
|
|
2622
|
+
abortSignal: abortController.signal,
|
|
2623
|
+
},
|
|
2624
|
+
initialContextTokens: conversation.contextTokens ?? 0,
|
|
2625
|
+
initialContextWindow: conversation.contextWindow ?? 0,
|
|
2626
|
+
onEvent: (event) => {
|
|
2627
|
+
if (event.type === "run:started") {
|
|
2628
|
+
const active = activeConversationRuns.get(conversationId);
|
|
2629
|
+
if (active) active.runId = event.runId;
|
|
2290
2630
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
|
|
2296
|
-
if (currentText.length > 0) sections.push({ type: "text", content: currentText });
|
|
2631
|
+
broadcastEvent(conversationId, event);
|
|
2632
|
+
},
|
|
2633
|
+
});
|
|
2634
|
+
flushTurnDraft(execution.draft);
|
|
2297
2635
|
|
|
2298
|
-
|
|
2636
|
+
const callbackNeedsContinuation = execution.runContinuation && execution.runContinuationMessages;
|
|
2637
|
+
if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
|
|
2299
2638
|
const freshConv = await conversationStore.get(conversationId);
|
|
2300
2639
|
if (freshConv) {
|
|
2301
|
-
if (
|
|
2302
|
-
freshConv._continuationMessages = runContinuationMessages;
|
|
2640
|
+
if (callbackNeedsContinuation) {
|
|
2641
|
+
freshConv._continuationMessages = execution.runContinuationMessages;
|
|
2303
2642
|
} else {
|
|
2304
2643
|
freshConv._continuationMessages = undefined;
|
|
2305
2644
|
freshConv.messages.push({
|
|
2306
2645
|
role: "assistant",
|
|
2307
|
-
content: assistantResponse,
|
|
2308
|
-
metadata:
|
|
2309
|
-
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
2310
|
-
: undefined,
|
|
2646
|
+
content: execution.draft.assistantResponse,
|
|
2647
|
+
metadata: buildAssistantMetadata(execution.draft),
|
|
2311
2648
|
});
|
|
2312
2649
|
}
|
|
2313
|
-
if (runHarnessMessages) {
|
|
2314
|
-
freshConv._harnessMessages = runHarnessMessages;
|
|
2650
|
+
if (callbackNeedsContinuation && execution.runHarnessMessages) {
|
|
2651
|
+
freshConv._harnessMessages = execution.runHarnessMessages;
|
|
2652
|
+
} else if (historySelection.shouldRebuildCanonical) {
|
|
2653
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
2654
|
+
} else {
|
|
2655
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
2315
2656
|
}
|
|
2316
2657
|
freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
2317
|
-
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
2658
|
+
freshConv.runtimeRunId = execution.latestRunId || freshConv.runtimeRunId;
|
|
2318
2659
|
freshConv.runningCallbackSince = undefined;
|
|
2319
2660
|
freshConv.runStatus = "idle";
|
|
2320
|
-
if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
|
|
2321
|
-
if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
|
|
2661
|
+
if (execution.runContextTokens > 0) freshConv.contextTokens = execution.runContextTokens;
|
|
2662
|
+
if (execution.runContextWindow > 0) freshConv.contextWindow = execution.runContextWindow;
|
|
2322
2663
|
freshConv.updatedAt = Date.now();
|
|
2323
2664
|
await conversationStore.update(freshConv);
|
|
2324
2665
|
|
|
2325
2666
|
// Proactive messaging notification if conversation has a messaging channel
|
|
2326
|
-
if (freshConv.channelMeta && assistantResponse.length > 0) {
|
|
2667
|
+
if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
|
|
2327
2668
|
const adapter = messagingAdapters.get(freshConv.channelMeta.platform);
|
|
2328
2669
|
if (adapter) {
|
|
2329
2670
|
try {
|
|
@@ -2332,7 +2673,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2332
2673
|
channelId: freshConv.channelMeta.channelId,
|
|
2333
2674
|
platformThreadId: freshConv.channelMeta.platformThreadId,
|
|
2334
2675
|
},
|
|
2335
|
-
assistantResponse,
|
|
2676
|
+
execution.draft.assistantResponse,
|
|
2336
2677
|
);
|
|
2337
2678
|
} catch (sendErr) {
|
|
2338
2679
|
console.error(`[poncho][subagent-callback] Messaging notify failed:`, sendErr instanceof Error ? sendErr.message : sendErr);
|
|
@@ -2343,7 +2684,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2343
2684
|
}
|
|
2344
2685
|
|
|
2345
2686
|
// Handle continuation for the callback run itself
|
|
2346
|
-
if (runContinuation) {
|
|
2687
|
+
if (execution.runContinuation) {
|
|
2347
2688
|
if (isServerless) {
|
|
2348
2689
|
const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(err =>
|
|
2349
2690
|
console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
@@ -2365,7 +2706,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2365
2706
|
}
|
|
2366
2707
|
} finally {
|
|
2367
2708
|
activeConversationRuns.delete(conversationId);
|
|
2368
|
-
finishConversationStream(conversationId);
|
|
2369
2709
|
|
|
2370
2710
|
// Check both the in-memory flag (always reliable) and the store.
|
|
2371
2711
|
// We drain the flag first so a concurrent triggerParentCallback that
|
|
@@ -2374,6 +2714,16 @@ export const createRequestHandler = async (options?: {
|
|
|
2374
2714
|
const hadDeferredTrigger = pendingCallbackNeeded.delete(conversationId);
|
|
2375
2715
|
const freshConv = await conversationStore.get(conversationId);
|
|
2376
2716
|
const hasPendingInStore = !!freshConv?.pendingSubagentResults?.length;
|
|
2717
|
+
const hasRunningCallbackChildren = Array.from(activeSubagentRuns.values()).some(
|
|
2718
|
+
(run) => run.parentConversationId === conversationId,
|
|
2719
|
+
);
|
|
2720
|
+
|
|
2721
|
+
// Only close the event stream when no further subagent work is pending.
|
|
2722
|
+
// If more callbacks are about to run, the stream stays alive so clients
|
|
2723
|
+
// receive the next callback's events through the same SSE connection.
|
|
2724
|
+
if (!hadDeferredTrigger && !hasPendingInStore && !hasRunningCallbackChildren) {
|
|
2725
|
+
finishConversationStream(conversationId);
|
|
2726
|
+
}
|
|
2377
2727
|
|
|
2378
2728
|
if (hadDeferredTrigger || hasPendingInStore) {
|
|
2379
2729
|
// Re-trigger immediately. Skip the runningCallbackSince lock check
|
|
@@ -2551,10 +2901,11 @@ export const createRequestHandler = async (options?: {
|
|
|
2551
2901
|
let runContextWindow = conversation.contextWindow ?? 0;
|
|
2552
2902
|
let resumeHarnessMessages: Message[] | undefined;
|
|
2553
2903
|
|
|
2554
|
-
const
|
|
2555
|
-
|
|
2904
|
+
const normalizedCheckpoint = normalizeApprovalCheckpoint(checkpoint, conversation.messages);
|
|
2905
|
+
const baseMessages = normalizedCheckpoint.baseMessageCount != null
|
|
2906
|
+
? conversation.messages.slice(0, normalizedCheckpoint.baseMessageCount)
|
|
2556
2907
|
: [];
|
|
2557
|
-
const fullCheckpointMessages = [...baseMessages, ...
|
|
2908
|
+
const fullCheckpointMessages = [...baseMessages, ...normalizedCheckpoint.checkpointMessages!];
|
|
2558
2909
|
|
|
2559
2910
|
// Build the tool result message that continueFromToolResult will also
|
|
2560
2911
|
// construct internally. We need it here so that if the resumed run hits
|
|
@@ -2645,25 +2996,27 @@ export const createRequestHandler = async (options?: {
|
|
|
2645
2996
|
if (event.type === "tool:approval:checkpoint") {
|
|
2646
2997
|
const conv = await conversationStore.get(conversationId);
|
|
2647
2998
|
if (conv) {
|
|
2648
|
-
conv.pendingApprovals =
|
|
2649
|
-
|
|
2999
|
+
conv.pendingApprovals = buildApprovalCheckpoints({
|
|
3000
|
+
approvals: event.approvals,
|
|
2650
3001
|
runId: latestRunId,
|
|
2651
|
-
tool: a.tool,
|
|
2652
|
-
toolCallId: a.toolCallId,
|
|
2653
|
-
input: a.input,
|
|
2654
3002
|
checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
|
|
2655
3003
|
baseMessageCount: 0,
|
|
2656
3004
|
pendingToolCalls: event.pendingToolCalls,
|
|
2657
|
-
})
|
|
3005
|
+
});
|
|
2658
3006
|
conv.updatedAt = Date.now();
|
|
2659
3007
|
await conversationStore.update(conv);
|
|
2660
3008
|
|
|
2661
3009
|
if (conv.channelMeta?.platform === "telegram") {
|
|
2662
3010
|
const tgAdapter = messagingAdapters.get("telegram") as TelegramAdapter | undefined;
|
|
2663
3011
|
if (tgAdapter) {
|
|
3012
|
+
const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
|
|
3013
|
+
conv.channelMeta.platformThreadId,
|
|
3014
|
+
conv.channelMeta.channelId,
|
|
3015
|
+
);
|
|
2664
3016
|
void tgAdapter.sendApprovalRequest(
|
|
2665
3017
|
conv.channelMeta.channelId,
|
|
2666
3018
|
event.approvals.map(a => ({ approvalId: a.approvalId, tool: a.tool, input: a.input })),
|
|
3019
|
+
{ message_thread_id: messageThreadId },
|
|
2667
3020
|
).catch(() => {});
|
|
2668
3021
|
}
|
|
2669
3022
|
}
|
|
@@ -2745,6 +3098,8 @@ export const createRequestHandler = async (options?: {
|
|
|
2745
3098
|
}
|
|
2746
3099
|
if (resumeHarnessMessages) {
|
|
2747
3100
|
conv._harnessMessages = resumeHarnessMessages;
|
|
3101
|
+
} else {
|
|
3102
|
+
conv._harnessMessages = conv.messages;
|
|
2748
3103
|
}
|
|
2749
3104
|
conv.runtimeRunId = latestRunId || conv.runtimeRunId;
|
|
2750
3105
|
conv.pendingApprovals = [];
|
|
@@ -2763,7 +3118,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2763
3118
|
}
|
|
2764
3119
|
}
|
|
2765
3120
|
|
|
2766
|
-
finishConversationStream(conversationId);
|
|
2767
3121
|
activeConversationRuns.delete(conversationId);
|
|
2768
3122
|
if (latestRunId) {
|
|
2769
3123
|
runOwners.delete(latestRunId);
|
|
@@ -2774,7 +3128,14 @@ export const createRequestHandler = async (options?: {
|
|
|
2774
3128
|
// Check for pending subagent results that arrived during the run
|
|
2775
3129
|
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
2776
3130
|
const postConv = await conversationStore.get(conversationId);
|
|
2777
|
-
|
|
3131
|
+
const needsResumeCallback = hadDeferred || !!postConv?.pendingSubagentResults?.length;
|
|
3132
|
+
const hasRunningResumeChildren = Array.from(activeSubagentRuns.values()).some(
|
|
3133
|
+
(run) => run.parentConversationId === conversationId,
|
|
3134
|
+
);
|
|
3135
|
+
if (!needsResumeCallback && !hasRunningResumeChildren) {
|
|
3136
|
+
finishConversationStream(conversationId);
|
|
3137
|
+
}
|
|
3138
|
+
if (needsResumeCallback) {
|
|
2778
3139
|
processSubagentCallback(conversationId, true).catch(err =>
|
|
2779
3140
|
console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err),
|
|
2780
3141
|
);
|
|
@@ -2786,7 +3147,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2786
3147
|
// adapter handles its own request verification (e.g. Slack signing secret).
|
|
2787
3148
|
// ---------------------------------------------------------------------------
|
|
2788
3149
|
const messagingRoutes = new Map<string, Map<string, (req: IncomingMessage, res: ServerResponse) => Promise<void>>>();
|
|
2789
|
-
const messagingRunQueues = new Map<string, Promise<void>>();
|
|
2790
3150
|
const messagingRouteRegistrar: RouteRegistrar = (method, path, routeHandler) => {
|
|
2791
3151
|
let byMethod = messagingRoutes.get(path);
|
|
2792
3152
|
if (!byMethod) {
|
|
@@ -2808,7 +3168,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2808
3168
|
};
|
|
2809
3169
|
await conversationStore.update(existing);
|
|
2810
3170
|
}
|
|
2811
|
-
return { messages: existing.
|
|
3171
|
+
return { messages: loadCanonicalHistory(existing).messages };
|
|
2812
3172
|
}
|
|
2813
3173
|
const now = Date.now();
|
|
2814
3174
|
const channelMeta = meta.channelId
|
|
@@ -2832,28 +3192,22 @@ export const createRequestHandler = async (options?: {
|
|
|
2832
3192
|
return { messages: [] };
|
|
2833
3193
|
},
|
|
2834
3194
|
async run(conversationId, input) {
|
|
2835
|
-
const
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
? [...latestConversation._harnessMessages]
|
|
2848
|
-
: [...latestConversation.messages])
|
|
2849
|
-
: [...input.messages];
|
|
2850
|
-
|
|
2851
|
-
const isContinuation = input.task == null;
|
|
2852
|
-
console.log("[messaging-runner] starting run for", conversationId, isContinuation ? "(continuation)" : `task: ${input.task!.slice(0, 80)}`);
|
|
3195
|
+
const latestConversation = await conversationStore.get(conversationId);
|
|
3196
|
+
const canonicalHistory = latestConversation
|
|
3197
|
+
? loadCanonicalHistory(latestConversation)
|
|
3198
|
+
: { messages: [...input.messages], source: "messages" as const };
|
|
3199
|
+
const shouldRebuildCanonical = canonicalHistory.source !== "harness";
|
|
3200
|
+
|
|
3201
|
+
const isContinuation = input.task == null;
|
|
3202
|
+
console.log(
|
|
3203
|
+
`[messaging-runner] starting run for ${conversationId} ` +
|
|
3204
|
+
`${isContinuation ? "(continuation)" : `task: ${input.task!.slice(0, 80)}`} ` +
|
|
3205
|
+
`history_source=${canonicalHistory.source}`,
|
|
3206
|
+
);
|
|
2853
3207
|
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3208
|
+
const historyMessages = [...canonicalHistory.messages];
|
|
3209
|
+
const preRunMessages = [...canonicalHistory.messages];
|
|
3210
|
+
const userContent = input.task;
|
|
2857
3211
|
|
|
2858
3212
|
// Read-modify-write helper: always fetches the latest version from
|
|
2859
3213
|
// the store before writing, so concurrent writers don't get clobbered.
|
|
@@ -2875,11 +3229,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2875
3229
|
});
|
|
2876
3230
|
|
|
2877
3231
|
let latestRunId = "";
|
|
2878
|
-
|
|
2879
|
-
const toolTimeline: string[] = [];
|
|
2880
|
-
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
2881
|
-
let currentTools: string[] = [];
|
|
2882
|
-
let currentText = "";
|
|
3232
|
+
const draft = createTurnDraftState();
|
|
2883
3233
|
let checkpointedRun = false;
|
|
2884
3234
|
let runContextTokens = 0;
|
|
2885
3235
|
let runContextWindow = 0;
|
|
@@ -2889,23 +3239,18 @@ export const createRequestHandler = async (options?: {
|
|
|
2889
3239
|
let runMaxSteps: number | undefined;
|
|
2890
3240
|
|
|
2891
3241
|
const buildMessages = (): Message[] => {
|
|
2892
|
-
const draftSections
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
content: Array.isArray(s.content) ? [...s.content] : s.content,
|
|
2896
|
-
})),
|
|
2897
|
-
];
|
|
2898
|
-
if (currentTools.length > 0) {
|
|
2899
|
-
draftSections.push({ type: "tools", content: [...currentTools] });
|
|
3242
|
+
const draftSections = cloneSections(draft.sections);
|
|
3243
|
+
if (draft.currentTools.length > 0) {
|
|
3244
|
+
draftSections.push({ type: "tools", content: [...draft.currentTools] });
|
|
2900
3245
|
}
|
|
2901
|
-
if (currentText.length > 0) {
|
|
2902
|
-
draftSections.push({ type: "text", content: currentText });
|
|
3246
|
+
if (draft.currentText.length > 0) {
|
|
3247
|
+
draftSections.push({ type: "text", content: draft.currentText });
|
|
2903
3248
|
}
|
|
2904
3249
|
const userTurn: Message[] = userContent != null
|
|
2905
3250
|
? [{ role: "user" as const, content: userContent }]
|
|
2906
3251
|
: [];
|
|
2907
3252
|
const hasDraftContent =
|
|
2908
|
-
assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
|
|
3253
|
+
draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
|
|
2909
3254
|
if (!hasDraftContent) {
|
|
2910
3255
|
return [...historyMessages, ...userTurn];
|
|
2911
3256
|
}
|
|
@@ -2914,20 +3259,14 @@ export const createRequestHandler = async (options?: {
|
|
|
2914
3259
|
...userTurn,
|
|
2915
3260
|
{
|
|
2916
3261
|
role: "assistant" as const,
|
|
2917
|
-
content: assistantResponse,
|
|
2918
|
-
metadata:
|
|
2919
|
-
toolTimeline.length > 0 || draftSections.length > 0
|
|
2920
|
-
? ({
|
|
2921
|
-
toolActivity: [...toolTimeline],
|
|
2922
|
-
sections: draftSections.length > 0 ? draftSections : undefined,
|
|
2923
|
-
} as Message["metadata"])
|
|
2924
|
-
: undefined,
|
|
3262
|
+
content: draft.assistantResponse,
|
|
3263
|
+
metadata: buildAssistantMetadata(draft, draftSections),
|
|
2925
3264
|
},
|
|
2926
3265
|
];
|
|
2927
3266
|
};
|
|
2928
3267
|
|
|
2929
3268
|
const persistDraftAssistantTurn = async (): Promise<void> => {
|
|
2930
|
-
if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
|
|
3269
|
+
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
2931
3270
|
await updateConversation((c) => {
|
|
2932
3271
|
c.messages = buildMessages();
|
|
2933
3272
|
});
|
|
@@ -2950,64 +3289,39 @@ export const createRequestHandler = async (options?: {
|
|
|
2950
3289
|
};
|
|
2951
3290
|
|
|
2952
3291
|
try {
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
}
|
|
2967
|
-
assistantResponse += event.content;
|
|
2968
|
-
currentText += event.content;
|
|
2969
|
-
}
|
|
2970
|
-
if (event.type === "tool:started") {
|
|
2971
|
-
if (currentText.length > 0) {
|
|
2972
|
-
sections.push({ type: "text", content: currentText });
|
|
2973
|
-
currentText = "";
|
|
3292
|
+
const execution = await executeConversationTurn({
|
|
3293
|
+
harness,
|
|
3294
|
+
runInput,
|
|
3295
|
+
onEvent: async (event, eventDraft) => {
|
|
3296
|
+
draft.assistantResponse = eventDraft.assistantResponse;
|
|
3297
|
+
draft.toolTimeline = eventDraft.toolTimeline;
|
|
3298
|
+
draft.sections = eventDraft.sections;
|
|
3299
|
+
draft.currentTools = eventDraft.currentTools;
|
|
3300
|
+
draft.currentText = eventDraft.currentText;
|
|
3301
|
+
if (event.type === "run:started") {
|
|
3302
|
+
latestRunId = event.runId;
|
|
3303
|
+
runOwners.set(event.runId, "local-owner");
|
|
3304
|
+
runConversations.set(event.runId, conversationId);
|
|
2974
3305
|
}
|
|
2975
|
-
const toolText = `- start \`${event.tool}\``;
|
|
2976
|
-
toolTimeline.push(toolText);
|
|
2977
|
-
currentTools.push(toolText);
|
|
2978
|
-
}
|
|
2979
|
-
if (event.type === "tool:completed") {
|
|
2980
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
2981
|
-
toolTimeline.push(toolText);
|
|
2982
|
-
currentTools.push(toolText);
|
|
2983
|
-
}
|
|
2984
|
-
if (event.type === "tool:error") {
|
|
2985
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
2986
|
-
toolTimeline.push(toolText);
|
|
2987
|
-
currentTools.push(toolText);
|
|
2988
|
-
}
|
|
2989
3306
|
if (event.type === "step:completed") {
|
|
2990
3307
|
await persistDraftAssistantTurn();
|
|
2991
3308
|
}
|
|
2992
3309
|
if (event.type === "tool:approval:required") {
|
|
2993
3310
|
const toolText = `- approval required \`${event.tool}\``;
|
|
2994
|
-
toolTimeline.push(toolText);
|
|
2995
|
-
currentTools.push(toolText);
|
|
3311
|
+
draft.toolTimeline.push(toolText);
|
|
3312
|
+
draft.currentTools.push(toolText);
|
|
2996
3313
|
await persistDraftAssistantTurn();
|
|
2997
3314
|
}
|
|
2998
3315
|
if (event.type === "tool:approval:checkpoint") {
|
|
2999
3316
|
await updateConversation((c) => {
|
|
3000
3317
|
c.messages = buildMessages();
|
|
3001
|
-
c.pendingApprovals =
|
|
3002
|
-
|
|
3318
|
+
c.pendingApprovals = buildApprovalCheckpoints({
|
|
3319
|
+
approvals: event.approvals,
|
|
3003
3320
|
runId: latestRunId,
|
|
3004
|
-
tool: a.tool,
|
|
3005
|
-
toolCallId: a.toolCallId,
|
|
3006
|
-
input: a.input,
|
|
3007
3321
|
checkpointMessages: event.checkpointMessages,
|
|
3008
3322
|
baseMessageCount: historyMessages.length,
|
|
3009
3323
|
pendingToolCalls: event.pendingToolCalls,
|
|
3010
|
-
})
|
|
3324
|
+
});
|
|
3011
3325
|
});
|
|
3012
3326
|
checkpointedRun = true;
|
|
3013
3327
|
|
|
@@ -3021,9 +3335,14 @@ export const createRequestHandler = async (options?: {
|
|
|
3021
3335
|
tool: a.tool,
|
|
3022
3336
|
input: a.input,
|
|
3023
3337
|
}));
|
|
3338
|
+
const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
|
|
3339
|
+
conv.channelMeta.platformThreadId,
|
|
3340
|
+
conv.channelMeta.channelId,
|
|
3341
|
+
);
|
|
3024
3342
|
void tgAdapter.sendApprovalRequest(
|
|
3025
3343
|
conv.channelMeta.channelId,
|
|
3026
3344
|
approvals,
|
|
3345
|
+
{ message_thread_id: messageThreadId },
|
|
3027
3346
|
).catch((err: unknown) => {
|
|
3028
3347
|
console.error("[messaging-runner] failed to send Telegram approval request:", err instanceof Error ? err.message : err);
|
|
3029
3348
|
});
|
|
@@ -3046,38 +3365,22 @@ export const createRequestHandler = async (options?: {
|
|
|
3046
3365
|
});
|
|
3047
3366
|
}
|
|
3048
3367
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
3060
|
-
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
3061
|
-
}
|
|
3062
|
-
if (event.type === "run:error") {
|
|
3063
|
-
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
3064
|
-
}
|
|
3065
|
-
broadcastEvent(conversationId, event);
|
|
3066
|
-
}
|
|
3368
|
+
broadcastEvent(conversationId, event);
|
|
3369
|
+
},
|
|
3370
|
+
});
|
|
3371
|
+
runContinuation = execution.runContinuation;
|
|
3372
|
+
runContinuationMessages = execution.runContinuationMessages;
|
|
3373
|
+
runSteps = execution.runSteps;
|
|
3374
|
+
runMaxSteps = execution.runMaxSteps;
|
|
3375
|
+
runContextTokens = execution.runContextTokens;
|
|
3376
|
+
runContextWindow = execution.runContextWindow;
|
|
3377
|
+
latestRunId = execution.latestRunId || latestRunId;
|
|
3067
3378
|
} catch (err) {
|
|
3068
3379
|
console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
|
|
3069
|
-
assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
3380
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
3070
3381
|
}
|
|
3071
3382
|
|
|
3072
|
-
|
|
3073
|
-
if (currentTools.length > 0) {
|
|
3074
|
-
sections.push({ type: "tools", content: currentTools });
|
|
3075
|
-
currentTools = [];
|
|
3076
|
-
}
|
|
3077
|
-
if (currentText.length > 0) {
|
|
3078
|
-
sections.push({ type: "text", content: currentText });
|
|
3079
|
-
currentText = "";
|
|
3080
|
-
}
|
|
3383
|
+
flushTurnDraft(draft);
|
|
3081
3384
|
|
|
3082
3385
|
if (!checkpointedRun) {
|
|
3083
3386
|
await updateConversation((c) => {
|
|
@@ -3089,6 +3392,10 @@ export const createRequestHandler = async (options?: {
|
|
|
3089
3392
|
}
|
|
3090
3393
|
if (runContinuationMessages) {
|
|
3091
3394
|
c._harnessMessages = runContinuationMessages;
|
|
3395
|
+
} else if (shouldRebuildCanonical) {
|
|
3396
|
+
c._harnessMessages = c.messages;
|
|
3397
|
+
} else {
|
|
3398
|
+
c._harnessMessages = c.messages;
|
|
3092
3399
|
}
|
|
3093
3400
|
c.runtimeRunId = latestRunId || c.runtimeRunId;
|
|
3094
3401
|
c.pendingApprovals = [];
|
|
@@ -3098,6 +3405,9 @@ export const createRequestHandler = async (options?: {
|
|
|
3098
3405
|
});
|
|
3099
3406
|
} else {
|
|
3100
3407
|
await updateConversation((c) => {
|
|
3408
|
+
if (shouldRebuildCanonical && !c._harnessMessages?.length) {
|
|
3409
|
+
c._harnessMessages = c.messages;
|
|
3410
|
+
}
|
|
3101
3411
|
c.runStatus = "idle";
|
|
3102
3412
|
});
|
|
3103
3413
|
}
|
|
@@ -3107,21 +3417,15 @@ export const createRequestHandler = async (options?: {
|
|
|
3107
3417
|
runConversations.delete(latestRunId);
|
|
3108
3418
|
}
|
|
3109
3419
|
|
|
3110
|
-
|
|
3111
|
-
|
|
3420
|
+
console.log("[messaging-runner] run complete, response length:", draft.assistantResponse.length, runContinuation ? "(continuation)" : "");
|
|
3421
|
+
const response = draft.assistantResponse;
|
|
3112
3422
|
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
} finally {
|
|
3120
|
-
releaseQueue?.();
|
|
3121
|
-
if (messagingRunQueues.get(conversationId) === chained) {
|
|
3122
|
-
messagingRunQueues.delete(conversationId);
|
|
3123
|
-
}
|
|
3124
|
-
}
|
|
3423
|
+
return {
|
|
3424
|
+
response,
|
|
3425
|
+
continuation: runContinuation,
|
|
3426
|
+
steps: runSteps,
|
|
3427
|
+
maxSteps: runMaxSteps,
|
|
3428
|
+
};
|
|
3125
3429
|
},
|
|
3126
3430
|
};
|
|
3127
3431
|
|
|
@@ -3271,6 +3575,7 @@ export const createRequestHandler = async (options?: {
|
|
|
3271
3575
|
): AsyncGenerator<AgentEvent> {
|
|
3272
3576
|
const conversation = await conversationStore.get(conversationId);
|
|
3273
3577
|
if (!conversation) return;
|
|
3578
|
+
if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
|
|
3274
3579
|
if (!conversation._continuationMessages?.length) return;
|
|
3275
3580
|
if (conversation.runStatus === "running") return;
|
|
3276
3581
|
|
|
@@ -3438,7 +3743,11 @@ export const createRequestHandler = async (options?: {
|
|
|
3438
3743
|
freshConv._continuationCount = undefined;
|
|
3439
3744
|
}
|
|
3440
3745
|
|
|
3441
|
-
if (nextHarnessMessages)
|
|
3746
|
+
if (nextHarnessMessages) {
|
|
3747
|
+
freshConv._harnessMessages = nextHarnessMessages;
|
|
3748
|
+
} else {
|
|
3749
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
3750
|
+
}
|
|
3442
3751
|
freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
3443
3752
|
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
3444
3753
|
freshConv.pendingApprovals = [];
|
|
@@ -3575,6 +3884,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3575
3884
|
}
|
|
3576
3885
|
if (runResult?.continuationMessages) {
|
|
3577
3886
|
conv._harnessMessages = runResult.continuationMessages;
|
|
3887
|
+
} else {
|
|
3888
|
+
conv._harnessMessages = conv.messages;
|
|
3578
3889
|
}
|
|
3579
3890
|
conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
|
|
3580
3891
|
conv.lastActivityAt = Date.now();
|
|
@@ -3774,23 +4085,15 @@ export const createRequestHandler = async (options?: {
|
|
|
3774
4085
|
}
|
|
3775
4086
|
|
|
3776
4087
|
// Regular (non-subagent) approval
|
|
3777
|
-
const
|
|
3778
|
-
let foundConversation
|
|
3779
|
-
let foundApproval
|
|
3780
|
-
for (const conv of conversations) {
|
|
3781
|
-
if (!Array.isArray(conv.pendingApprovals)) continue;
|
|
3782
|
-
const match = conv.pendingApprovals.find(a => a.approvalId === approvalId);
|
|
3783
|
-
if (match) {
|
|
3784
|
-
foundConversation = conv;
|
|
3785
|
-
foundApproval = match;
|
|
3786
|
-
break;
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
4088
|
+
const found = await findPendingApproval(approvalId, "local-owner");
|
|
4089
|
+
let foundConversation = found?.conversation;
|
|
4090
|
+
let foundApproval = found?.approval;
|
|
3789
4091
|
|
|
3790
4092
|
if (!foundConversation || !foundApproval) {
|
|
3791
4093
|
console.warn("[telegram-approval] approval not found:", approvalId);
|
|
3792
4094
|
return;
|
|
3793
4095
|
}
|
|
4096
|
+
foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
|
|
3794
4097
|
|
|
3795
4098
|
await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
|
|
3796
4099
|
|
|
@@ -3802,7 +4105,9 @@ export const createRequestHandler = async (options?: {
|
|
|
3802
4105
|
: { type: "tool:approval:denied", approvalId },
|
|
3803
4106
|
);
|
|
3804
4107
|
|
|
3805
|
-
const allApprovals = foundConversation.pendingApprovals ?? []
|
|
4108
|
+
const allApprovals = (foundConversation.pendingApprovals ?? []).map((approval) =>
|
|
4109
|
+
normalizeApprovalCheckpoint(approval, foundConversation!.messages),
|
|
4110
|
+
);
|
|
3806
4111
|
const allDecided = allApprovals.length > 0 && allApprovals.every(a => a.decision != null);
|
|
3807
4112
|
|
|
3808
4113
|
if (!allDecided) {
|
|
@@ -4220,6 +4525,9 @@ export const createRequestHandler = async (options?: {
|
|
|
4220
4525
|
pathname !== "/api/auth/login" &&
|
|
4221
4526
|
request.headers["x-csrf-token"] !== session?.csrfToken
|
|
4222
4527
|
) {
|
|
4528
|
+
console.warn(
|
|
4529
|
+
`[poncho][csrf] blocked request method=${request.method} path="${pathname}" session=${session.sessionId}`,
|
|
4530
|
+
);
|
|
4223
4531
|
writeJson(response, 403, {
|
|
4224
4532
|
code: "CSRF_ERROR",
|
|
4225
4533
|
message: "Invalid CSRF token",
|
|
@@ -4443,8 +4751,11 @@ export const createRequestHandler = async (options?: {
|
|
|
4443
4751
|
const approvalMatch = pathname.match(/^\/api\/approvals\/([^/]+)$/);
|
|
4444
4752
|
if (approvalMatch && request.method === "POST") {
|
|
4445
4753
|
const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
|
|
4446
|
-
const body = (await readRequestBody(request)) as { approved?: boolean };
|
|
4754
|
+
const body = (await readRequestBody(request)) as { approved?: boolean; conversationId?: string };
|
|
4447
4755
|
const approved = body.approved === true;
|
|
4756
|
+
const hintedConversationId = typeof body.conversationId === "string" && body.conversationId.trim().length > 0
|
|
4757
|
+
? body.conversationId.trim()
|
|
4758
|
+
: undefined;
|
|
4448
4759
|
|
|
4449
4760
|
// Check if this is a pending subagent approval (handled inline by runSubagent)
|
|
4450
4761
|
const pendingSubagent = pendingSubagentApprovals.get(approvalId);
|
|
@@ -4480,18 +4791,23 @@ export const createRequestHandler = async (options?: {
|
|
|
4480
4791
|
}
|
|
4481
4792
|
|
|
4482
4793
|
// Find the approval in the conversation store (checkpoint-based flow)
|
|
4483
|
-
const conversations = await conversationStore.list(ownerId);
|
|
4484
4794
|
let foundConversation: Conversation | undefined;
|
|
4485
4795
|
let foundApproval: NonNullable<Conversation["pendingApprovals"]>[number] | undefined;
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4796
|
+
if (hintedConversationId) {
|
|
4797
|
+
const hintedConversation = await conversationStore.get(hintedConversationId);
|
|
4798
|
+
if (hintedConversation && hintedConversation.ownerId === ownerId && Array.isArray(hintedConversation.pendingApprovals)) {
|
|
4799
|
+
const hintedMatch = hintedConversation.pendingApprovals.find((approval) => approval.approvalId === approvalId);
|
|
4800
|
+
if (hintedMatch) {
|
|
4801
|
+
foundConversation = hintedConversation;
|
|
4802
|
+
foundApproval = hintedMatch;
|
|
4803
|
+
}
|
|
4493
4804
|
}
|
|
4494
4805
|
}
|
|
4806
|
+
if (!foundConversation || !foundApproval) {
|
|
4807
|
+
const found = await findPendingApproval(approvalId, ownerId);
|
|
4808
|
+
foundConversation = found?.conversation;
|
|
4809
|
+
foundApproval = found?.approval;
|
|
4810
|
+
}
|
|
4495
4811
|
|
|
4496
4812
|
if (!foundConversation || !foundApproval) {
|
|
4497
4813
|
writeJson(response, 404, {
|
|
@@ -4502,28 +4818,23 @@ export const createRequestHandler = async (options?: {
|
|
|
4502
4818
|
}
|
|
4503
4819
|
|
|
4504
4820
|
const conversationId = foundConversation.conversationId;
|
|
4821
|
+
foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
|
|
4505
4822
|
|
|
4506
4823
|
if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
writeJson(response, 404, {
|
|
4511
|
-
code: "APPROVAL_NOT_FOUND",
|
|
4512
|
-
message: "Approval request is no longer active (no checkpoint data)",
|
|
4824
|
+
writeJson(response, 409, {
|
|
4825
|
+
code: "APPROVAL_NOT_READY",
|
|
4826
|
+
message: "Approval checkpoint is not ready yet. Please retry shortly.",
|
|
4513
4827
|
});
|
|
4514
4828
|
return;
|
|
4515
4829
|
}
|
|
4516
4830
|
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
batchDecisions.set(approvalId, approved);
|
|
4525
|
-
|
|
4526
|
-
foundApproval.decision = approved ? "approved" : "denied";
|
|
4831
|
+
const approvalDecision = approved ? "approved" : "denied";
|
|
4832
|
+
foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).map((approval) =>
|
|
4833
|
+
approval.approvalId === approvalId
|
|
4834
|
+
? { ...normalizeApprovalCheckpoint(approval, foundConversation!.messages), decision: approvalDecision }
|
|
4835
|
+
: normalizeApprovalCheckpoint(approval, foundConversation!.messages),
|
|
4836
|
+
);
|
|
4837
|
+
await conversationStore.update(foundConversation);
|
|
4527
4838
|
|
|
4528
4839
|
broadcastEvent(conversationId,
|
|
4529
4840
|
approved
|
|
@@ -4531,25 +4842,18 @@ export const createRequestHandler = async (options?: {
|
|
|
4531
4842
|
: { type: "tool:approval:denied", approvalId },
|
|
4532
4843
|
);
|
|
4533
4844
|
|
|
4534
|
-
const
|
|
4845
|
+
const refreshedConversation = await conversationStore.get(conversationId);
|
|
4846
|
+
const allApprovals = (refreshedConversation?.pendingApprovals ?? []).map((approval) =>
|
|
4847
|
+
normalizeApprovalCheckpoint(approval, refreshedConversation!.messages),
|
|
4848
|
+
);
|
|
4535
4849
|
const allDecided = allApprovals.length > 0 &&
|
|
4536
|
-
allApprovals.every(a =>
|
|
4850
|
+
allApprovals.every(a => a.decision != null);
|
|
4537
4851
|
|
|
4538
4852
|
if (!allDecided) {
|
|
4539
|
-
// Still waiting for more decisions — persist best-effort and respond.
|
|
4540
|
-
// The write may be overwritten by a concurrent request, but that's
|
|
4541
|
-
// fine: the in-memory tracker is the source of truth for completion.
|
|
4542
|
-
await conversationStore.update(foundConversation);
|
|
4543
4853
|
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
|
|
4544
4854
|
return;
|
|
4545
4855
|
}
|
|
4546
4856
|
|
|
4547
|
-
// All approvals in the batch are decided — apply tracked decisions,
|
|
4548
|
-
// execute approved tools, and resume the run.
|
|
4549
|
-
for (const a of allApprovals) {
|
|
4550
|
-
const d = batchDecisions.get(a.approvalId);
|
|
4551
|
-
if (d != null) a.decision = d ? "approved" : "denied";
|
|
4552
|
-
}
|
|
4553
4857
|
approvalDecisionTracker.delete(conversationId);
|
|
4554
4858
|
|
|
4555
4859
|
foundConversation.pendingApprovals = [];
|
|
@@ -4787,6 +5091,7 @@ export const createRequestHandler = async (options?: {
|
|
|
4787
5091
|
runId: a.runId,
|
|
4788
5092
|
tool: a.tool,
|
|
4789
5093
|
input: a.input,
|
|
5094
|
+
decision: a.decision,
|
|
4790
5095
|
}))
|
|
4791
5096
|
: [];
|
|
4792
5097
|
// Collect pending approvals from subagent conversations (in-memory map, no disk I/O)
|
|
@@ -4805,26 +5110,17 @@ export const createRequestHandler = async (options?: {
|
|
|
4805
5110
|
}
|
|
4806
5111
|
const activeStream = conversationEventStreams.get(conversationId);
|
|
4807
5112
|
const hasActiveRun = (!!activeStream && !activeStream.finished) || conversation.runStatus === "running";
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
// On serverless, in-memory maps may be empty — also check store
|
|
4812
|
-
if (!hasRunningSubagents && !conversation.parentConversationId) {
|
|
4813
|
-
const summaries = await conversationStore.listSummaries(conversation.ownerId);
|
|
4814
|
-
for (const s of summaries) {
|
|
4815
|
-
if (s.parentConversationId !== conversationId) continue;
|
|
4816
|
-
const c = await conversationStore.get(s.conversationId);
|
|
4817
|
-
if (c?.subagentMeta?.status === "running") {
|
|
4818
|
-
hasRunningSubagents = true;
|
|
4819
|
-
break;
|
|
4820
|
-
}
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
5113
|
+
const hasRunningSubagents = !conversation.parentConversationId
|
|
5114
|
+
? await hasRunningSubagentsForParent(conversationId, conversation.ownerId)
|
|
5115
|
+
: false;
|
|
4823
5116
|
const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults)
|
|
4824
5117
|
&& conversation.pendingSubagentResults.length > 0;
|
|
5118
|
+
const hasPendingApprovals = Array.isArray(conversation.pendingApprovals)
|
|
5119
|
+
&& conversation.pendingApprovals.length > 0;
|
|
4825
5120
|
const needsContinuation = !hasActiveRun
|
|
4826
5121
|
&& Array.isArray(conversation._continuationMessages)
|
|
4827
|
-
&& conversation._continuationMessages.length > 0
|
|
5122
|
+
&& conversation._continuationMessages.length > 0
|
|
5123
|
+
&& !hasPendingApprovals;
|
|
4828
5124
|
writeJson(response, 200, {
|
|
4829
5125
|
conversation: {
|
|
4830
5126
|
...conversation,
|
|
@@ -5036,11 +5332,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5036
5332
|
eventCount++;
|
|
5037
5333
|
let sseEvent: AgentEvent = event;
|
|
5038
5334
|
if (sseEvent.type === "run:completed") {
|
|
5039
|
-
const
|
|
5040
|
-
r => r.parentConversationId === conversationId,
|
|
5041
|
-
);
|
|
5335
|
+
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
5042
5336
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
5043
|
-
sseEvent =
|
|
5337
|
+
sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
|
|
5044
5338
|
}
|
|
5045
5339
|
try {
|
|
5046
5340
|
response.write(formatSseEvent(sseEvent));
|
|
@@ -5067,7 +5361,10 @@ export const createRequestHandler = async (options?: {
|
|
|
5067
5361
|
// fire a delayed safety net in case the client disconnects before
|
|
5068
5362
|
// POSTing the next /continue.
|
|
5069
5363
|
const freshConv = await conversationStore.get(conversationId);
|
|
5070
|
-
if (
|
|
5364
|
+
if (
|
|
5365
|
+
freshConv?._continuationMessages?.length &&
|
|
5366
|
+
(!Array.isArray(freshConv.pendingApprovals) || freshConv.pendingApprovals.length === 0)
|
|
5367
|
+
) {
|
|
5071
5368
|
doWaitUntil(
|
|
5072
5369
|
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5073
5370
|
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
@@ -5159,11 +5456,17 @@ export const createRequestHandler = async (options?: {
|
|
|
5159
5456
|
Connection: "keep-alive",
|
|
5160
5457
|
"X-Accel-Buffering": "no",
|
|
5161
5458
|
});
|
|
5162
|
-
const
|
|
5163
|
-
|
|
5164
|
-
:
|
|
5459
|
+
const canonicalHistory = resolveRunRequest(conversation, {
|
|
5460
|
+
conversationId,
|
|
5461
|
+
messages: conversation.messages,
|
|
5462
|
+
});
|
|
5463
|
+
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
5464
|
+
const harnessMessages = [...canonicalHistory.messages];
|
|
5165
5465
|
const historyMessages = [...conversation.messages];
|
|
5166
5466
|
const preRunMessages = [...conversation.messages];
|
|
5467
|
+
console.info(
|
|
5468
|
+
`[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`,
|
|
5469
|
+
);
|
|
5167
5470
|
let latestRunId = conversation.runtimeRunId ?? "";
|
|
5168
5471
|
let assistantResponse = "";
|
|
5169
5472
|
const toolTimeline: string[] = [];
|
|
@@ -5379,6 +5682,26 @@ export const createRequestHandler = async (options?: {
|
|
|
5379
5682
|
const toolText = `- approval required \`${event.tool}\``;
|
|
5380
5683
|
toolTimeline.push(toolText);
|
|
5381
5684
|
currentTools.push(toolText);
|
|
5685
|
+
const existingApprovals = Array.isArray(conversation.pendingApprovals)
|
|
5686
|
+
? conversation.pendingApprovals
|
|
5687
|
+
: [];
|
|
5688
|
+
if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
|
|
5689
|
+
conversation.pendingApprovals = [
|
|
5690
|
+
...existingApprovals,
|
|
5691
|
+
{
|
|
5692
|
+
approvalId: event.approvalId,
|
|
5693
|
+
runId: latestRunId || conversation.runtimeRunId || "",
|
|
5694
|
+
tool: event.tool,
|
|
5695
|
+
toolCallId: undefined,
|
|
5696
|
+
input: (event.input ?? {}) as Record<string, unknown>,
|
|
5697
|
+
checkpointMessages: undefined,
|
|
5698
|
+
baseMessageCount: historyMessages.length,
|
|
5699
|
+
pendingToolCalls: [],
|
|
5700
|
+
},
|
|
5701
|
+
];
|
|
5702
|
+
conversation.updatedAt = Date.now();
|
|
5703
|
+
await conversationStore.update(conversation);
|
|
5704
|
+
}
|
|
5382
5705
|
await persistDraftAssistantTurn();
|
|
5383
5706
|
}
|
|
5384
5707
|
if (event.type === "tool:approval:checkpoint") {
|
|
@@ -5402,16 +5725,13 @@ export const createRequestHandler = async (options?: {
|
|
|
5402
5725
|
}]
|
|
5403
5726
|
: []),
|
|
5404
5727
|
];
|
|
5405
|
-
conversation.pendingApprovals =
|
|
5406
|
-
|
|
5728
|
+
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
5729
|
+
approvals: event.approvals,
|
|
5407
5730
|
runId: latestRunId,
|
|
5408
|
-
tool: a.tool,
|
|
5409
|
-
toolCallId: a.toolCallId,
|
|
5410
|
-
input: a.input,
|
|
5411
5731
|
checkpointMessages: event.checkpointMessages,
|
|
5412
5732
|
baseMessageCount: historyMessages.length,
|
|
5413
5733
|
pendingToolCalls: event.pendingToolCalls,
|
|
5414
|
-
})
|
|
5734
|
+
});
|
|
5415
5735
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5416
5736
|
conversation.updatedAt = Date.now();
|
|
5417
5737
|
await conversationStore.update(conversation);
|
|
@@ -5447,7 +5767,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5447
5767
|
conversation._harnessMessages = runContinuationMessages;
|
|
5448
5768
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5449
5769
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
5450
|
-
|
|
5770
|
+
if (!checkpointedRun) {
|
|
5771
|
+
conversation.pendingApprovals = [];
|
|
5772
|
+
}
|
|
5451
5773
|
if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
|
|
5452
5774
|
if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
|
|
5453
5775
|
conversation.updatedAt = Date.now();
|
|
@@ -5455,11 +5777,13 @@ export const createRequestHandler = async (options?: {
|
|
|
5455
5777
|
|
|
5456
5778
|
// Delayed safety net: if the client doesn't POST to /continue
|
|
5457
5779
|
// within 3 seconds (e.g. browser closed), the server picks it up.
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5780
|
+
if (!checkpointedRun) {
|
|
5781
|
+
doWaitUntil(
|
|
5782
|
+
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5783
|
+
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
5784
|
+
),
|
|
5785
|
+
);
|
|
5786
|
+
}
|
|
5463
5787
|
}
|
|
5464
5788
|
}
|
|
5465
5789
|
await telemetry.emit(event);
|
|
@@ -5467,11 +5791,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5467
5791
|
? { ...event, compactedMessages: undefined }
|
|
5468
5792
|
: event;
|
|
5469
5793
|
if (sseEvent.type === "run:completed") {
|
|
5470
|
-
const
|
|
5471
|
-
r => r.parentConversationId === conversationId,
|
|
5472
|
-
);
|
|
5794
|
+
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
5473
5795
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
5474
|
-
if (
|
|
5796
|
+
if (hasPendingSubagents) {
|
|
5475
5797
|
sseEvent = { ...stripped, pendingSubagents: true };
|
|
5476
5798
|
} else {
|
|
5477
5799
|
sseEvent = stripped;
|
|
@@ -5519,6 +5841,10 @@ export const createRequestHandler = async (options?: {
|
|
|
5519
5841
|
conversation._continuationMessages = undefined;
|
|
5520
5842
|
if (runHarnessMessages) {
|
|
5521
5843
|
conversation._harnessMessages = runHarnessMessages;
|
|
5844
|
+
} else if (shouldRebuildCanonical) {
|
|
5845
|
+
conversation._harnessMessages = conversation.messages;
|
|
5846
|
+
} else {
|
|
5847
|
+
conversation._harnessMessages = conversation.messages;
|
|
5522
5848
|
}
|
|
5523
5849
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5524
5850
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
@@ -5556,7 +5882,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5556
5882
|
conversation.updatedAt = Date.now();
|
|
5557
5883
|
await conversationStore.update(conversation);
|
|
5558
5884
|
}
|
|
5559
|
-
|
|
5885
|
+
if (!checkpointedRun) {
|
|
5886
|
+
await clearPendingApprovalsForConversation(conversationId);
|
|
5887
|
+
}
|
|
5560
5888
|
return;
|
|
5561
5889
|
}
|
|
5562
5890
|
try {
|
|
@@ -5604,20 +5932,32 @@ export const createRequestHandler = async (options?: {
|
|
|
5604
5932
|
if (active && active.abortController === abortController) {
|
|
5605
5933
|
activeConversationRuns.delete(conversationId);
|
|
5606
5934
|
}
|
|
5607
|
-
finishConversationStream(conversationId);
|
|
5608
5935
|
if (latestRunId) {
|
|
5609
5936
|
runOwners.delete(latestRunId);
|
|
5610
5937
|
runConversations.delete(latestRunId);
|
|
5611
5938
|
}
|
|
5939
|
+
|
|
5940
|
+
// Determine if subagent work is pending before deciding to close the
|
|
5941
|
+
// event stream. When a callback is about to run, the stream stays open
|
|
5942
|
+
// so clients that subscribe to /events receive callback-run events in
|
|
5943
|
+
// real-time — the same delivery path used for every other run.
|
|
5944
|
+
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
5945
|
+
const freshConv = await conversationStore.get(conversationId);
|
|
5946
|
+
const needsCallback = hadDeferred || !!freshConv?.pendingSubagentResults?.length;
|
|
5947
|
+
const hasRunningChildren = Array.from(activeSubagentRuns.values()).some(
|
|
5948
|
+
(run) => run.parentConversationId === conversationId,
|
|
5949
|
+
);
|
|
5950
|
+
|
|
5951
|
+
if (!needsCallback && !hasRunningChildren) {
|
|
5952
|
+
finishConversationStream(conversationId);
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5612
5955
|
try {
|
|
5613
5956
|
response.end();
|
|
5614
5957
|
} catch {
|
|
5615
5958
|
// Already closed.
|
|
5616
5959
|
}
|
|
5617
|
-
|
|
5618
|
-
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
5619
|
-
const freshConv = await conversationStore.get(conversationId);
|
|
5620
|
-
if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
|
|
5960
|
+
if (needsCallback) {
|
|
5621
5961
|
processSubagentCallback(conversationId, true).catch(err =>
|
|
5622
5962
|
console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err),
|
|
5623
5963
|
);
|
|
@@ -5682,46 +6022,42 @@ export const createRequestHandler = async (options?: {
|
|
|
5682
6022
|
if (!conv) continue;
|
|
5683
6023
|
|
|
5684
6024
|
const task = `[Scheduled: ${jobName}]\n${cronJob.task}`;
|
|
5685
|
-
const
|
|
5686
|
-
|
|
5687
|
-
:
|
|
6025
|
+
const historySelection = resolveRunRequest(conv, {
|
|
6026
|
+
conversationId: conv.conversationId,
|
|
6027
|
+
messages: conv.messages,
|
|
6028
|
+
});
|
|
6029
|
+
const historyMessages = [...historySelection.messages];
|
|
5688
6030
|
try {
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
steps = event.result.steps;
|
|
5706
|
-
if (!assistantResponse && event.result.response) {
|
|
5707
|
-
assistantResponse = event.result.response;
|
|
5708
|
-
}
|
|
5709
|
-
if (event.result.continuationMessages) {
|
|
5710
|
-
cronHarnessMessages = event.result.continuationMessages;
|
|
5711
|
-
}
|
|
5712
|
-
}
|
|
5713
|
-
await telemetry.emit(event);
|
|
5714
|
-
}
|
|
6031
|
+
const execution = await executeConversationTurn({
|
|
6032
|
+
harness,
|
|
6033
|
+
runInput: {
|
|
6034
|
+
task,
|
|
6035
|
+
conversationId: conv.conversationId,
|
|
6036
|
+
parameters: withToolResultArchiveParam(
|
|
6037
|
+
{ __activeConversationId: conv.conversationId },
|
|
6038
|
+
conv,
|
|
6039
|
+
),
|
|
6040
|
+
messages: historyMessages,
|
|
6041
|
+
},
|
|
6042
|
+
onEvent: async (event) => {
|
|
6043
|
+
await telemetry.emit(event);
|
|
6044
|
+
},
|
|
6045
|
+
});
|
|
6046
|
+
const assistantResponse = execution.draft.assistantResponse;
|
|
5715
6047
|
|
|
5716
6048
|
conv.messages = [
|
|
5717
6049
|
...historyMessages,
|
|
5718
6050
|
{ role: "user" as const, content: task },
|
|
5719
6051
|
...(assistantResponse ? [{ role: "assistant" as const, content: assistantResponse }] : []),
|
|
5720
6052
|
];
|
|
5721
|
-
if (
|
|
5722
|
-
conv._harnessMessages =
|
|
6053
|
+
if (execution.runHarnessMessages) {
|
|
6054
|
+
conv._harnessMessages = execution.runHarnessMessages;
|
|
6055
|
+
} else if (historySelection.shouldRebuildCanonical) {
|
|
6056
|
+
conv._harnessMessages = conv.messages;
|
|
5723
6057
|
}
|
|
5724
6058
|
conv._toolResultArchive = harness.getToolResultArchive(conv.conversationId);
|
|
6059
|
+
if (execution.runContextTokens > 0) conv.contextTokens = execution.runContextTokens;
|
|
6060
|
+
if (execution.runContextWindow > 0) conv.contextWindow = execution.runContextWindow;
|
|
5725
6061
|
conv.updatedAt = Date.now();
|
|
5726
6062
|
await conversationStore.update(conv);
|
|
5727
6063
|
|
|
@@ -5738,7 +6074,7 @@ export const createRequestHandler = async (options?: {
|
|
|
5738
6074
|
console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
|
|
5739
6075
|
}
|
|
5740
6076
|
}
|
|
5741
|
-
chatResults.push({ chatId, status: "completed", steps });
|
|
6077
|
+
chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
|
|
5742
6078
|
} catch (runError) {
|
|
5743
6079
|
chatResults.push({ chatId, status: "error" });
|
|
5744
6080
|
console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
|
|
@@ -6038,91 +6374,32 @@ export const startDevServer = async (
|
|
|
6038
6374
|
conversationId: string,
|
|
6039
6375
|
historyMessages: Message[],
|
|
6040
6376
|
toolResultArchive?: Conversation["_toolResultArchive"],
|
|
6041
|
-
onEvent?: (event: AgentEvent) => void
|
|
6377
|
+
onEvent?: (event: AgentEvent) => void | Promise<void>,
|
|
6042
6378
|
): Promise<CronRunResult> => {
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
task,
|
|
6054
|
-
conversationId,
|
|
6055
|
-
parameters: {
|
|
6056
|
-
__activeConversationId: conversationId,
|
|
6057
|
-
[TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {},
|
|
6379
|
+
const execution = await executeConversationTurn({
|
|
6380
|
+
harness: harnessRef,
|
|
6381
|
+
runInput: {
|
|
6382
|
+
task,
|
|
6383
|
+
conversationId,
|
|
6384
|
+
parameters: {
|
|
6385
|
+
__activeConversationId: conversationId,
|
|
6386
|
+
[TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {},
|
|
6387
|
+
},
|
|
6388
|
+
messages: historyMessages,
|
|
6058
6389
|
},
|
|
6059
|
-
|
|
6060
|
-
})
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
sections.push({ type: "tools", content: currentTools });
|
|
6065
|
-
currentTools = [];
|
|
6066
|
-
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
6067
|
-
assistantResponse += " ";
|
|
6068
|
-
}
|
|
6069
|
-
}
|
|
6070
|
-
assistantResponse += event.content;
|
|
6071
|
-
currentText += event.content;
|
|
6072
|
-
}
|
|
6073
|
-
if (event.type === "tool:started") {
|
|
6074
|
-
if (currentText.length > 0) {
|
|
6075
|
-
sections.push({ type: "text", content: currentText });
|
|
6076
|
-
currentText = "";
|
|
6077
|
-
}
|
|
6078
|
-
const toolText = `- start \`${event.tool}\``;
|
|
6079
|
-
toolTimeline.push(toolText);
|
|
6080
|
-
currentTools.push(toolText);
|
|
6081
|
-
}
|
|
6082
|
-
if (event.type === "tool:completed") {
|
|
6083
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
6084
|
-
toolTimeline.push(toolText);
|
|
6085
|
-
currentTools.push(toolText);
|
|
6086
|
-
}
|
|
6087
|
-
if (event.type === "tool:error") {
|
|
6088
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
6089
|
-
toolTimeline.push(toolText);
|
|
6090
|
-
currentTools.push(toolText);
|
|
6091
|
-
}
|
|
6092
|
-
if (event.type === "run:completed") {
|
|
6093
|
-
steps = event.result.steps;
|
|
6094
|
-
contextTokens = event.result.contextTokens ?? 0;
|
|
6095
|
-
contextWindow = event.result.contextWindow ?? 0;
|
|
6096
|
-
if (event.result.continuationMessages) {
|
|
6097
|
-
harnessMessages = event.result.continuationMessages;
|
|
6098
|
-
}
|
|
6099
|
-
if (!assistantResponse && event.result.response) {
|
|
6100
|
-
assistantResponse = event.result.response;
|
|
6101
|
-
}
|
|
6102
|
-
}
|
|
6103
|
-
}
|
|
6104
|
-
if (currentTools.length > 0) {
|
|
6105
|
-
sections.push({ type: "tools", content: currentTools });
|
|
6106
|
-
}
|
|
6107
|
-
if (currentText.length > 0) {
|
|
6108
|
-
sections.push({ type: "text", content: currentText });
|
|
6109
|
-
}
|
|
6110
|
-
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
6111
|
-
const assistantMetadata =
|
|
6112
|
-
toolTimeline.length > 0 || sections.length > 0
|
|
6113
|
-
? ({
|
|
6114
|
-
toolActivity: [...toolTimeline],
|
|
6115
|
-
sections: sections.length > 0 ? sections : undefined,
|
|
6116
|
-
} as Message["metadata"])
|
|
6117
|
-
: undefined;
|
|
6390
|
+
onEvent,
|
|
6391
|
+
});
|
|
6392
|
+
flushTurnDraft(execution.draft);
|
|
6393
|
+
const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
|
|
6394
|
+
const assistantMetadata = buildAssistantMetadata(execution.draft);
|
|
6118
6395
|
return {
|
|
6119
|
-
response: assistantResponse,
|
|
6120
|
-
steps,
|
|
6396
|
+
response: execution.draft.assistantResponse,
|
|
6397
|
+
steps: execution.runSteps,
|
|
6121
6398
|
assistantMetadata,
|
|
6122
6399
|
hasContent,
|
|
6123
|
-
contextTokens,
|
|
6124
|
-
contextWindow,
|
|
6125
|
-
harnessMessages,
|
|
6400
|
+
contextTokens: execution.runContextTokens,
|
|
6401
|
+
contextWindow: execution.runContextWindow,
|
|
6402
|
+
harnessMessages: execution.runHarnessMessages,
|
|
6126
6403
|
toolResultArchive: harnessRef.getToolResultArchive(conversationId),
|
|
6127
6404
|
};
|
|
6128
6405
|
};
|
|
@@ -6194,9 +6471,11 @@ export const startDevServer = async (
|
|
|
6194
6471
|
if (!conversation) continue;
|
|
6195
6472
|
|
|
6196
6473
|
const task = `[Scheduled: ${jobName}]\n${config.task}`;
|
|
6197
|
-
const
|
|
6198
|
-
|
|
6199
|
-
:
|
|
6474
|
+
const historySelection = resolveRunRequest(conversation, {
|
|
6475
|
+
conversationId: conversation.conversationId,
|
|
6476
|
+
messages: conversation.messages,
|
|
6477
|
+
});
|
|
6478
|
+
const historyMessages = [...historySelection.messages];
|
|
6200
6479
|
const convId = conversation.conversationId;
|
|
6201
6480
|
|
|
6202
6481
|
activeRuns?.set(convId, {
|
|
@@ -6217,6 +6496,8 @@ export const startDevServer = async (
|
|
|
6217
6496
|
freshConv.messages = buildCronMessages(task, historyMessages, result);
|
|
6218
6497
|
if (result.harnessMessages) {
|
|
6219
6498
|
freshConv._harnessMessages = result.harnessMessages;
|
|
6499
|
+
} else if (historySelection.shouldRebuildCanonical) {
|
|
6500
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
6220
6501
|
}
|
|
6221
6502
|
if (result.toolResultArchive) {
|
|
6222
6503
|
freshConv._toolResultArchive = result.toolResultArchive;
|