@poncho-ai/cli 0.32.3 → 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 +14 -0
- package/dist/{chunk-TQRPRFR3.js → chunk-LVWNWMNE.js} +729 -455
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +81 -3
- package/dist/index.js +3 -1
- package/dist/{run-interactive-ink-GD3IRICQ.js → run-interactive-ink-VGKSZJDO.js} +1 -1
- package/package.json +1 -1
- package/src/index.ts +768 -440
- 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 {
|
|
@@ -292,6 +303,325 @@ const parseParams = (values: string[]): Record<string, string> => {
|
|
|
292
303
|
return params;
|
|
293
304
|
};
|
|
294
305
|
|
|
306
|
+
const normalizeMessageForClient = (message: Message): Message => {
|
|
307
|
+
if (message.role !== "assistant" || typeof message.content !== "string") {
|
|
308
|
+
return message;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const parsed = JSON.parse(message.content) as Record<string, unknown>;
|
|
312
|
+
const text = typeof parsed.text === "string" ? parsed.text : undefined;
|
|
313
|
+
const toolCalls = Array.isArray(parsed.tool_calls) ? parsed.tool_calls : undefined;
|
|
314
|
+
if (typeof text === "string" && toolCalls) {
|
|
315
|
+
return {
|
|
316
|
+
...message,
|
|
317
|
+
content: text,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// Keep original assistant content when it's plain text or non-JSON.
|
|
322
|
+
}
|
|
323
|
+
return message;
|
|
324
|
+
};
|
|
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
|
+
|
|
295
625
|
const AGENT_TEMPLATE = (
|
|
296
626
|
name: string,
|
|
297
627
|
id: string,
|
|
@@ -1621,6 +1951,77 @@ export const createRequestHandler = async (options?: {
|
|
|
1621
1951
|
// separate copy, causing last-writer-wins when decisions overlap).
|
|
1622
1952
|
const approvalDecisionTracker = new Map<string, Map<string, boolean>>();
|
|
1623
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
|
+
|
|
1624
2025
|
const getSubagentDepth = async (conversationId: string): Promise<number> => {
|
|
1625
2026
|
let depth = 0;
|
|
1626
2027
|
let current = await conversationStore.get(conversationId);
|
|
@@ -1672,7 +2073,9 @@ export const createRequestHandler = async (options?: {
|
|
|
1672
2073
|
const conv = await conversationStore.get(subagentId);
|
|
1673
2074
|
if (!conv || !conv.parentConversationId) return;
|
|
1674
2075
|
|
|
1675
|
-
const allApprovals = conv.pendingApprovals ?? []
|
|
2076
|
+
const allApprovals = (conv.pendingApprovals ?? []).map((approval) =>
|
|
2077
|
+
normalizeApprovalCheckpoint(approval, conv.messages),
|
|
2078
|
+
);
|
|
1676
2079
|
if (allApprovals.length === 0) return;
|
|
1677
2080
|
const allDecided = allApprovals.every(a => a.decision != null);
|
|
1678
2081
|
if (!allDecided) return;
|
|
@@ -1764,9 +2167,11 @@ export const createRequestHandler = async (options?: {
|
|
|
1764
2167
|
conversation.lastActivityAt = Date.now();
|
|
1765
2168
|
await conversationStore.update(conversation);
|
|
1766
2169
|
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
:
|
|
2170
|
+
const runOutcome = resolveRunRequest(conversation, {
|
|
2171
|
+
conversationId: childConversationId,
|
|
2172
|
+
messages: conversation.messages,
|
|
2173
|
+
});
|
|
2174
|
+
const harnessMessages = [...runOutcome.messages];
|
|
1770
2175
|
|
|
1771
2176
|
for await (const event of childHarness.runWithTelemetry({
|
|
1772
2177
|
task,
|
|
@@ -1829,16 +2234,13 @@ export const createRequestHandler = async (options?: {
|
|
|
1829
2234
|
if (event.type === "tool:approval:checkpoint") {
|
|
1830
2235
|
const cpConv = await conversationStore.get(childConversationId);
|
|
1831
2236
|
if (cpConv) {
|
|
1832
|
-
const allCpData
|
|
1833
|
-
|
|
2237
|
+
const allCpData = buildApprovalCheckpoints({
|
|
2238
|
+
approvals: event.approvals,
|
|
1834
2239
|
runId: latestRunId,
|
|
1835
|
-
tool: a.tool,
|
|
1836
|
-
toolCallId: a.toolCallId,
|
|
1837
|
-
input: a.input,
|
|
1838
2240
|
checkpointMessages: [...harnessMessages, ...event.checkpointMessages],
|
|
1839
2241
|
baseMessageCount: 0,
|
|
1840
2242
|
pendingToolCalls: event.pendingToolCalls,
|
|
1841
|
-
})
|
|
2243
|
+
});
|
|
1842
2244
|
cpConv.pendingApprovals = allCpData;
|
|
1843
2245
|
cpConv.updatedAt = Date.now();
|
|
1844
2246
|
await conversationStore.update(cpConv);
|
|
@@ -1855,7 +2257,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1855
2257
|
}
|
|
1856
2258
|
});
|
|
1857
2259
|
|
|
1858
|
-
const checkpointRef = allCpData[0]
|
|
2260
|
+
const checkpointRef = normalizeApprovalCheckpoint(allCpData[0]!, [...harnessMessages]);
|
|
1859
2261
|
const toolContext = {
|
|
1860
2262
|
runId: checkpointRef.runId,
|
|
1861
2263
|
agentId: identity.id,
|
|
@@ -1988,6 +2390,8 @@ export const createRequestHandler = async (options?: {
|
|
|
1988
2390
|
}
|
|
1989
2391
|
if (runResult?.continuationMessages) {
|
|
1990
2392
|
conv._harnessMessages = runResult.continuationMessages;
|
|
2393
|
+
} else if (runOutcome.shouldRebuildCanonical) {
|
|
2394
|
+
conv._harnessMessages = conv.messages;
|
|
1991
2395
|
}
|
|
1992
2396
|
conv._toolResultArchive = childHarness.getToolResultArchive(childConversationId);
|
|
1993
2397
|
conv.lastActivityAt = Date.now();
|
|
@@ -2156,6 +2560,9 @@ export const createRequestHandler = async (options?: {
|
|
|
2156
2560
|
metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp } as Message["metadata"],
|
|
2157
2561
|
});
|
|
2158
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];
|
|
2159
2566
|
conversation.updatedAt = Date.now();
|
|
2160
2567
|
await conversationStore.update(conversation);
|
|
2161
2568
|
|
|
@@ -2190,120 +2597,74 @@ export const createRequestHandler = async (options?: {
|
|
|
2190
2597
|
});
|
|
2191
2598
|
}
|
|
2192
2599
|
|
|
2193
|
-
const
|
|
2194
|
-
|
|
2195
|
-
: conversation.
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
let
|
|
2203
|
-
let runContextTokens = conversation.contextTokens ?? 0;
|
|
2204
|
-
let runContextWindow = conversation.contextWindow ?? 0;
|
|
2205
|
-
const toolTimeline: string[] = [];
|
|
2206
|
-
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
2207
|
-
let currentTools: string[] = [];
|
|
2208
|
-
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;
|
|
2209
2610
|
|
|
2210
2611
|
try {
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
currentTools = [];
|
|
2230
|
-
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
2231
|
-
assistantResponse += " ";
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
assistantResponse += event.content;
|
|
2235
|
-
currentText += event.content;
|
|
2236
|
-
}
|
|
2237
|
-
if (event.type === "tool:started") {
|
|
2238
|
-
if (currentText.length > 0) {
|
|
2239
|
-
sections.push({ type: "text", content: currentText });
|
|
2240
|
-
currentText = "";
|
|
2241
|
-
}
|
|
2242
|
-
const toolText = `- start \`${event.tool}\``;
|
|
2243
|
-
toolTimeline.push(toolText);
|
|
2244
|
-
currentTools.push(toolText);
|
|
2245
|
-
}
|
|
2246
|
-
if (event.type === "tool:completed") {
|
|
2247
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
2248
|
-
toolTimeline.push(toolText);
|
|
2249
|
-
currentTools.push(toolText);
|
|
2250
|
-
}
|
|
2251
|
-
if (event.type === "tool:error") {
|
|
2252
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
2253
|
-
toolTimeline.push(toolText);
|
|
2254
|
-
currentTools.push(toolText);
|
|
2255
|
-
}
|
|
2256
|
-
if (event.type === "run:completed") {
|
|
2257
|
-
if (assistantResponse.length === 0 && event.result.response) {
|
|
2258
|
-
assistantResponse = event.result.response;
|
|
2259
|
-
}
|
|
2260
|
-
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
2261
|
-
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
2262
|
-
if (event.result.continuationMessages) {
|
|
2263
|
-
runHarnessMessages = event.result.continuationMessages;
|
|
2264
|
-
}
|
|
2265
|
-
if (event.result.continuation) {
|
|
2266
|
-
runContinuation = true;
|
|
2267
|
-
if (event.result.continuationMessages) {
|
|
2268
|
-
runContinuationMessages = event.result.continuationMessages;
|
|
2269
|
-
}
|
|
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;
|
|
2270
2630
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
|
|
2276
|
-
if (currentText.length > 0) sections.push({ type: "text", content: currentText });
|
|
2631
|
+
broadcastEvent(conversationId, event);
|
|
2632
|
+
},
|
|
2633
|
+
});
|
|
2634
|
+
flushTurnDraft(execution.draft);
|
|
2277
2635
|
|
|
2278
|
-
|
|
2636
|
+
const callbackNeedsContinuation = execution.runContinuation && execution.runContinuationMessages;
|
|
2637
|
+
if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
|
|
2279
2638
|
const freshConv = await conversationStore.get(conversationId);
|
|
2280
2639
|
if (freshConv) {
|
|
2281
|
-
if (
|
|
2282
|
-
freshConv._continuationMessages = runContinuationMessages;
|
|
2640
|
+
if (callbackNeedsContinuation) {
|
|
2641
|
+
freshConv._continuationMessages = execution.runContinuationMessages;
|
|
2283
2642
|
} else {
|
|
2284
2643
|
freshConv._continuationMessages = undefined;
|
|
2285
2644
|
freshConv.messages.push({
|
|
2286
2645
|
role: "assistant",
|
|
2287
|
-
content: assistantResponse,
|
|
2288
|
-
metadata:
|
|
2289
|
-
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
2290
|
-
: undefined,
|
|
2646
|
+
content: execution.draft.assistantResponse,
|
|
2647
|
+
metadata: buildAssistantMetadata(execution.draft),
|
|
2291
2648
|
});
|
|
2292
2649
|
}
|
|
2293
|
-
if (runHarnessMessages) {
|
|
2294
|
-
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;
|
|
2295
2656
|
}
|
|
2296
2657
|
freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
2297
|
-
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
2658
|
+
freshConv.runtimeRunId = execution.latestRunId || freshConv.runtimeRunId;
|
|
2298
2659
|
freshConv.runningCallbackSince = undefined;
|
|
2299
2660
|
freshConv.runStatus = "idle";
|
|
2300
|
-
if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
|
|
2301
|
-
if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
|
|
2661
|
+
if (execution.runContextTokens > 0) freshConv.contextTokens = execution.runContextTokens;
|
|
2662
|
+
if (execution.runContextWindow > 0) freshConv.contextWindow = execution.runContextWindow;
|
|
2302
2663
|
freshConv.updatedAt = Date.now();
|
|
2303
2664
|
await conversationStore.update(freshConv);
|
|
2304
2665
|
|
|
2305
2666
|
// Proactive messaging notification if conversation has a messaging channel
|
|
2306
|
-
if (freshConv.channelMeta && assistantResponse.length > 0) {
|
|
2667
|
+
if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
|
|
2307
2668
|
const adapter = messagingAdapters.get(freshConv.channelMeta.platform);
|
|
2308
2669
|
if (adapter) {
|
|
2309
2670
|
try {
|
|
@@ -2312,7 +2673,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2312
2673
|
channelId: freshConv.channelMeta.channelId,
|
|
2313
2674
|
platformThreadId: freshConv.channelMeta.platformThreadId,
|
|
2314
2675
|
},
|
|
2315
|
-
assistantResponse,
|
|
2676
|
+
execution.draft.assistantResponse,
|
|
2316
2677
|
);
|
|
2317
2678
|
} catch (sendErr) {
|
|
2318
2679
|
console.error(`[poncho][subagent-callback] Messaging notify failed:`, sendErr instanceof Error ? sendErr.message : sendErr);
|
|
@@ -2323,7 +2684,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2323
2684
|
}
|
|
2324
2685
|
|
|
2325
2686
|
// Handle continuation for the callback run itself
|
|
2326
|
-
if (runContinuation) {
|
|
2687
|
+
if (execution.runContinuation) {
|
|
2327
2688
|
if (isServerless) {
|
|
2328
2689
|
const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(err =>
|
|
2329
2690
|
console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
@@ -2345,7 +2706,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2345
2706
|
}
|
|
2346
2707
|
} finally {
|
|
2347
2708
|
activeConversationRuns.delete(conversationId);
|
|
2348
|
-
finishConversationStream(conversationId);
|
|
2349
2709
|
|
|
2350
2710
|
// Check both the in-memory flag (always reliable) and the store.
|
|
2351
2711
|
// We drain the flag first so a concurrent triggerParentCallback that
|
|
@@ -2354,6 +2714,16 @@ export const createRequestHandler = async (options?: {
|
|
|
2354
2714
|
const hadDeferredTrigger = pendingCallbackNeeded.delete(conversationId);
|
|
2355
2715
|
const freshConv = await conversationStore.get(conversationId);
|
|
2356
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
|
+
}
|
|
2357
2727
|
|
|
2358
2728
|
if (hadDeferredTrigger || hasPendingInStore) {
|
|
2359
2729
|
// Re-trigger immediately. Skip the runningCallbackSince lock check
|
|
@@ -2531,10 +2901,11 @@ export const createRequestHandler = async (options?: {
|
|
|
2531
2901
|
let runContextWindow = conversation.contextWindow ?? 0;
|
|
2532
2902
|
let resumeHarnessMessages: Message[] | undefined;
|
|
2533
2903
|
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2904
|
+
const normalizedCheckpoint = normalizeApprovalCheckpoint(checkpoint, conversation.messages);
|
|
2905
|
+
const baseMessages = normalizedCheckpoint.baseMessageCount != null
|
|
2906
|
+
? conversation.messages.slice(0, normalizedCheckpoint.baseMessageCount)
|
|
2536
2907
|
: [];
|
|
2537
|
-
const fullCheckpointMessages = [...baseMessages, ...
|
|
2908
|
+
const fullCheckpointMessages = [...baseMessages, ...normalizedCheckpoint.checkpointMessages!];
|
|
2538
2909
|
|
|
2539
2910
|
// Build the tool result message that continueFromToolResult will also
|
|
2540
2911
|
// construct internally. We need it here so that if the resumed run hits
|
|
@@ -2625,25 +2996,27 @@ export const createRequestHandler = async (options?: {
|
|
|
2625
2996
|
if (event.type === "tool:approval:checkpoint") {
|
|
2626
2997
|
const conv = await conversationStore.get(conversationId);
|
|
2627
2998
|
if (conv) {
|
|
2628
|
-
conv.pendingApprovals =
|
|
2629
|
-
|
|
2999
|
+
conv.pendingApprovals = buildApprovalCheckpoints({
|
|
3000
|
+
approvals: event.approvals,
|
|
2630
3001
|
runId: latestRunId,
|
|
2631
|
-
tool: a.tool,
|
|
2632
|
-
toolCallId: a.toolCallId,
|
|
2633
|
-
input: a.input,
|
|
2634
3002
|
checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
|
|
2635
3003
|
baseMessageCount: 0,
|
|
2636
3004
|
pendingToolCalls: event.pendingToolCalls,
|
|
2637
|
-
})
|
|
3005
|
+
});
|
|
2638
3006
|
conv.updatedAt = Date.now();
|
|
2639
3007
|
await conversationStore.update(conv);
|
|
2640
3008
|
|
|
2641
3009
|
if (conv.channelMeta?.platform === "telegram") {
|
|
2642
3010
|
const tgAdapter = messagingAdapters.get("telegram") as TelegramAdapter | undefined;
|
|
2643
3011
|
if (tgAdapter) {
|
|
3012
|
+
const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
|
|
3013
|
+
conv.channelMeta.platformThreadId,
|
|
3014
|
+
conv.channelMeta.channelId,
|
|
3015
|
+
);
|
|
2644
3016
|
void tgAdapter.sendApprovalRequest(
|
|
2645
3017
|
conv.channelMeta.channelId,
|
|
2646
3018
|
event.approvals.map(a => ({ approvalId: a.approvalId, tool: a.tool, input: a.input })),
|
|
3019
|
+
{ message_thread_id: messageThreadId },
|
|
2647
3020
|
).catch(() => {});
|
|
2648
3021
|
}
|
|
2649
3022
|
}
|
|
@@ -2725,6 +3098,8 @@ export const createRequestHandler = async (options?: {
|
|
|
2725
3098
|
}
|
|
2726
3099
|
if (resumeHarnessMessages) {
|
|
2727
3100
|
conv._harnessMessages = resumeHarnessMessages;
|
|
3101
|
+
} else {
|
|
3102
|
+
conv._harnessMessages = conv.messages;
|
|
2728
3103
|
}
|
|
2729
3104
|
conv.runtimeRunId = latestRunId || conv.runtimeRunId;
|
|
2730
3105
|
conv.pendingApprovals = [];
|
|
@@ -2743,7 +3118,6 @@ export const createRequestHandler = async (options?: {
|
|
|
2743
3118
|
}
|
|
2744
3119
|
}
|
|
2745
3120
|
|
|
2746
|
-
finishConversationStream(conversationId);
|
|
2747
3121
|
activeConversationRuns.delete(conversationId);
|
|
2748
3122
|
if (latestRunId) {
|
|
2749
3123
|
runOwners.delete(latestRunId);
|
|
@@ -2754,7 +3128,14 @@ export const createRequestHandler = async (options?: {
|
|
|
2754
3128
|
// Check for pending subagent results that arrived during the run
|
|
2755
3129
|
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
2756
3130
|
const postConv = await conversationStore.get(conversationId);
|
|
2757
|
-
|
|
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) {
|
|
2758
3139
|
processSubagentCallback(conversationId, true).catch(err =>
|
|
2759
3140
|
console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err),
|
|
2760
3141
|
);
|
|
@@ -2787,7 +3168,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2787
3168
|
};
|
|
2788
3169
|
await conversationStore.update(existing);
|
|
2789
3170
|
}
|
|
2790
|
-
return { messages: existing.
|
|
3171
|
+
return { messages: loadCanonicalHistory(existing).messages };
|
|
2791
3172
|
}
|
|
2792
3173
|
const now = Date.now();
|
|
2793
3174
|
const channelMeta = meta.channelId
|
|
@@ -2811,11 +3192,21 @@ export const createRequestHandler = async (options?: {
|
|
|
2811
3192
|
return { messages: [] };
|
|
2812
3193
|
},
|
|
2813
3194
|
async run(conversationId, input) {
|
|
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
|
+
|
|
2814
3201
|
const isContinuation = input.task == null;
|
|
2815
|
-
console.log(
|
|
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
|
+
);
|
|
2816
3207
|
|
|
2817
|
-
const historyMessages = [...
|
|
2818
|
-
const preRunMessages = [...
|
|
3208
|
+
const historyMessages = [...canonicalHistory.messages];
|
|
3209
|
+
const preRunMessages = [...canonicalHistory.messages];
|
|
2819
3210
|
const userContent = input.task;
|
|
2820
3211
|
|
|
2821
3212
|
// Read-modify-write helper: always fetches the latest version from
|
|
@@ -2838,11 +3229,7 @@ export const createRequestHandler = async (options?: {
|
|
|
2838
3229
|
});
|
|
2839
3230
|
|
|
2840
3231
|
let latestRunId = "";
|
|
2841
|
-
|
|
2842
|
-
const toolTimeline: string[] = [];
|
|
2843
|
-
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
2844
|
-
let currentTools: string[] = [];
|
|
2845
|
-
let currentText = "";
|
|
3232
|
+
const draft = createTurnDraftState();
|
|
2846
3233
|
let checkpointedRun = false;
|
|
2847
3234
|
let runContextTokens = 0;
|
|
2848
3235
|
let runContextWindow = 0;
|
|
@@ -2852,23 +3239,18 @@ export const createRequestHandler = async (options?: {
|
|
|
2852
3239
|
let runMaxSteps: number | undefined;
|
|
2853
3240
|
|
|
2854
3241
|
const buildMessages = (): Message[] => {
|
|
2855
|
-
const draftSections
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
content: Array.isArray(s.content) ? [...s.content] : s.content,
|
|
2859
|
-
})),
|
|
2860
|
-
];
|
|
2861
|
-
if (currentTools.length > 0) {
|
|
2862
|
-
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] });
|
|
2863
3245
|
}
|
|
2864
|
-
if (currentText.length > 0) {
|
|
2865
|
-
draftSections.push({ type: "text", content: currentText });
|
|
3246
|
+
if (draft.currentText.length > 0) {
|
|
3247
|
+
draftSections.push({ type: "text", content: draft.currentText });
|
|
2866
3248
|
}
|
|
2867
3249
|
const userTurn: Message[] = userContent != null
|
|
2868
3250
|
? [{ role: "user" as const, content: userContent }]
|
|
2869
3251
|
: [];
|
|
2870
3252
|
const hasDraftContent =
|
|
2871
|
-
assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
|
|
3253
|
+
draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
|
|
2872
3254
|
if (!hasDraftContent) {
|
|
2873
3255
|
return [...historyMessages, ...userTurn];
|
|
2874
3256
|
}
|
|
@@ -2877,20 +3259,14 @@ export const createRequestHandler = async (options?: {
|
|
|
2877
3259
|
...userTurn,
|
|
2878
3260
|
{
|
|
2879
3261
|
role: "assistant" as const,
|
|
2880
|
-
content: assistantResponse,
|
|
2881
|
-
metadata:
|
|
2882
|
-
toolTimeline.length > 0 || draftSections.length > 0
|
|
2883
|
-
? ({
|
|
2884
|
-
toolActivity: [...toolTimeline],
|
|
2885
|
-
sections: draftSections.length > 0 ? draftSections : undefined,
|
|
2886
|
-
} as Message["metadata"])
|
|
2887
|
-
: undefined,
|
|
3262
|
+
content: draft.assistantResponse,
|
|
3263
|
+
metadata: buildAssistantMetadata(draft, draftSections),
|
|
2888
3264
|
},
|
|
2889
3265
|
];
|
|
2890
3266
|
};
|
|
2891
3267
|
|
|
2892
3268
|
const persistDraftAssistantTurn = async (): Promise<void> => {
|
|
2893
|
-
if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
|
|
3269
|
+
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
2894
3270
|
await updateConversation((c) => {
|
|
2895
3271
|
c.messages = buildMessages();
|
|
2896
3272
|
});
|
|
@@ -2899,75 +3275,53 @@ export const createRequestHandler = async (options?: {
|
|
|
2899
3275
|
const runInput = {
|
|
2900
3276
|
task: input.task,
|
|
2901
3277
|
conversationId,
|
|
2902
|
-
messages:
|
|
3278
|
+
messages: historyMessages,
|
|
2903
3279
|
files: input.files,
|
|
2904
|
-
parameters:
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
3280
|
+
parameters: {
|
|
3281
|
+
...(input.metadata ? {
|
|
3282
|
+
__messaging_platform: input.metadata.platform,
|
|
3283
|
+
__messaging_sender_id: input.metadata.sender.id,
|
|
3284
|
+
__messaging_sender_name: input.metadata.sender.name ?? "",
|
|
3285
|
+
__messaging_thread_id: input.metadata.threadId,
|
|
3286
|
+
} : {}),
|
|
3287
|
+
__activeConversationId: conversationId,
|
|
3288
|
+
},
|
|
2910
3289
|
};
|
|
2911
3290
|
|
|
2912
3291
|
try {
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
}
|
|
2927
|
-
assistantResponse += event.content;
|
|
2928
|
-
currentText += event.content;
|
|
2929
|
-
}
|
|
2930
|
-
if (event.type === "tool:started") {
|
|
2931
|
-
if (currentText.length > 0) {
|
|
2932
|
-
sections.push({ type: "text", content: currentText });
|
|
2933
|
-
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);
|
|
2934
3305
|
}
|
|
2935
|
-
const toolText = `- start \`${event.tool}\``;
|
|
2936
|
-
toolTimeline.push(toolText);
|
|
2937
|
-
currentTools.push(toolText);
|
|
2938
|
-
}
|
|
2939
|
-
if (event.type === "tool:completed") {
|
|
2940
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
2941
|
-
toolTimeline.push(toolText);
|
|
2942
|
-
currentTools.push(toolText);
|
|
2943
|
-
}
|
|
2944
|
-
if (event.type === "tool:error") {
|
|
2945
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
2946
|
-
toolTimeline.push(toolText);
|
|
2947
|
-
currentTools.push(toolText);
|
|
2948
|
-
}
|
|
2949
3306
|
if (event.type === "step:completed") {
|
|
2950
3307
|
await persistDraftAssistantTurn();
|
|
2951
3308
|
}
|
|
2952
3309
|
if (event.type === "tool:approval:required") {
|
|
2953
3310
|
const toolText = `- approval required \`${event.tool}\``;
|
|
2954
|
-
toolTimeline.push(toolText);
|
|
2955
|
-
currentTools.push(toolText);
|
|
3311
|
+
draft.toolTimeline.push(toolText);
|
|
3312
|
+
draft.currentTools.push(toolText);
|
|
2956
3313
|
await persistDraftAssistantTurn();
|
|
2957
3314
|
}
|
|
2958
3315
|
if (event.type === "tool:approval:checkpoint") {
|
|
2959
3316
|
await updateConversation((c) => {
|
|
2960
3317
|
c.messages = buildMessages();
|
|
2961
|
-
c.pendingApprovals =
|
|
2962
|
-
|
|
3318
|
+
c.pendingApprovals = buildApprovalCheckpoints({
|
|
3319
|
+
approvals: event.approvals,
|
|
2963
3320
|
runId: latestRunId,
|
|
2964
|
-
tool: a.tool,
|
|
2965
|
-
toolCallId: a.toolCallId,
|
|
2966
|
-
input: a.input,
|
|
2967
3321
|
checkpointMessages: event.checkpointMessages,
|
|
2968
3322
|
baseMessageCount: historyMessages.length,
|
|
2969
3323
|
pendingToolCalls: event.pendingToolCalls,
|
|
2970
|
-
})
|
|
3324
|
+
});
|
|
2971
3325
|
});
|
|
2972
3326
|
checkpointedRun = true;
|
|
2973
3327
|
|
|
@@ -2981,9 +3335,14 @@ export const createRequestHandler = async (options?: {
|
|
|
2981
3335
|
tool: a.tool,
|
|
2982
3336
|
input: a.input,
|
|
2983
3337
|
}));
|
|
3338
|
+
const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
|
|
3339
|
+
conv.channelMeta.platformThreadId,
|
|
3340
|
+
conv.channelMeta.channelId,
|
|
3341
|
+
);
|
|
2984
3342
|
void tgAdapter.sendApprovalRequest(
|
|
2985
3343
|
conv.channelMeta.channelId,
|
|
2986
3344
|
approvals,
|
|
3345
|
+
{ message_thread_id: messageThreadId },
|
|
2987
3346
|
).catch((err: unknown) => {
|
|
2988
3347
|
console.error("[messaging-runner] failed to send Telegram approval request:", err instanceof Error ? err.message : err);
|
|
2989
3348
|
});
|
|
@@ -3006,38 +3365,22 @@ export const createRequestHandler = async (options?: {
|
|
|
3006
3365
|
});
|
|
3007
3366
|
}
|
|
3008
3367
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
3020
|
-
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
3021
|
-
}
|
|
3022
|
-
if (event.type === "run:error") {
|
|
3023
|
-
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
3024
|
-
}
|
|
3025
|
-
broadcastEvent(conversationId, event);
|
|
3026
|
-
}
|
|
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;
|
|
3027
3378
|
} catch (err) {
|
|
3028
3379
|
console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
|
|
3029
|
-
assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
3380
|
+
draft.assistantResponse = draft.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
|
|
3030
3381
|
}
|
|
3031
3382
|
|
|
3032
|
-
|
|
3033
|
-
if (currentTools.length > 0) {
|
|
3034
|
-
sections.push({ type: "tools", content: currentTools });
|
|
3035
|
-
currentTools = [];
|
|
3036
|
-
}
|
|
3037
|
-
if (currentText.length > 0) {
|
|
3038
|
-
sections.push({ type: "text", content: currentText });
|
|
3039
|
-
currentText = "";
|
|
3040
|
-
}
|
|
3383
|
+
flushTurnDraft(draft);
|
|
3041
3384
|
|
|
3042
3385
|
if (!checkpointedRun) {
|
|
3043
3386
|
await updateConversation((c) => {
|
|
@@ -3049,6 +3392,10 @@ export const createRequestHandler = async (options?: {
|
|
|
3049
3392
|
}
|
|
3050
3393
|
if (runContinuationMessages) {
|
|
3051
3394
|
c._harnessMessages = runContinuationMessages;
|
|
3395
|
+
} else if (shouldRebuildCanonical) {
|
|
3396
|
+
c._harnessMessages = c.messages;
|
|
3397
|
+
} else {
|
|
3398
|
+
c._harnessMessages = c.messages;
|
|
3052
3399
|
}
|
|
3053
3400
|
c.runtimeRunId = latestRunId || c.runtimeRunId;
|
|
3054
3401
|
c.pendingApprovals = [];
|
|
@@ -3058,6 +3405,9 @@ export const createRequestHandler = async (options?: {
|
|
|
3058
3405
|
});
|
|
3059
3406
|
} else {
|
|
3060
3407
|
await updateConversation((c) => {
|
|
3408
|
+
if (shouldRebuildCanonical && !c._harnessMessages?.length) {
|
|
3409
|
+
c._harnessMessages = c.messages;
|
|
3410
|
+
}
|
|
3061
3411
|
c.runStatus = "idle";
|
|
3062
3412
|
});
|
|
3063
3413
|
}
|
|
@@ -3067,8 +3417,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3067
3417
|
runConversations.delete(latestRunId);
|
|
3068
3418
|
}
|
|
3069
3419
|
|
|
3070
|
-
console.log("[messaging-runner] run complete, response length:", assistantResponse.length, runContinuation ? "(continuation)" : "");
|
|
3071
|
-
const response = assistantResponse;
|
|
3420
|
+
console.log("[messaging-runner] run complete, response length:", draft.assistantResponse.length, runContinuation ? "(continuation)" : "");
|
|
3421
|
+
const response = draft.assistantResponse;
|
|
3072
3422
|
|
|
3073
3423
|
return {
|
|
3074
3424
|
response,
|
|
@@ -3225,6 +3575,7 @@ export const createRequestHandler = async (options?: {
|
|
|
3225
3575
|
): AsyncGenerator<AgentEvent> {
|
|
3226
3576
|
const conversation = await conversationStore.get(conversationId);
|
|
3227
3577
|
if (!conversation) return;
|
|
3578
|
+
if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
|
|
3228
3579
|
if (!conversation._continuationMessages?.length) return;
|
|
3229
3580
|
if (conversation.runStatus === "running") return;
|
|
3230
3581
|
|
|
@@ -3392,7 +3743,11 @@ export const createRequestHandler = async (options?: {
|
|
|
3392
3743
|
freshConv._continuationCount = undefined;
|
|
3393
3744
|
}
|
|
3394
3745
|
|
|
3395
|
-
if (nextHarnessMessages)
|
|
3746
|
+
if (nextHarnessMessages) {
|
|
3747
|
+
freshConv._harnessMessages = nextHarnessMessages;
|
|
3748
|
+
} else {
|
|
3749
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
3750
|
+
}
|
|
3396
3751
|
freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
3397
3752
|
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
3398
3753
|
freshConv.pendingApprovals = [];
|
|
@@ -3529,6 +3884,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3529
3884
|
}
|
|
3530
3885
|
if (runResult?.continuationMessages) {
|
|
3531
3886
|
conv._harnessMessages = runResult.continuationMessages;
|
|
3887
|
+
} else {
|
|
3888
|
+
conv._harnessMessages = conv.messages;
|
|
3532
3889
|
}
|
|
3533
3890
|
conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
|
|
3534
3891
|
conv.lastActivityAt = Date.now();
|
|
@@ -3728,23 +4085,15 @@ export const createRequestHandler = async (options?: {
|
|
|
3728
4085
|
}
|
|
3729
4086
|
|
|
3730
4087
|
// Regular (non-subagent) approval
|
|
3731
|
-
const
|
|
3732
|
-
let foundConversation
|
|
3733
|
-
let foundApproval
|
|
3734
|
-
for (const conv of conversations) {
|
|
3735
|
-
if (!Array.isArray(conv.pendingApprovals)) continue;
|
|
3736
|
-
const match = conv.pendingApprovals.find(a => a.approvalId === approvalId);
|
|
3737
|
-
if (match) {
|
|
3738
|
-
foundConversation = conv;
|
|
3739
|
-
foundApproval = match;
|
|
3740
|
-
break;
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
4088
|
+
const found = await findPendingApproval(approvalId, "local-owner");
|
|
4089
|
+
let foundConversation = found?.conversation;
|
|
4090
|
+
let foundApproval = found?.approval;
|
|
3743
4091
|
|
|
3744
4092
|
if (!foundConversation || !foundApproval) {
|
|
3745
4093
|
console.warn("[telegram-approval] approval not found:", approvalId);
|
|
3746
4094
|
return;
|
|
3747
4095
|
}
|
|
4096
|
+
foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
|
|
3748
4097
|
|
|
3749
4098
|
await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
|
|
3750
4099
|
|
|
@@ -3756,7 +4105,9 @@ export const createRequestHandler = async (options?: {
|
|
|
3756
4105
|
: { type: "tool:approval:denied", approvalId },
|
|
3757
4106
|
);
|
|
3758
4107
|
|
|
3759
|
-
const allApprovals = foundConversation.pendingApprovals ?? []
|
|
4108
|
+
const allApprovals = (foundConversation.pendingApprovals ?? []).map((approval) =>
|
|
4109
|
+
normalizeApprovalCheckpoint(approval, foundConversation!.messages),
|
|
4110
|
+
);
|
|
3760
4111
|
const allDecided = allApprovals.length > 0 && allApprovals.every(a => a.decision != null);
|
|
3761
4112
|
|
|
3762
4113
|
if (!allDecided) {
|
|
@@ -4174,6 +4525,9 @@ export const createRequestHandler = async (options?: {
|
|
|
4174
4525
|
pathname !== "/api/auth/login" &&
|
|
4175
4526
|
request.headers["x-csrf-token"] !== session?.csrfToken
|
|
4176
4527
|
) {
|
|
4528
|
+
console.warn(
|
|
4529
|
+
`[poncho][csrf] blocked request method=${request.method} path="${pathname}" session=${session.sessionId}`,
|
|
4530
|
+
);
|
|
4177
4531
|
writeJson(response, 403, {
|
|
4178
4532
|
code: "CSRF_ERROR",
|
|
4179
4533
|
message: "Invalid CSRF token",
|
|
@@ -4397,8 +4751,11 @@ export const createRequestHandler = async (options?: {
|
|
|
4397
4751
|
const approvalMatch = pathname.match(/^\/api\/approvals\/([^/]+)$/);
|
|
4398
4752
|
if (approvalMatch && request.method === "POST") {
|
|
4399
4753
|
const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
|
|
4400
|
-
const body = (await readRequestBody(request)) as { approved?: boolean };
|
|
4754
|
+
const body = (await readRequestBody(request)) as { approved?: boolean; conversationId?: string };
|
|
4401
4755
|
const approved = body.approved === true;
|
|
4756
|
+
const hintedConversationId = typeof body.conversationId === "string" && body.conversationId.trim().length > 0
|
|
4757
|
+
? body.conversationId.trim()
|
|
4758
|
+
: undefined;
|
|
4402
4759
|
|
|
4403
4760
|
// Check if this is a pending subagent approval (handled inline by runSubagent)
|
|
4404
4761
|
const pendingSubagent = pendingSubagentApprovals.get(approvalId);
|
|
@@ -4434,18 +4791,23 @@ export const createRequestHandler = async (options?: {
|
|
|
4434
4791
|
}
|
|
4435
4792
|
|
|
4436
4793
|
// Find the approval in the conversation store (checkpoint-based flow)
|
|
4437
|
-
const conversations = await conversationStore.list(ownerId);
|
|
4438
4794
|
let foundConversation: Conversation | undefined;
|
|
4439
4795
|
let foundApproval: NonNullable<Conversation["pendingApprovals"]>[number] | undefined;
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
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
|
+
}
|
|
4447
4804
|
}
|
|
4448
4805
|
}
|
|
4806
|
+
if (!foundConversation || !foundApproval) {
|
|
4807
|
+
const found = await findPendingApproval(approvalId, ownerId);
|
|
4808
|
+
foundConversation = found?.conversation;
|
|
4809
|
+
foundApproval = found?.approval;
|
|
4810
|
+
}
|
|
4449
4811
|
|
|
4450
4812
|
if (!foundConversation || !foundApproval) {
|
|
4451
4813
|
writeJson(response, 404, {
|
|
@@ -4456,28 +4818,23 @@ export const createRequestHandler = async (options?: {
|
|
|
4456
4818
|
}
|
|
4457
4819
|
|
|
4458
4820
|
const conversationId = foundConversation.conversationId;
|
|
4821
|
+
foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
|
|
4459
4822
|
|
|
4460
4823
|
if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
writeJson(response, 404, {
|
|
4465
|
-
code: "APPROVAL_NOT_FOUND",
|
|
4466
|
-
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.",
|
|
4467
4827
|
});
|
|
4468
4828
|
return;
|
|
4469
4829
|
}
|
|
4470
4830
|
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
batchDecisions.set(approvalId, approved);
|
|
4479
|
-
|
|
4480
|
-
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);
|
|
4481
4838
|
|
|
4482
4839
|
broadcastEvent(conversationId,
|
|
4483
4840
|
approved
|
|
@@ -4485,25 +4842,18 @@ export const createRequestHandler = async (options?: {
|
|
|
4485
4842
|
: { type: "tool:approval:denied", approvalId },
|
|
4486
4843
|
);
|
|
4487
4844
|
|
|
4488
|
-
const
|
|
4845
|
+
const refreshedConversation = await conversationStore.get(conversationId);
|
|
4846
|
+
const allApprovals = (refreshedConversation?.pendingApprovals ?? []).map((approval) =>
|
|
4847
|
+
normalizeApprovalCheckpoint(approval, refreshedConversation!.messages),
|
|
4848
|
+
);
|
|
4489
4849
|
const allDecided = allApprovals.length > 0 &&
|
|
4490
|
-
allApprovals.every(a =>
|
|
4850
|
+
allApprovals.every(a => a.decision != null);
|
|
4491
4851
|
|
|
4492
4852
|
if (!allDecided) {
|
|
4493
|
-
// Still waiting for more decisions — persist best-effort and respond.
|
|
4494
|
-
// The write may be overwritten by a concurrent request, but that's
|
|
4495
|
-
// fine: the in-memory tracker is the source of truth for completion.
|
|
4496
|
-
await conversationStore.update(foundConversation);
|
|
4497
4853
|
writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
|
|
4498
4854
|
return;
|
|
4499
4855
|
}
|
|
4500
4856
|
|
|
4501
|
-
// All approvals in the batch are decided — apply tracked decisions,
|
|
4502
|
-
// execute approved tools, and resume the run.
|
|
4503
|
-
for (const a of allApprovals) {
|
|
4504
|
-
const d = batchDecisions.get(a.approvalId);
|
|
4505
|
-
if (d != null) a.decision = d ? "approved" : "denied";
|
|
4506
|
-
}
|
|
4507
4857
|
approvalDecisionTracker.delete(conversationId);
|
|
4508
4858
|
|
|
4509
4859
|
foundConversation.pendingApprovals = [];
|
|
@@ -4741,6 +5091,7 @@ export const createRequestHandler = async (options?: {
|
|
|
4741
5091
|
runId: a.runId,
|
|
4742
5092
|
tool: a.tool,
|
|
4743
5093
|
input: a.input,
|
|
5094
|
+
decision: a.decision,
|
|
4744
5095
|
}))
|
|
4745
5096
|
: [];
|
|
4746
5097
|
// Collect pending approvals from subagent conversations (in-memory map, no disk I/O)
|
|
@@ -4759,29 +5110,21 @@ export const createRequestHandler = async (options?: {
|
|
|
4759
5110
|
}
|
|
4760
5111
|
const activeStream = conversationEventStreams.get(conversationId);
|
|
4761
5112
|
const hasActiveRun = (!!activeStream && !activeStream.finished) || conversation.runStatus === "running";
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
// On serverless, in-memory maps may be empty — also check store
|
|
4766
|
-
if (!hasRunningSubagents && !conversation.parentConversationId) {
|
|
4767
|
-
const summaries = await conversationStore.listSummaries(conversation.ownerId);
|
|
4768
|
-
for (const s of summaries) {
|
|
4769
|
-
if (s.parentConversationId !== conversationId) continue;
|
|
4770
|
-
const c = await conversationStore.get(s.conversationId);
|
|
4771
|
-
if (c?.subagentMeta?.status === "running") {
|
|
4772
|
-
hasRunningSubagents = true;
|
|
4773
|
-
break;
|
|
4774
|
-
}
|
|
4775
|
-
}
|
|
4776
|
-
}
|
|
5113
|
+
const hasRunningSubagents = !conversation.parentConversationId
|
|
5114
|
+
? await hasRunningSubagentsForParent(conversationId, conversation.ownerId)
|
|
5115
|
+
: false;
|
|
4777
5116
|
const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults)
|
|
4778
5117
|
&& conversation.pendingSubagentResults.length > 0;
|
|
5118
|
+
const hasPendingApprovals = Array.isArray(conversation.pendingApprovals)
|
|
5119
|
+
&& conversation.pendingApprovals.length > 0;
|
|
4779
5120
|
const needsContinuation = !hasActiveRun
|
|
4780
5121
|
&& Array.isArray(conversation._continuationMessages)
|
|
4781
|
-
&& conversation._continuationMessages.length > 0
|
|
5122
|
+
&& conversation._continuationMessages.length > 0
|
|
5123
|
+
&& !hasPendingApprovals;
|
|
4782
5124
|
writeJson(response, 200, {
|
|
4783
5125
|
conversation: {
|
|
4784
5126
|
...conversation,
|
|
5127
|
+
messages: conversation.messages.map(normalizeMessageForClient),
|
|
4785
5128
|
pendingApprovals: storedPending,
|
|
4786
5129
|
_continuationMessages: undefined,
|
|
4787
5130
|
_harnessMessages: undefined,
|
|
@@ -4989,11 +5332,9 @@ export const createRequestHandler = async (options?: {
|
|
|
4989
5332
|
eventCount++;
|
|
4990
5333
|
let sseEvent: AgentEvent = event;
|
|
4991
5334
|
if (sseEvent.type === "run:completed") {
|
|
4992
|
-
const
|
|
4993
|
-
r => r.parentConversationId === conversationId,
|
|
4994
|
-
);
|
|
5335
|
+
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
4995
5336
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
4996
|
-
sseEvent =
|
|
5337
|
+
sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
|
|
4997
5338
|
}
|
|
4998
5339
|
try {
|
|
4999
5340
|
response.write(formatSseEvent(sseEvent));
|
|
@@ -5020,7 +5361,10 @@ export const createRequestHandler = async (options?: {
|
|
|
5020
5361
|
// fire a delayed safety net in case the client disconnects before
|
|
5021
5362
|
// POSTing the next /continue.
|
|
5022
5363
|
const freshConv = await conversationStore.get(conversationId);
|
|
5023
|
-
if (
|
|
5364
|
+
if (
|
|
5365
|
+
freshConv?._continuationMessages?.length &&
|
|
5366
|
+
(!Array.isArray(freshConv.pendingApprovals) || freshConv.pendingApprovals.length === 0)
|
|
5367
|
+
) {
|
|
5024
5368
|
doWaitUntil(
|
|
5025
5369
|
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5026
5370
|
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
@@ -5112,11 +5456,17 @@ export const createRequestHandler = async (options?: {
|
|
|
5112
5456
|
Connection: "keep-alive",
|
|
5113
5457
|
"X-Accel-Buffering": "no",
|
|
5114
5458
|
});
|
|
5115
|
-
const
|
|
5116
|
-
|
|
5117
|
-
:
|
|
5459
|
+
const canonicalHistory = resolveRunRequest(conversation, {
|
|
5460
|
+
conversationId,
|
|
5461
|
+
messages: conversation.messages,
|
|
5462
|
+
});
|
|
5463
|
+
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
5464
|
+
const harnessMessages = [...canonicalHistory.messages];
|
|
5118
5465
|
const historyMessages = [...conversation.messages];
|
|
5119
5466
|
const preRunMessages = [...conversation.messages];
|
|
5467
|
+
console.info(
|
|
5468
|
+
`[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`,
|
|
5469
|
+
);
|
|
5120
5470
|
let latestRunId = conversation.runtimeRunId ?? "";
|
|
5121
5471
|
let assistantResponse = "";
|
|
5122
5472
|
const toolTimeline: string[] = [];
|
|
@@ -5332,6 +5682,26 @@ export const createRequestHandler = async (options?: {
|
|
|
5332
5682
|
const toolText = `- approval required \`${event.tool}\``;
|
|
5333
5683
|
toolTimeline.push(toolText);
|
|
5334
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
|
+
}
|
|
5335
5705
|
await persistDraftAssistantTurn();
|
|
5336
5706
|
}
|
|
5337
5707
|
if (event.type === "tool:approval:checkpoint") {
|
|
@@ -5355,16 +5725,13 @@ export const createRequestHandler = async (options?: {
|
|
|
5355
5725
|
}]
|
|
5356
5726
|
: []),
|
|
5357
5727
|
];
|
|
5358
|
-
conversation.pendingApprovals =
|
|
5359
|
-
|
|
5728
|
+
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
5729
|
+
approvals: event.approvals,
|
|
5360
5730
|
runId: latestRunId,
|
|
5361
|
-
tool: a.tool,
|
|
5362
|
-
toolCallId: a.toolCallId,
|
|
5363
|
-
input: a.input,
|
|
5364
5731
|
checkpointMessages: event.checkpointMessages,
|
|
5365
5732
|
baseMessageCount: historyMessages.length,
|
|
5366
5733
|
pendingToolCalls: event.pendingToolCalls,
|
|
5367
|
-
})
|
|
5734
|
+
});
|
|
5368
5735
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5369
5736
|
conversation.updatedAt = Date.now();
|
|
5370
5737
|
await conversationStore.update(conversation);
|
|
@@ -5400,7 +5767,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5400
5767
|
conversation._harnessMessages = runContinuationMessages;
|
|
5401
5768
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5402
5769
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
5403
|
-
|
|
5770
|
+
if (!checkpointedRun) {
|
|
5771
|
+
conversation.pendingApprovals = [];
|
|
5772
|
+
}
|
|
5404
5773
|
if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
|
|
5405
5774
|
if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
|
|
5406
5775
|
conversation.updatedAt = Date.now();
|
|
@@ -5408,11 +5777,13 @@ export const createRequestHandler = async (options?: {
|
|
|
5408
5777
|
|
|
5409
5778
|
// Delayed safety net: if the client doesn't POST to /continue
|
|
5410
5779
|
// within 3 seconds (e.g. browser closed), the server picks it up.
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5780
|
+
if (!checkpointedRun) {
|
|
5781
|
+
doWaitUntil(
|
|
5782
|
+
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5783
|
+
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
5784
|
+
),
|
|
5785
|
+
);
|
|
5786
|
+
}
|
|
5416
5787
|
}
|
|
5417
5788
|
}
|
|
5418
5789
|
await telemetry.emit(event);
|
|
@@ -5420,11 +5791,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5420
5791
|
? { ...event, compactedMessages: undefined }
|
|
5421
5792
|
: event;
|
|
5422
5793
|
if (sseEvent.type === "run:completed") {
|
|
5423
|
-
const
|
|
5424
|
-
r => r.parentConversationId === conversationId,
|
|
5425
|
-
);
|
|
5794
|
+
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
5426
5795
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
5427
|
-
if (
|
|
5796
|
+
if (hasPendingSubagents) {
|
|
5428
5797
|
sseEvent = { ...stripped, pendingSubagents: true };
|
|
5429
5798
|
} else {
|
|
5430
5799
|
sseEvent = stripped;
|
|
@@ -5472,6 +5841,10 @@ export const createRequestHandler = async (options?: {
|
|
|
5472
5841
|
conversation._continuationMessages = undefined;
|
|
5473
5842
|
if (runHarnessMessages) {
|
|
5474
5843
|
conversation._harnessMessages = runHarnessMessages;
|
|
5844
|
+
} else if (shouldRebuildCanonical) {
|
|
5845
|
+
conversation._harnessMessages = conversation.messages;
|
|
5846
|
+
} else {
|
|
5847
|
+
conversation._harnessMessages = conversation.messages;
|
|
5475
5848
|
}
|
|
5476
5849
|
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
5477
5850
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
@@ -5509,7 +5882,9 @@ export const createRequestHandler = async (options?: {
|
|
|
5509
5882
|
conversation.updatedAt = Date.now();
|
|
5510
5883
|
await conversationStore.update(conversation);
|
|
5511
5884
|
}
|
|
5512
|
-
|
|
5885
|
+
if (!checkpointedRun) {
|
|
5886
|
+
await clearPendingApprovalsForConversation(conversationId);
|
|
5887
|
+
}
|
|
5513
5888
|
return;
|
|
5514
5889
|
}
|
|
5515
5890
|
try {
|
|
@@ -5557,20 +5932,32 @@ export const createRequestHandler = async (options?: {
|
|
|
5557
5932
|
if (active && active.abortController === abortController) {
|
|
5558
5933
|
activeConversationRuns.delete(conversationId);
|
|
5559
5934
|
}
|
|
5560
|
-
finishConversationStream(conversationId);
|
|
5561
5935
|
if (latestRunId) {
|
|
5562
5936
|
runOwners.delete(latestRunId);
|
|
5563
5937
|
runConversations.delete(latestRunId);
|
|
5564
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
|
+
|
|
5565
5955
|
try {
|
|
5566
5956
|
response.end();
|
|
5567
5957
|
} catch {
|
|
5568
5958
|
// Already closed.
|
|
5569
5959
|
}
|
|
5570
|
-
|
|
5571
|
-
const hadDeferred = pendingCallbackNeeded.delete(conversationId);
|
|
5572
|
-
const freshConv = await conversationStore.get(conversationId);
|
|
5573
|
-
if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
|
|
5960
|
+
if (needsCallback) {
|
|
5574
5961
|
processSubagentCallback(conversationId, true).catch(err =>
|
|
5575
5962
|
console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err),
|
|
5576
5963
|
);
|
|
@@ -5635,46 +6022,42 @@ export const createRequestHandler = async (options?: {
|
|
|
5635
6022
|
if (!conv) continue;
|
|
5636
6023
|
|
|
5637
6024
|
const task = `[Scheduled: ${jobName}]\n${cronJob.task}`;
|
|
5638
|
-
const
|
|
5639
|
-
|
|
5640
|
-
:
|
|
6025
|
+
const historySelection = resolveRunRequest(conv, {
|
|
6026
|
+
conversationId: conv.conversationId,
|
|
6027
|
+
messages: conv.messages,
|
|
6028
|
+
});
|
|
6029
|
+
const historyMessages = [...historySelection.messages];
|
|
5641
6030
|
try {
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
steps = event.result.steps;
|
|
5659
|
-
if (!assistantResponse && event.result.response) {
|
|
5660
|
-
assistantResponse = event.result.response;
|
|
5661
|
-
}
|
|
5662
|
-
if (event.result.continuationMessages) {
|
|
5663
|
-
cronHarnessMessages = event.result.continuationMessages;
|
|
5664
|
-
}
|
|
5665
|
-
}
|
|
5666
|
-
await telemetry.emit(event);
|
|
5667
|
-
}
|
|
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;
|
|
5668
6047
|
|
|
5669
6048
|
conv.messages = [
|
|
5670
6049
|
...historyMessages,
|
|
5671
6050
|
{ role: "user" as const, content: task },
|
|
5672
6051
|
...(assistantResponse ? [{ role: "assistant" as const, content: assistantResponse }] : []),
|
|
5673
6052
|
];
|
|
5674
|
-
if (
|
|
5675
|
-
conv._harnessMessages =
|
|
6053
|
+
if (execution.runHarnessMessages) {
|
|
6054
|
+
conv._harnessMessages = execution.runHarnessMessages;
|
|
6055
|
+
} else if (historySelection.shouldRebuildCanonical) {
|
|
6056
|
+
conv._harnessMessages = conv.messages;
|
|
5676
6057
|
}
|
|
5677
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;
|
|
5678
6061
|
conv.updatedAt = Date.now();
|
|
5679
6062
|
await conversationStore.update(conv);
|
|
5680
6063
|
|
|
@@ -5691,7 +6074,7 @@ export const createRequestHandler = async (options?: {
|
|
|
5691
6074
|
console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
|
|
5692
6075
|
}
|
|
5693
6076
|
}
|
|
5694
|
-
chatResults.push({ chatId, status: "completed", steps });
|
|
6077
|
+
chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
|
|
5695
6078
|
} catch (runError) {
|
|
5696
6079
|
chatResults.push({ chatId, status: "error" });
|
|
5697
6080
|
console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
|
|
@@ -5991,91 +6374,32 @@ export const startDevServer = async (
|
|
|
5991
6374
|
conversationId: string,
|
|
5992
6375
|
historyMessages: Message[],
|
|
5993
6376
|
toolResultArchive?: Conversation["_toolResultArchive"],
|
|
5994
|
-
onEvent?: (event: AgentEvent) => void
|
|
6377
|
+
onEvent?: (event: AgentEvent) => void | Promise<void>,
|
|
5995
6378
|
): Promise<CronRunResult> => {
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
task,
|
|
6007
|
-
conversationId,
|
|
6008
|
-
parameters: {
|
|
6009
|
-
__activeConversationId: conversationId,
|
|
6010
|
-
[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,
|
|
6011
6389
|
},
|
|
6012
|
-
|
|
6013
|
-
})
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
sections.push({ type: "tools", content: currentTools });
|
|
6018
|
-
currentTools = [];
|
|
6019
|
-
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
6020
|
-
assistantResponse += " ";
|
|
6021
|
-
}
|
|
6022
|
-
}
|
|
6023
|
-
assistantResponse += event.content;
|
|
6024
|
-
currentText += event.content;
|
|
6025
|
-
}
|
|
6026
|
-
if (event.type === "tool:started") {
|
|
6027
|
-
if (currentText.length > 0) {
|
|
6028
|
-
sections.push({ type: "text", content: currentText });
|
|
6029
|
-
currentText = "";
|
|
6030
|
-
}
|
|
6031
|
-
const toolText = `- start \`${event.tool}\``;
|
|
6032
|
-
toolTimeline.push(toolText);
|
|
6033
|
-
currentTools.push(toolText);
|
|
6034
|
-
}
|
|
6035
|
-
if (event.type === "tool:completed") {
|
|
6036
|
-
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
6037
|
-
toolTimeline.push(toolText);
|
|
6038
|
-
currentTools.push(toolText);
|
|
6039
|
-
}
|
|
6040
|
-
if (event.type === "tool:error") {
|
|
6041
|
-
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
6042
|
-
toolTimeline.push(toolText);
|
|
6043
|
-
currentTools.push(toolText);
|
|
6044
|
-
}
|
|
6045
|
-
if (event.type === "run:completed") {
|
|
6046
|
-
steps = event.result.steps;
|
|
6047
|
-
contextTokens = event.result.contextTokens ?? 0;
|
|
6048
|
-
contextWindow = event.result.contextWindow ?? 0;
|
|
6049
|
-
if (event.result.continuationMessages) {
|
|
6050
|
-
harnessMessages = event.result.continuationMessages;
|
|
6051
|
-
}
|
|
6052
|
-
if (!assistantResponse && event.result.response) {
|
|
6053
|
-
assistantResponse = event.result.response;
|
|
6054
|
-
}
|
|
6055
|
-
}
|
|
6056
|
-
}
|
|
6057
|
-
if (currentTools.length > 0) {
|
|
6058
|
-
sections.push({ type: "tools", content: currentTools });
|
|
6059
|
-
}
|
|
6060
|
-
if (currentText.length > 0) {
|
|
6061
|
-
sections.push({ type: "text", content: currentText });
|
|
6062
|
-
}
|
|
6063
|
-
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
6064
|
-
const assistantMetadata =
|
|
6065
|
-
toolTimeline.length > 0 || sections.length > 0
|
|
6066
|
-
? ({
|
|
6067
|
-
toolActivity: [...toolTimeline],
|
|
6068
|
-
sections: sections.length > 0 ? sections : undefined,
|
|
6069
|
-
} as Message["metadata"])
|
|
6070
|
-
: 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);
|
|
6071
6395
|
return {
|
|
6072
|
-
response: assistantResponse,
|
|
6073
|
-
steps,
|
|
6396
|
+
response: execution.draft.assistantResponse,
|
|
6397
|
+
steps: execution.runSteps,
|
|
6074
6398
|
assistantMetadata,
|
|
6075
6399
|
hasContent,
|
|
6076
|
-
contextTokens,
|
|
6077
|
-
contextWindow,
|
|
6078
|
-
harnessMessages,
|
|
6400
|
+
contextTokens: execution.runContextTokens,
|
|
6401
|
+
contextWindow: execution.runContextWindow,
|
|
6402
|
+
harnessMessages: execution.runHarnessMessages,
|
|
6079
6403
|
toolResultArchive: harnessRef.getToolResultArchive(conversationId),
|
|
6080
6404
|
};
|
|
6081
6405
|
};
|
|
@@ -6147,9 +6471,11 @@ export const startDevServer = async (
|
|
|
6147
6471
|
if (!conversation) continue;
|
|
6148
6472
|
|
|
6149
6473
|
const task = `[Scheduled: ${jobName}]\n${config.task}`;
|
|
6150
|
-
const
|
|
6151
|
-
|
|
6152
|
-
:
|
|
6474
|
+
const historySelection = resolveRunRequest(conversation, {
|
|
6475
|
+
conversationId: conversation.conversationId,
|
|
6476
|
+
messages: conversation.messages,
|
|
6477
|
+
});
|
|
6478
|
+
const historyMessages = [...historySelection.messages];
|
|
6153
6479
|
const convId = conversation.conversationId;
|
|
6154
6480
|
|
|
6155
6481
|
activeRuns?.set(convId, {
|
|
@@ -6170,6 +6496,8 @@ export const startDevServer = async (
|
|
|
6170
6496
|
freshConv.messages = buildCronMessages(task, historyMessages, result);
|
|
6171
6497
|
if (result.harnessMessages) {
|
|
6172
6498
|
freshConv._harnessMessages = result.harnessMessages;
|
|
6499
|
+
} else if (historySelection.shouldRebuildCanonical) {
|
|
6500
|
+
freshConv._harnessMessages = freshConv.messages;
|
|
6173
6501
|
}
|
|
6174
6502
|
if (result.toolResultArchive) {
|
|
6175
6503
|
freshConv._toolResultArchive = result.toolResultArchive;
|