@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.
@@ -1861,6 +1861,7 @@ var getWebUiClientScript = (markedSource2) => `
1861
1861
  todoPanelCollapsed: false,
1862
1862
  cronSectionCollapsed: true,
1863
1863
  cronShowAll: false,
1864
+ subagentPollInFlight: {},
1864
1865
  };
1865
1866
 
1866
1867
  const agentInitial = document.body.dataset.agentInitial || "A";
@@ -2320,11 +2321,20 @@ var getWebUiClientScript = (markedSource2) => `
2320
2321
  return null;
2321
2322
  }
2322
2323
  const toolName = item && typeof item.tool === "string" ? item.tool : "tool";
2324
+ const decision =
2325
+ item && typeof item.decision === "string" ? item.decision : null;
2326
+ const isResolved = decision === "approved" || decision === "denied";
2323
2327
  return {
2324
2328
  approvalId,
2325
2329
  tool: toolName,
2326
2330
  input: item?.input ?? {},
2327
- state: "pending",
2331
+ state: isResolved ? "resolved" : "pending",
2332
+ resolvedDecision:
2333
+ decision === "approved"
2334
+ ? "approve"
2335
+ : decision === "denied"
2336
+ ? "deny"
2337
+ : null,
2328
2338
  };
2329
2339
  })
2330
2340
  .filter(Boolean);
@@ -3374,8 +3384,8 @@ var getWebUiClientScript = (markedSource2) => `
3374
3384
  updateContextRing();
3375
3385
  renderMessages(state.activeMessages, payload.hasActiveRun);
3376
3386
  }
3377
- if (payload.hasActiveRun) {
3378
- if (window._connectBrowserStream) window._connectBrowserStream();
3387
+ if (payload.hasActiveRun || payload.hasRunningSubagents) {
3388
+ if (payload.hasActiveRun && window._connectBrowserStream) window._connectBrowserStream();
3379
3389
  setTimeout(poll, 2000);
3380
3390
  } else {
3381
3391
  setStreaming(false);
@@ -3390,9 +3400,15 @@ var getWebUiClientScript = (markedSource2) => `
3390
3400
  };
3391
3401
 
3392
3402
  const pollForSubagentResults = (conversationId) => {
3403
+ if (state.subagentPollInFlight[conversationId]) return;
3404
+ state.subagentPollInFlight[conversationId] = true;
3393
3405
  let lastMessageCount = state.activeMessages ? state.activeMessages.length : 0;
3406
+ let lastUpdatedAt = 0;
3394
3407
  const poll = async () => {
3395
- if (state.activeConversationId !== conversationId) return;
3408
+ if (state.activeConversationId !== conversationId) {
3409
+ delete state.subagentPollInFlight[conversationId];
3410
+ return;
3411
+ }
3396
3412
  try {
3397
3413
  var payload = await api("/api/conversations/" + encodeURIComponent(conversationId));
3398
3414
  if (state.activeConversationId !== conversationId) return;
@@ -3413,13 +3429,17 @@ var getWebUiClientScript = (markedSource2) => `
3413
3429
  });
3414
3430
  });
3415
3431
  }
3416
- if (messages.length > lastMessageCount) {
3432
+ const conversationUpdatedAt =
3433
+ typeof payload.conversation.updatedAt === "number" ? payload.conversation.updatedAt : 0;
3434
+ if (messages.length > lastMessageCount || conversationUpdatedAt > lastUpdatedAt) {
3417
3435
  lastMessageCount = messages.length;
3436
+ lastUpdatedAt = conversationUpdatedAt;
3418
3437
  state.activeMessages = hydratePendingApprovals(messages, allPending);
3419
3438
  renderMessages(state.activeMessages, payload.hasActiveRun || payload.hasRunningSubagents);
3420
3439
  }
3421
3440
  if (payload.hasActiveRun) {
3422
3441
  // Parent agent started its continuation run \u2014 switch to live stream
3442
+ delete state.subagentPollInFlight[conversationId];
3423
3443
  setStreaming(true);
3424
3444
  state.activeMessages = hydratePendingApprovals(messages, allPending);
3425
3445
  streamConversationEvents(conversationId, { liveOnly: true }).finally(() => {
@@ -3432,6 +3452,7 @@ var getWebUiClientScript = (markedSource2) => `
3432
3452
  } else {
3433
3453
  renderMessages(state.activeMessages, false);
3434
3454
  await loadConversations();
3455
+ delete state.subagentPollInFlight[conversationId];
3435
3456
  }
3436
3457
  }
3437
3458
  } catch {
@@ -4359,6 +4380,7 @@ var getWebUiClientScript = (markedSource2) => `
4359
4380
  let _maxSteps = 0;
4360
4381
  let _receivedTerminalEvent = false;
4361
4382
  let _shouldContinue = false;
4383
+ let _pendingSubagentsConversation = null;
4362
4384
 
4363
4385
  // Helper to read an SSE stream from a fetch response
4364
4386
  const readSseStream = async (response) => {
@@ -4676,7 +4698,7 @@ var getWebUiClientScript = (markedSource2) => `
4676
4698
  assistantMessage.content = String(payload.result?.response || "");
4677
4699
  }
4678
4700
  if (payload.pendingSubagents) {
4679
- pollForSubagentResults(conversationId);
4701
+ _pendingSubagentsConversation = conversationId;
4680
4702
  }
4681
4703
  renderIfActiveConversation(false);
4682
4704
  }
@@ -4804,6 +4826,19 @@ var getWebUiClientScript = (markedSource2) => `
4804
4826
  }
4805
4827
  elements.prompt.focus();
4806
4828
  }
4829
+
4830
+ // Subagent callback: after sendMessage fully completes (including
4831
+ // finally cleanup), reload the conversation. loadConversation is
4832
+ // the exact same code path as a manual refresh \u2014 if the callback
4833
+ // is still running it connects to the event stream; if it already
4834
+ // finished it just renders the final persisted state.
4835
+ if (_pendingSubagentsConversation && state.activeConversationId === _pendingSubagentsConversation) {
4836
+ const cbConvId = _pendingSubagentsConversation;
4837
+ await new Promise(r => setTimeout(r, 1200));
4838
+ if (state.activeConversationId === cbConvId && !state.isStreaming) {
4839
+ await loadConversation(cbConvId);
4840
+ }
4841
+ }
4807
4842
  };
4808
4843
 
4809
4844
  const requireAuth = async () => {
@@ -5037,9 +5072,22 @@ var getWebUiClientScript = (markedSource2) => `
5037
5072
  }));
5038
5073
  return api("/api/approvals/" + encodeURIComponent(approvalId), {
5039
5074
  method: "POST",
5040
- body: JSON.stringify({ approved: decision === "approve" }),
5075
+ body: JSON.stringify({
5076
+ approved: decision === "approve",
5077
+ conversationId: state.activeConversationId || undefined,
5078
+ }),
5041
5079
  }).catch((error) => {
5042
5080
  const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
5081
+ const isNotReady = error && error.payload && error.payload.code === "APPROVAL_NOT_READY";
5082
+ if (isNotReady) {
5083
+ updatePendingApproval(approvalId, (request) => ({
5084
+ ...request,
5085
+ state: "pending",
5086
+ pendingDecision: null,
5087
+ resolvedDecision: null,
5088
+ }));
5089
+ return;
5090
+ }
5043
5091
  if (isStale) {
5044
5092
  updatePendingApproval(approvalId, () => null);
5045
5093
  } else {
@@ -5110,9 +5158,23 @@ var getWebUiClientScript = (markedSource2) => `
5110
5158
  for (const aid of pending) {
5111
5159
  await api("/api/approvals/" + encodeURIComponent(aid), {
5112
5160
  method: "POST",
5113
- body: JSON.stringify({ approved: decision === "approve" }),
5161
+ body: JSON.stringify({
5162
+ approved: decision === "approve",
5163
+ conversationId: state.activeConversationId || undefined,
5164
+ }),
5114
5165
  }).catch((error) => {
5115
5166
  const isStale = error && error.payload && error.payload.code === "APPROVAL_NOT_FOUND";
5167
+ const isNotReady = error && error.payload && error.payload.code === "APPROVAL_NOT_READY";
5168
+ if (isNotReady) {
5169
+ updatePendingApproval(aid, (request) => ({
5170
+ ...request,
5171
+ state: "pending",
5172
+ pendingDecision: null,
5173
+ resolvedDecision: null,
5174
+ }));
5175
+ renderMessages(state.activeMessages, state.isStreaming);
5176
+ return;
5177
+ }
5116
5178
  if (isStale) {
5117
5179
  updatePendingApproval(aid, () => null);
5118
5180
  } else {
@@ -7412,6 +7474,13 @@ var readRequestBody = async (request) => {
7412
7474
  const body = Buffer.concat(chunks).toString("utf8");
7413
7475
  return body.length > 0 ? JSON.parse(body) : {};
7414
7476
  };
7477
+ var parseTelegramMessageThreadIdFromPlatformThreadId = (platformThreadId, chatId) => {
7478
+ if (!platformThreadId || !chatId) return void 0;
7479
+ const parts = platformThreadId.split(":");
7480
+ if (parts.length !== 3 || parts[0] !== chatId) return void 0;
7481
+ const threadId = Number(parts[1]);
7482
+ return Number.isInteger(threadId) ? threadId : void 0;
7483
+ };
7415
7484
  var MAX_UPLOAD_SIZE = 25 * 1024 * 1024;
7416
7485
  var parseMultipartRequest = (request) => new Promise((resolve4, reject) => {
7417
7486
  const result = { message: "", files: [] };
@@ -7528,6 +7597,227 @@ var parseParams = (values) => {
7528
7597
  }
7529
7598
  return params;
7530
7599
  };
7600
+ var normalizeMessageForClient = (message) => {
7601
+ if (message.role !== "assistant" || typeof message.content !== "string") {
7602
+ return message;
7603
+ }
7604
+ try {
7605
+ const parsed = JSON.parse(message.content);
7606
+ const text = typeof parsed.text === "string" ? parsed.text : void 0;
7607
+ const toolCalls = Array.isArray(parsed.tool_calls) ? parsed.tool_calls : void 0;
7608
+ if (typeof text === "string" && toolCalls) {
7609
+ return {
7610
+ ...message,
7611
+ content: text
7612
+ };
7613
+ }
7614
+ } catch {
7615
+ }
7616
+ return message;
7617
+ };
7618
+ var isMessageArray = (value) => Array.isArray(value) && value.every((entry) => {
7619
+ if (!entry || typeof entry !== "object") return false;
7620
+ const row = entry;
7621
+ const role = row.role;
7622
+ const content = row.content;
7623
+ const roleOk = role === "system" || role === "user" || role === "assistant" || role === "tool";
7624
+ const contentOk = typeof content === "string" || Array.isArray(content);
7625
+ return roleOk && contentOk;
7626
+ });
7627
+ var loadCanonicalHistory = (conversation) => {
7628
+ if (isMessageArray(conversation._harnessMessages) && conversation._harnessMessages.length > 0) {
7629
+ return { messages: [...conversation._harnessMessages], source: "harness" };
7630
+ }
7631
+ return { messages: [...conversation.messages], source: "messages" };
7632
+ };
7633
+ var loadRunHistory = (conversation, options) => {
7634
+ if (options?.preferContinuation && isMessageArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0) {
7635
+ return {
7636
+ messages: [...conversation._continuationMessages],
7637
+ source: "continuation",
7638
+ shouldRebuildCanonical: !isMessageArray(conversation._harnessMessages) || conversation._harnessMessages.length === 0
7639
+ };
7640
+ }
7641
+ const canonical = loadCanonicalHistory(conversation);
7642
+ return {
7643
+ ...canonical,
7644
+ shouldRebuildCanonical: canonical.source !== "harness"
7645
+ };
7646
+ };
7647
+ var createTurnDraftState = () => ({
7648
+ assistantResponse: "",
7649
+ toolTimeline: [],
7650
+ sections: [],
7651
+ currentTools: [],
7652
+ currentText: ""
7653
+ });
7654
+ var cloneSections = (sections) => sections.map((section) => ({
7655
+ type: section.type,
7656
+ content: Array.isArray(section.content) ? [...section.content] : section.content
7657
+ }));
7658
+ var flushTurnDraft = (draft) => {
7659
+ if (draft.currentTools.length > 0) {
7660
+ draft.sections.push({ type: "tools", content: draft.currentTools });
7661
+ draft.currentTools = [];
7662
+ }
7663
+ if (draft.currentText.length > 0) {
7664
+ draft.sections.push({ type: "text", content: draft.currentText });
7665
+ draft.currentText = "";
7666
+ }
7667
+ };
7668
+ var recordStandardTurnEvent = (draft, event) => {
7669
+ if (event.type === "model:chunk") {
7670
+ if (draft.currentTools.length > 0) {
7671
+ draft.sections.push({ type: "tools", content: draft.currentTools });
7672
+ draft.currentTools = [];
7673
+ if (draft.assistantResponse.length > 0 && !/\s$/.test(draft.assistantResponse)) {
7674
+ draft.assistantResponse += " ";
7675
+ }
7676
+ }
7677
+ draft.assistantResponse += event.content;
7678
+ draft.currentText += event.content;
7679
+ return;
7680
+ }
7681
+ if (event.type === "tool:started") {
7682
+ if (draft.currentText.length > 0) {
7683
+ draft.sections.push({ type: "text", content: draft.currentText });
7684
+ draft.currentText = "";
7685
+ }
7686
+ const toolText = `- start \`${event.tool}\``;
7687
+ draft.toolTimeline.push(toolText);
7688
+ draft.currentTools.push(toolText);
7689
+ return;
7690
+ }
7691
+ if (event.type === "tool:completed") {
7692
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
7693
+ draft.toolTimeline.push(toolText);
7694
+ draft.currentTools.push(toolText);
7695
+ return;
7696
+ }
7697
+ if (event.type === "tool:error") {
7698
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
7699
+ draft.toolTimeline.push(toolText);
7700
+ draft.currentTools.push(toolText);
7701
+ }
7702
+ };
7703
+ var buildAssistantMetadata = (draft, sectionsOverride) => {
7704
+ const sections = sectionsOverride ?? cloneSections(draft.sections);
7705
+ if (draft.toolTimeline.length === 0 && sections.length === 0) return void 0;
7706
+ return {
7707
+ toolActivity: [...draft.toolTimeline],
7708
+ sections: sections.length > 0 ? sections : void 0
7709
+ };
7710
+ };
7711
+ var executeConversationTurn = async ({
7712
+ harness,
7713
+ runInput,
7714
+ initialContextTokens = 0,
7715
+ initialContextWindow = 0,
7716
+ onEvent
7717
+ }) => {
7718
+ const draft = createTurnDraftState();
7719
+ let latestRunId = "";
7720
+ let runCancelled = false;
7721
+ let runContinuation = false;
7722
+ let runContinuationMessages;
7723
+ let runHarnessMessages;
7724
+ let runContextTokens = initialContextTokens;
7725
+ let runContextWindow = initialContextWindow;
7726
+ let runSteps = 0;
7727
+ let runMaxSteps;
7728
+ for await (const event of harness.runWithTelemetry(runInput)) {
7729
+ recordStandardTurnEvent(draft, event);
7730
+ if (event.type === "run:started") {
7731
+ latestRunId = event.runId;
7732
+ }
7733
+ if (event.type === "run:cancelled") {
7734
+ runCancelled = true;
7735
+ }
7736
+ if (event.type === "run:completed") {
7737
+ runContinuation = event.result.continuation === true;
7738
+ runContinuationMessages = event.result.continuationMessages;
7739
+ runHarnessMessages = event.result.continuationMessages;
7740
+ runContextTokens = event.result.contextTokens ?? runContextTokens;
7741
+ runContextWindow = event.result.contextWindow ?? runContextWindow;
7742
+ runSteps = event.result.steps;
7743
+ if (typeof event.result.maxSteps === "number") {
7744
+ runMaxSteps = event.result.maxSteps;
7745
+ }
7746
+ if (draft.assistantResponse.length === 0 && event.result.response) {
7747
+ draft.assistantResponse = event.result.response;
7748
+ }
7749
+ }
7750
+ if (event.type === "run:error") {
7751
+ draft.assistantResponse = draft.assistantResponse || `[Error: ${event.error.message}]`;
7752
+ }
7753
+ if (onEvent) {
7754
+ await onEvent(event, draft);
7755
+ }
7756
+ }
7757
+ return {
7758
+ latestRunId,
7759
+ runCancelled,
7760
+ runContinuation,
7761
+ runContinuationMessages,
7762
+ runHarnessMessages,
7763
+ runContextTokens,
7764
+ runContextWindow,
7765
+ runSteps,
7766
+ runMaxSteps,
7767
+ draft
7768
+ };
7769
+ };
7770
+ var normalizePendingToolCalls = (value) => {
7771
+ if (!Array.isArray(value)) return [];
7772
+ return value.filter((entry) => {
7773
+ if (!entry || typeof entry !== "object") return false;
7774
+ const row = entry;
7775
+ return typeof row.id === "string" && typeof row.name === "string" && typeof row.input === "object" && row.input !== null;
7776
+ }).map((entry) => ({ id: entry.id, name: entry.name, input: entry.input }));
7777
+ };
7778
+ var normalizeApprovalCheckpoint = (approval, fallbackMessages) => ({
7779
+ ...approval,
7780
+ checkpointMessages: isMessageArray(approval.checkpointMessages) ? approval.checkpointMessages : [...fallbackMessages],
7781
+ baseMessageCount: typeof approval.baseMessageCount === "number" && approval.baseMessageCount >= 0 ? approval.baseMessageCount : 0,
7782
+ pendingToolCalls: normalizePendingToolCalls(approval.pendingToolCalls)
7783
+ });
7784
+ var buildApprovalCheckpoints = ({
7785
+ approvals,
7786
+ runId,
7787
+ checkpointMessages,
7788
+ baseMessageCount,
7789
+ pendingToolCalls
7790
+ }) => approvals.map((approval) => ({
7791
+ approvalId: approval.approvalId,
7792
+ runId,
7793
+ tool: approval.tool,
7794
+ toolCallId: approval.toolCallId,
7795
+ input: approval.input,
7796
+ checkpointMessages,
7797
+ baseMessageCount,
7798
+ pendingToolCalls
7799
+ }));
7800
+ var resolveRunRequest = (conversation, request) => {
7801
+ const resolved = loadRunHistory(conversation, {
7802
+ preferContinuation: request.preferContinuation
7803
+ });
7804
+ return {
7805
+ source: resolved.source,
7806
+ shouldRebuildCanonical: resolved.shouldRebuildCanonical,
7807
+ messages: resolved.messages.length > 0 ? resolved.messages : request.messages
7808
+ };
7809
+ };
7810
+ var __internalRunOrchestration = {
7811
+ isMessageArray,
7812
+ loadCanonicalHistory,
7813
+ loadRunHistory,
7814
+ normalizeApprovalCheckpoint,
7815
+ buildApprovalCheckpoints,
7816
+ resolveRunRequest,
7817
+ createTurnDraftState,
7818
+ recordStandardTurnEvent,
7819
+ executeConversationTurn
7820
+ };
7531
7821
  var AGENT_TEMPLATE = (name, id, options) => `---
7532
7822
  name: ${name}
7533
7823
  id: ${id}
@@ -8711,6 +9001,60 @@ data: ${JSON.stringify(statusPayload)}
8711
9001
  const activeSubagentRuns = /* @__PURE__ */ new Map();
8712
9002
  const pendingSubagentApprovals = /* @__PURE__ */ new Map();
8713
9003
  const approvalDecisionTracker = /* @__PURE__ */ new Map();
9004
+ const findPendingApproval = async (approvalId, owner) => {
9005
+ const searchedConversationIds = /* @__PURE__ */ new Set();
9006
+ const scan = async (conversations) => {
9007
+ for (const conv of conversations) {
9008
+ if (searchedConversationIds.has(conv.conversationId)) continue;
9009
+ searchedConversationIds.add(conv.conversationId);
9010
+ if (!Array.isArray(conv.pendingApprovals)) continue;
9011
+ const match = conv.pendingApprovals.find((approval) => approval.approvalId === approvalId);
9012
+ if (match) {
9013
+ return { conversation: conv, approval: match };
9014
+ }
9015
+ }
9016
+ return void 0;
9017
+ };
9018
+ const ownerScoped = await scan(await conversationStore.list(owner));
9019
+ if (ownerScoped) return ownerScoped;
9020
+ if (owner === "local-owner") {
9021
+ return await scan(await conversationStore.list());
9022
+ }
9023
+ return void 0;
9024
+ };
9025
+ const hasRunningSubagentsForParent = async (parentConversationId, owner) => {
9026
+ let hasRunning = Array.from(activeSubagentRuns.values()).some(
9027
+ (run) => run.parentConversationId === parentConversationId
9028
+ );
9029
+ if (hasRunning) return true;
9030
+ const summaries = await conversationStore.listSummaries(owner);
9031
+ for (const summary of summaries) {
9032
+ if (summary.parentConversationId !== parentConversationId) continue;
9033
+ const childConversation = await conversationStore.get(summary.conversationId);
9034
+ if (childConversation?.subagentMeta?.status === "running") {
9035
+ hasRunning = true;
9036
+ break;
9037
+ }
9038
+ }
9039
+ return hasRunning;
9040
+ };
9041
+ const hasPendingSubagentWorkForParent = async (parentConversationId, owner) => {
9042
+ if (await hasRunningSubagentsForParent(parentConversationId, owner)) {
9043
+ return true;
9044
+ }
9045
+ if (pendingCallbackNeeded.has(parentConversationId)) {
9046
+ return true;
9047
+ }
9048
+ const parentConversation = await conversationStore.get(parentConversationId);
9049
+ if (!parentConversation) return false;
9050
+ if (Array.isArray(parentConversation.pendingSubagentResults) && parentConversation.pendingSubagentResults.length > 0) {
9051
+ return true;
9052
+ }
9053
+ if (typeof parentConversation.runningCallbackSince === "number" && parentConversation.runningCallbackSince > 0) {
9054
+ return true;
9055
+ }
9056
+ return false;
9057
+ };
8714
9058
  const getSubagentDepth = async (conversationId) => {
8715
9059
  let depth = 0;
8716
9060
  let current = await conversationStore.get(conversationId);
@@ -8754,7 +9098,9 @@ data: ${JSON.stringify(statusPayload)}
8754
9098
  const resumeSubagentFromCheckpoint = async (subagentId) => {
8755
9099
  const conv = await conversationStore.get(subagentId);
8756
9100
  if (!conv || !conv.parentConversationId) return;
8757
- const allApprovals = conv.pendingApprovals ?? [];
9101
+ const allApprovals = (conv.pendingApprovals ?? []).map(
9102
+ (approval) => normalizeApprovalCheckpoint(approval, conv.messages)
9103
+ );
8758
9104
  if (allApprovals.length === 0) return;
8759
9105
  const allDecided = allApprovals.every((a) => a.decision != null);
8760
9106
  if (!allDecided) return;
@@ -8825,7 +9171,11 @@ data: ${JSON.stringify(statusPayload)}
8825
9171
  if (conversation.subagentMeta?.status === "stopped") return;
8826
9172
  conversation.lastActivityAt = Date.now();
8827
9173
  await conversationStore.update(conversation);
8828
- const harnessMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
9174
+ const runOutcome = resolveRunRequest(conversation, {
9175
+ conversationId: childConversationId,
9176
+ messages: conversation.messages
9177
+ });
9178
+ const harnessMessages = [...runOutcome.messages];
8829
9179
  for await (const event of childHarness.runWithTelemetry({
8830
9180
  task,
8831
9181
  conversationId: childConversationId,
@@ -8887,16 +9237,13 @@ data: ${JSON.stringify(statusPayload)}
8887
9237
  if (event.type === "tool:approval:checkpoint") {
8888
9238
  const cpConv = await conversationStore.get(childConversationId);
8889
9239
  if (cpConv) {
8890
- const allCpData = event.approvals.map((a) => ({
8891
- approvalId: a.approvalId,
9240
+ const allCpData = buildApprovalCheckpoints({
9241
+ approvals: event.approvals,
8892
9242
  runId: latestRunId,
8893
- tool: a.tool,
8894
- toolCallId: a.toolCallId,
8895
- input: a.input,
8896
9243
  checkpointMessages: [...harnessMessages, ...event.checkpointMessages],
8897
9244
  baseMessageCount: 0,
8898
9245
  pendingToolCalls: event.pendingToolCalls
8899
- }));
9246
+ });
8900
9247
  cpConv.pendingApprovals = allCpData;
8901
9248
  cpConv.updatedAt = Date.now();
8902
9249
  await conversationStore.update(cpConv);
@@ -8911,7 +9258,7 @@ data: ${JSON.stringify(statusPayload)}
8911
9258
  });
8912
9259
  }
8913
9260
  });
8914
- const checkpointRef = allCpData[0];
9261
+ const checkpointRef = normalizeApprovalCheckpoint(allCpData[0], [...harnessMessages]);
8915
9262
  const toolContext = {
8916
9263
  runId: checkpointRef.runId,
8917
9264
  agentId: identity.id,
@@ -9033,6 +9380,8 @@ data: ${JSON.stringify(statusPayload)}
9033
9380
  }
9034
9381
  if (runResult?.continuationMessages) {
9035
9382
  conv._harnessMessages = runResult.continuationMessages;
9383
+ } else if (runOutcome.shouldRebuildCanonical) {
9384
+ conv._harnessMessages = conv.messages;
9036
9385
  }
9037
9386
  conv._toolResultArchive = childHarness.getToolResultArchive(childConversationId);
9038
9387
  conv.lastActivityAt = Date.now();
@@ -9175,6 +9524,7 @@ ${resultBody}`,
9175
9524
  metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp }
9176
9525
  });
9177
9526
  }
9527
+ conversation._harnessMessages = [...conversation.messages];
9178
9528
  conversation.updatedAt = Date.now();
9179
9529
  await conversationStore.update(conversation);
9180
9530
  if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
@@ -9203,109 +9553,70 @@ ${resultBody}`,
9203
9553
  finished: false
9204
9554
  });
9205
9555
  }
9206
- const historyMessages = isContinuationResume && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
9207
- let assistantResponse = "";
9208
- let latestRunId = "";
9209
- let runContinuation2 = false;
9210
- let runContinuationMessages;
9211
- let runHarnessMessages;
9212
- let runContextTokens = conversation.contextTokens ?? 0;
9213
- let runContextWindow = conversation.contextWindow ?? 0;
9214
- const toolTimeline = [];
9215
- const sections = [];
9216
- let currentTools = [];
9217
- let currentText = "";
9556
+ const historySelection = resolveRunRequest(conversation, {
9557
+ conversationId,
9558
+ messages: conversation.messages,
9559
+ preferContinuation: isContinuationResume
9560
+ });
9561
+ const historyMessages = [...historySelection.messages];
9562
+ console.info(
9563
+ `[poncho][subagent-callback] conversation="${conversationId}" history_source=${historySelection.source}`
9564
+ );
9565
+ let execution;
9218
9566
  try {
9219
- for await (const event of harness.runWithTelemetry({
9220
- task: void 0,
9221
- conversationId,
9222
- parameters: withToolResultArchiveParam({
9223
- __activeConversationId: conversationId,
9224
- __ownerId: conversation.ownerId
9225
- }, conversation),
9226
- messages: historyMessages,
9227
- abortSignal: abortController.signal
9228
- })) {
9229
- if (event.type === "run:started") {
9230
- latestRunId = event.runId;
9231
- const active = activeConversationRuns.get(conversationId);
9232
- if (active) active.runId = event.runId;
9233
- }
9234
- if (event.type === "model:chunk") {
9235
- if (currentTools.length > 0) {
9236
- sections.push({ type: "tools", content: currentTools });
9237
- currentTools = [];
9238
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
9239
- assistantResponse += " ";
9240
- }
9241
- }
9242
- assistantResponse += event.content;
9243
- currentText += event.content;
9244
- }
9245
- if (event.type === "tool:started") {
9246
- if (currentText.length > 0) {
9247
- sections.push({ type: "text", content: currentText });
9248
- currentText = "";
9249
- }
9250
- const toolText = `- start \`${event.tool}\``;
9251
- toolTimeline.push(toolText);
9252
- currentTools.push(toolText);
9253
- }
9254
- if (event.type === "tool:completed") {
9255
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
9256
- toolTimeline.push(toolText);
9257
- currentTools.push(toolText);
9258
- }
9259
- if (event.type === "tool:error") {
9260
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
9261
- toolTimeline.push(toolText);
9262
- currentTools.push(toolText);
9263
- }
9264
- if (event.type === "run:completed") {
9265
- if (assistantResponse.length === 0 && event.result.response) {
9266
- assistantResponse = event.result.response;
9267
- }
9268
- runContextTokens = event.result.contextTokens ?? runContextTokens;
9269
- runContextWindow = event.result.contextWindow ?? runContextWindow;
9270
- if (event.result.continuationMessages) {
9271
- runHarnessMessages = event.result.continuationMessages;
9272
- }
9273
- if (event.result.continuation) {
9274
- runContinuation2 = true;
9275
- if (event.result.continuationMessages) {
9276
- runContinuationMessages = event.result.continuationMessages;
9277
- }
9567
+ execution = await executeConversationTurn({
9568
+ harness,
9569
+ runInput: {
9570
+ task: void 0,
9571
+ conversationId,
9572
+ parameters: withToolResultArchiveParam({
9573
+ __activeConversationId: conversationId,
9574
+ __ownerId: conversation.ownerId
9575
+ }, conversation),
9576
+ messages: historyMessages,
9577
+ abortSignal: abortController.signal
9578
+ },
9579
+ initialContextTokens: conversation.contextTokens ?? 0,
9580
+ initialContextWindow: conversation.contextWindow ?? 0,
9581
+ onEvent: (event) => {
9582
+ if (event.type === "run:started") {
9583
+ const active = activeConversationRuns.get(conversationId);
9584
+ if (active) active.runId = event.runId;
9278
9585
  }
9586
+ broadcastEvent(conversationId, event);
9279
9587
  }
9280
- broadcastEvent(conversationId, event);
9281
- }
9282
- if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
9283
- if (currentText.length > 0) sections.push({ type: "text", content: currentText });
9284
- if (runContinuationMessages || assistantResponse.length > 0 || toolTimeline.length > 0) {
9588
+ });
9589
+ flushTurnDraft(execution.draft);
9590
+ const callbackNeedsContinuation = execution.runContinuation && execution.runContinuationMessages;
9591
+ if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
9285
9592
  const freshConv = await conversationStore.get(conversationId);
9286
9593
  if (freshConv) {
9287
- if (runContinuationMessages) {
9288
- freshConv._continuationMessages = runContinuationMessages;
9594
+ if (callbackNeedsContinuation) {
9595
+ freshConv._continuationMessages = execution.runContinuationMessages;
9289
9596
  } else {
9290
9597
  freshConv._continuationMessages = void 0;
9291
9598
  freshConv.messages.push({
9292
9599
  role: "assistant",
9293
- content: assistantResponse,
9294
- metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
9600
+ content: execution.draft.assistantResponse,
9601
+ metadata: buildAssistantMetadata(execution.draft)
9295
9602
  });
9296
9603
  }
9297
- if (runHarnessMessages) {
9298
- freshConv._harnessMessages = runHarnessMessages;
9604
+ if (callbackNeedsContinuation && execution.runHarnessMessages) {
9605
+ freshConv._harnessMessages = execution.runHarnessMessages;
9606
+ } else if (historySelection.shouldRebuildCanonical) {
9607
+ freshConv._harnessMessages = freshConv.messages;
9608
+ } else {
9609
+ freshConv._harnessMessages = freshConv.messages;
9299
9610
  }
9300
9611
  freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
9301
- freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
9612
+ freshConv.runtimeRunId = execution.latestRunId || freshConv.runtimeRunId;
9302
9613
  freshConv.runningCallbackSince = void 0;
9303
9614
  freshConv.runStatus = "idle";
9304
- if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
9305
- if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
9615
+ if (execution.runContextTokens > 0) freshConv.contextTokens = execution.runContextTokens;
9616
+ if (execution.runContextWindow > 0) freshConv.contextWindow = execution.runContextWindow;
9306
9617
  freshConv.updatedAt = Date.now();
9307
9618
  await conversationStore.update(freshConv);
9308
- if (freshConv.channelMeta && assistantResponse.length > 0) {
9619
+ if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
9309
9620
  const adapter = messagingAdapters.get(freshConv.channelMeta.platform);
9310
9621
  if (adapter) {
9311
9622
  try {
@@ -9314,7 +9625,7 @@ ${resultBody}`,
9314
9625
  channelId: freshConv.channelMeta.channelId,
9315
9626
  platformThreadId: freshConv.channelMeta.platformThreadId
9316
9627
  },
9317
- assistantResponse
9628
+ execution.draft.assistantResponse
9318
9629
  );
9319
9630
  } catch (sendErr) {
9320
9631
  console.error(`[poncho][subagent-callback] Messaging notify failed:`, sendErr instanceof Error ? sendErr.message : sendErr);
@@ -9323,7 +9634,7 @@ ${resultBody}`,
9323
9634
  }
9324
9635
  }
9325
9636
  }
9326
- if (runContinuation2) {
9637
+ if (execution.runContinuation) {
9327
9638
  if (isServerless) {
9328
9639
  const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
9329
9640
  (err) => console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
@@ -9345,10 +9656,15 @@ ${resultBody}`,
9345
9656
  }
9346
9657
  } finally {
9347
9658
  activeConversationRuns.delete(conversationId);
9348
- finishConversationStream(conversationId);
9349
9659
  const hadDeferredTrigger = pendingCallbackNeeded.delete(conversationId);
9350
9660
  const freshConv = await conversationStore.get(conversationId);
9351
9661
  const hasPendingInStore = !!freshConv?.pendingSubagentResults?.length;
9662
+ const hasRunningCallbackChildren = Array.from(activeSubagentRuns.values()).some(
9663
+ (run) => run.parentConversationId === conversationId
9664
+ );
9665
+ if (!hadDeferredTrigger && !hasPendingInStore && !hasRunningCallbackChildren) {
9666
+ finishConversationStream(conversationId);
9667
+ }
9352
9668
  if (hadDeferredTrigger || hasPendingInStore) {
9353
9669
  if (isServerless) {
9354
9670
  selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
@@ -9496,8 +9812,9 @@ ${resultBody}`,
9496
9812
  let runContextTokens = conversation.contextTokens ?? 0;
9497
9813
  let runContextWindow = conversation.contextWindow ?? 0;
9498
9814
  let resumeHarnessMessages;
9499
- const baseMessages = checkpoint.baseMessageCount != null ? conversation.messages.slice(0, checkpoint.baseMessageCount) : [];
9500
- const fullCheckpointMessages = [...baseMessages, ...checkpoint.checkpointMessages];
9815
+ const normalizedCheckpoint = normalizeApprovalCheckpoint(checkpoint, conversation.messages);
9816
+ const baseMessages = normalizedCheckpoint.baseMessageCount != null ? conversation.messages.slice(0, normalizedCheckpoint.baseMessageCount) : [];
9817
+ const fullCheckpointMessages = [...baseMessages, ...normalizedCheckpoint.checkpointMessages];
9501
9818
  let resumeToolResultMsg;
9502
9819
  const lastCpMsg = fullCheckpointMessages[fullCheckpointMessages.length - 1];
9503
9820
  if (lastCpMsg?.role === "assistant") {
@@ -9578,24 +9895,26 @@ ${resultBody}`,
9578
9895
  if (event.type === "tool:approval:checkpoint") {
9579
9896
  const conv = await conversationStore.get(conversationId);
9580
9897
  if (conv) {
9581
- conv.pendingApprovals = event.approvals.map((a) => ({
9582
- approvalId: a.approvalId,
9898
+ conv.pendingApprovals = buildApprovalCheckpoints({
9899
+ approvals: event.approvals,
9583
9900
  runId: latestRunId,
9584
- tool: a.tool,
9585
- toolCallId: a.toolCallId,
9586
- input: a.input,
9587
9901
  checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
9588
9902
  baseMessageCount: 0,
9589
9903
  pendingToolCalls: event.pendingToolCalls
9590
- }));
9904
+ });
9591
9905
  conv.updatedAt = Date.now();
9592
9906
  await conversationStore.update(conv);
9593
9907
  if (conv.channelMeta?.platform === "telegram") {
9594
9908
  const tgAdapter = messagingAdapters.get("telegram");
9595
9909
  if (tgAdapter) {
9910
+ const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
9911
+ conv.channelMeta.platformThreadId,
9912
+ conv.channelMeta.channelId
9913
+ );
9596
9914
  void tgAdapter.sendApprovalRequest(
9597
9915
  conv.channelMeta.channelId,
9598
- event.approvals.map((a) => ({ approvalId: a.approvalId, tool: a.tool, input: a.input }))
9916
+ event.approvals.map((a) => ({ approvalId: a.approvalId, tool: a.tool, input: a.input })),
9917
+ { message_thread_id: messageThreadId }
9599
9918
  ).catch(() => {
9600
9919
  });
9601
9920
  }
@@ -9673,6 +9992,8 @@ ${resultBody}`,
9673
9992
  }
9674
9993
  if (resumeHarnessMessages) {
9675
9994
  conv._harnessMessages = resumeHarnessMessages;
9995
+ } else {
9996
+ conv._harnessMessages = conv.messages;
9676
9997
  }
9677
9998
  conv.runtimeRunId = latestRunId || conv.runtimeRunId;
9678
9999
  conv.pendingApprovals = [];
@@ -9690,7 +10011,6 @@ ${resultBody}`,
9690
10011
  await conversationStore.update(conv);
9691
10012
  }
9692
10013
  }
9693
- finishConversationStream(conversationId);
9694
10014
  activeConversationRuns.delete(conversationId);
9695
10015
  if (latestRunId) {
9696
10016
  runOwners.delete(latestRunId);
@@ -9699,7 +10019,14 @@ ${resultBody}`,
9699
10019
  console.log("[resume-run] complete for", conversationId);
9700
10020
  const hadDeferred = pendingCallbackNeeded.delete(conversationId);
9701
10021
  const postConv = await conversationStore.get(conversationId);
9702
- if (hadDeferred || postConv?.pendingSubagentResults?.length) {
10022
+ const needsResumeCallback = hadDeferred || !!postConv?.pendingSubagentResults?.length;
10023
+ const hasRunningResumeChildren = Array.from(activeSubagentRuns.values()).some(
10024
+ (run) => run.parentConversationId === conversationId
10025
+ );
10026
+ if (!needsResumeCallback && !hasRunningResumeChildren) {
10027
+ finishConversationStream(conversationId);
10028
+ }
10029
+ if (needsResumeCallback) {
9703
10030
  processSubagentCallback(conversationId, true).catch(
9704
10031
  (err) => console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err)
9705
10032
  );
@@ -9726,7 +10053,7 @@ ${resultBody}`,
9726
10053
  };
9727
10054
  await conversationStore.update(existing);
9728
10055
  }
9729
- return { messages: existing._harnessMessages?.length ? existing._harnessMessages : existing.messages };
10056
+ return { messages: loadCanonicalHistory(existing).messages };
9730
10057
  }
9731
10058
  const now = Date.now();
9732
10059
  const channelMeta = meta.channelId ? {
@@ -9748,10 +10075,15 @@ ${resultBody}`,
9748
10075
  return { messages: [] };
9749
10076
  },
9750
10077
  async run(conversationId, input2) {
10078
+ const latestConversation = await conversationStore.get(conversationId);
10079
+ const canonicalHistory = latestConversation ? loadCanonicalHistory(latestConversation) : { messages: [...input2.messages], source: "messages" };
10080
+ const shouldRebuildCanonical = canonicalHistory.source !== "harness";
9751
10081
  const isContinuation = input2.task == null;
9752
- console.log("[messaging-runner] starting run for", conversationId, isContinuation ? "(continuation)" : `task: ${input2.task.slice(0, 80)}`);
9753
- const historyMessages = [...input2.messages];
9754
- const preRunMessages = [...input2.messages];
10082
+ console.log(
10083
+ `[messaging-runner] starting run for ${conversationId} ${isContinuation ? "(continuation)" : `task: ${input2.task.slice(0, 80)}`} history_source=${canonicalHistory.source}`
10084
+ );
10085
+ const historyMessages = [...canonicalHistory.messages];
10086
+ const preRunMessages = [...canonicalHistory.messages];
9755
10087
  const userContent = input2.task;
9756
10088
  const updateConversation = async (patch) => {
9757
10089
  const fresh = await conversationStore.get(conversationId);
@@ -9767,11 +10099,7 @@ ${resultBody}`,
9767
10099
  c.runStatus = "running";
9768
10100
  });
9769
10101
  let latestRunId = "";
9770
- let assistantResponse = "";
9771
- const toolTimeline = [];
9772
- const sections = [];
9773
- let currentTools = [];
9774
- let currentText = "";
10102
+ const draft = createTurnDraftState();
9775
10103
  let checkpointedRun = false;
9776
10104
  let runContextTokens = 0;
9777
10105
  let runContextWindow = 0;
@@ -9780,20 +10108,15 @@ ${resultBody}`,
9780
10108
  let runSteps = 0;
9781
10109
  let runMaxSteps;
9782
10110
  const buildMessages = () => {
9783
- const draftSections = [
9784
- ...sections.map((s) => ({
9785
- type: s.type,
9786
- content: Array.isArray(s.content) ? [...s.content] : s.content
9787
- }))
9788
- ];
9789
- if (currentTools.length > 0) {
9790
- draftSections.push({ type: "tools", content: [...currentTools] });
10111
+ const draftSections = cloneSections(draft.sections);
10112
+ if (draft.currentTools.length > 0) {
10113
+ draftSections.push({ type: "tools", content: [...draft.currentTools] });
9791
10114
  }
9792
- if (currentText.length > 0) {
9793
- draftSections.push({ type: "text", content: currentText });
10115
+ if (draft.currentText.length > 0) {
10116
+ draftSections.push({ type: "text", content: draft.currentText });
9794
10117
  }
9795
10118
  const userTurn = userContent != null ? [{ role: "user", content: userContent }] : [];
9796
- const hasDraftContent = assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
10119
+ const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
9797
10120
  if (!hasDraftContent) {
9798
10121
  return [...historyMessages, ...userTurn];
9799
10122
  }
@@ -9802,16 +10125,13 @@ ${resultBody}`,
9802
10125
  ...userTurn,
9803
10126
  {
9804
10127
  role: "assistant",
9805
- content: assistantResponse,
9806
- metadata: toolTimeline.length > 0 || draftSections.length > 0 ? {
9807
- toolActivity: [...toolTimeline],
9808
- sections: draftSections.length > 0 ? draftSections : void 0
9809
- } : void 0
10128
+ content: draft.assistantResponse,
10129
+ metadata: buildAssistantMetadata(draft, draftSections)
9810
10130
  }
9811
10131
  ];
9812
10132
  };
9813
10133
  const persistDraftAssistantTurn = async () => {
9814
- if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
10134
+ if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
9815
10135
  await updateConversation((c) => {
9816
10136
  c.messages = buildMessages();
9817
10137
  });
@@ -9819,139 +10139,107 @@ ${resultBody}`,
9819
10139
  const runInput = {
9820
10140
  task: input2.task,
9821
10141
  conversationId,
9822
- messages: input2.messages,
10142
+ messages: historyMessages,
9823
10143
  files: input2.files,
9824
- parameters: input2.metadata ? {
9825
- __messaging_platform: input2.metadata.platform,
9826
- __messaging_sender_id: input2.metadata.sender.id,
9827
- __messaging_sender_name: input2.metadata.sender.name ?? "",
9828
- __messaging_thread_id: input2.metadata.threadId
9829
- } : void 0
10144
+ parameters: {
10145
+ ...input2.metadata ? {
10146
+ __messaging_platform: input2.metadata.platform,
10147
+ __messaging_sender_id: input2.metadata.sender.id,
10148
+ __messaging_sender_name: input2.metadata.sender.name ?? "",
10149
+ __messaging_thread_id: input2.metadata.threadId
10150
+ } : {},
10151
+ __activeConversationId: conversationId
10152
+ }
9830
10153
  };
9831
10154
  try {
9832
- for await (const event of harness.runWithTelemetry(runInput)) {
9833
- if (event.type === "run:started") {
9834
- latestRunId = event.runId;
9835
- runOwners.set(event.runId, "local-owner");
9836
- runConversations.set(event.runId, conversationId);
9837
- }
9838
- if (event.type === "model:chunk") {
9839
- if (currentTools.length > 0) {
9840
- sections.push({ type: "tools", content: currentTools });
9841
- currentTools = [];
9842
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
9843
- assistantResponse += " ";
9844
- }
10155
+ const execution = await executeConversationTurn({
10156
+ harness,
10157
+ runInput,
10158
+ onEvent: async (event, eventDraft) => {
10159
+ draft.assistantResponse = eventDraft.assistantResponse;
10160
+ draft.toolTimeline = eventDraft.toolTimeline;
10161
+ draft.sections = eventDraft.sections;
10162
+ draft.currentTools = eventDraft.currentTools;
10163
+ draft.currentText = eventDraft.currentText;
10164
+ if (event.type === "run:started") {
10165
+ latestRunId = event.runId;
10166
+ runOwners.set(event.runId, "local-owner");
10167
+ runConversations.set(event.runId, conversationId);
9845
10168
  }
9846
- assistantResponse += event.content;
9847
- currentText += event.content;
9848
- }
9849
- if (event.type === "tool:started") {
9850
- if (currentText.length > 0) {
9851
- sections.push({ type: "text", content: currentText });
9852
- currentText = "";
10169
+ if (event.type === "step:completed") {
10170
+ await persistDraftAssistantTurn();
9853
10171
  }
9854
- const toolText = `- start \`${event.tool}\``;
9855
- toolTimeline.push(toolText);
9856
- currentTools.push(toolText);
9857
- }
9858
- if (event.type === "tool:completed") {
9859
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
9860
- toolTimeline.push(toolText);
9861
- currentTools.push(toolText);
9862
- }
9863
- if (event.type === "tool:error") {
9864
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
9865
- toolTimeline.push(toolText);
9866
- currentTools.push(toolText);
9867
- }
9868
- if (event.type === "step:completed") {
9869
- await persistDraftAssistantTurn();
9870
- }
9871
- if (event.type === "tool:approval:required") {
9872
- const toolText = `- approval required \`${event.tool}\``;
9873
- toolTimeline.push(toolText);
9874
- currentTools.push(toolText);
9875
- await persistDraftAssistantTurn();
9876
- }
9877
- if (event.type === "tool:approval:checkpoint") {
9878
- await updateConversation((c) => {
9879
- c.messages = buildMessages();
9880
- c.pendingApprovals = event.approvals.map((a) => ({
9881
- approvalId: a.approvalId,
9882
- runId: latestRunId,
9883
- tool: a.tool,
9884
- toolCallId: a.toolCallId,
9885
- input: a.input,
9886
- checkpointMessages: event.checkpointMessages,
9887
- baseMessageCount: historyMessages.length,
9888
- pendingToolCalls: event.pendingToolCalls
9889
- }));
9890
- });
9891
- checkpointedRun = true;
9892
- const conv = await conversationStore.get(conversationId);
9893
- if (conv?.channelMeta?.platform === "telegram") {
9894
- const tgAdapter = messagingAdapters.get("telegram");
9895
- if (tgAdapter) {
9896
- const approvals = event.approvals.map((a) => ({
9897
- approvalId: a.approvalId,
9898
- tool: a.tool,
9899
- input: a.input
9900
- }));
9901
- void tgAdapter.sendApprovalRequest(
9902
- conv.channelMeta.channelId,
9903
- approvals
9904
- ).catch((err) => {
9905
- console.error("[messaging-runner] failed to send Telegram approval request:", err instanceof Error ? err.message : err);
9906
- });
9907
- }
10172
+ if (event.type === "tool:approval:required") {
10173
+ const toolText = `- approval required \`${event.tool}\``;
10174
+ draft.toolTimeline.push(toolText);
10175
+ draft.currentTools.push(toolText);
10176
+ await persistDraftAssistantTurn();
9908
10177
  }
9909
- }
9910
- if (event.type === "compaction:completed") {
9911
- if (event.compactedMessages) {
9912
- historyMessages.length = 0;
9913
- historyMessages.push(...event.compactedMessages);
9914
- const preservedFromHistory = historyMessages.length - 1;
9915
- const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
10178
+ if (event.type === "tool:approval:checkpoint") {
9916
10179
  await updateConversation((c) => {
9917
- const existingHistory = c.compactedHistory ?? [];
9918
- c.compactedHistory = [
9919
- ...existingHistory,
9920
- ...preRunMessages.slice(0, removedCount)
9921
- ];
10180
+ c.messages = buildMessages();
10181
+ c.pendingApprovals = buildApprovalCheckpoints({
10182
+ approvals: event.approvals,
10183
+ runId: latestRunId,
10184
+ checkpointMessages: event.checkpointMessages,
10185
+ baseMessageCount: historyMessages.length,
10186
+ pendingToolCalls: event.pendingToolCalls
10187
+ });
9922
10188
  });
10189
+ checkpointedRun = true;
10190
+ const conv = await conversationStore.get(conversationId);
10191
+ if (conv?.channelMeta?.platform === "telegram") {
10192
+ const tgAdapter = messagingAdapters.get("telegram");
10193
+ if (tgAdapter) {
10194
+ const approvals = event.approvals.map((a) => ({
10195
+ approvalId: a.approvalId,
10196
+ tool: a.tool,
10197
+ input: a.input
10198
+ }));
10199
+ const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
10200
+ conv.channelMeta.platformThreadId,
10201
+ conv.channelMeta.channelId
10202
+ );
10203
+ void tgAdapter.sendApprovalRequest(
10204
+ conv.channelMeta.channelId,
10205
+ approvals,
10206
+ { message_thread_id: messageThreadId }
10207
+ ).catch((err) => {
10208
+ console.error("[messaging-runner] failed to send Telegram approval request:", err instanceof Error ? err.message : err);
10209
+ });
10210
+ }
10211
+ }
9923
10212
  }
9924
- }
9925
- if (event.type === "run:completed") {
9926
- if (assistantResponse.length === 0 && event.result.response) {
9927
- assistantResponse = event.result.response;
9928
- }
9929
- runContinuation2 = event.result.continuation === true;
9930
- if (event.result.continuationMessages) {
9931
- runContinuationMessages = event.result.continuationMessages;
10213
+ if (event.type === "compaction:completed") {
10214
+ if (event.compactedMessages) {
10215
+ historyMessages.length = 0;
10216
+ historyMessages.push(...event.compactedMessages);
10217
+ const preservedFromHistory = historyMessages.length - 1;
10218
+ const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
10219
+ await updateConversation((c) => {
10220
+ const existingHistory = c.compactedHistory ?? [];
10221
+ c.compactedHistory = [
10222
+ ...existingHistory,
10223
+ ...preRunMessages.slice(0, removedCount)
10224
+ ];
10225
+ });
10226
+ }
9932
10227
  }
9933
- runSteps = event.result.steps;
9934
- if (typeof event.result.maxSteps === "number") runMaxSteps = event.result.maxSteps;
9935
- runContextTokens = event.result.contextTokens ?? runContextTokens;
9936
- runContextWindow = event.result.contextWindow ?? runContextWindow;
9937
- }
9938
- if (event.type === "run:error") {
9939
- assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
10228
+ broadcastEvent(conversationId, event);
9940
10229
  }
9941
- broadcastEvent(conversationId, event);
9942
- }
10230
+ });
10231
+ runContinuation2 = execution.runContinuation;
10232
+ runContinuationMessages = execution.runContinuationMessages;
10233
+ runSteps = execution.runSteps;
10234
+ runMaxSteps = execution.runMaxSteps;
10235
+ runContextTokens = execution.runContextTokens;
10236
+ runContextWindow = execution.runContextWindow;
10237
+ latestRunId = execution.latestRunId || latestRunId;
9943
10238
  } catch (err) {
9944
10239
  console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
9945
- assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
9946
- }
9947
- if (currentTools.length > 0) {
9948
- sections.push({ type: "tools", content: currentTools });
9949
- currentTools = [];
9950
- }
9951
- if (currentText.length > 0) {
9952
- sections.push({ type: "text", content: currentText });
9953
- currentText = "";
10240
+ draft.assistantResponse = draft.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
9954
10241
  }
10242
+ flushTurnDraft(draft);
9955
10243
  if (!checkpointedRun) {
9956
10244
  await updateConversation((c) => {
9957
10245
  if (runContinuation2 && runContinuationMessages) {
@@ -9962,6 +10250,10 @@ ${resultBody}`,
9962
10250
  }
9963
10251
  if (runContinuationMessages) {
9964
10252
  c._harnessMessages = runContinuationMessages;
10253
+ } else if (shouldRebuildCanonical) {
10254
+ c._harnessMessages = c.messages;
10255
+ } else {
10256
+ c._harnessMessages = c.messages;
9965
10257
  }
9966
10258
  c.runtimeRunId = latestRunId || c.runtimeRunId;
9967
10259
  c.pendingApprovals = [];
@@ -9971,6 +10263,9 @@ ${resultBody}`,
9971
10263
  });
9972
10264
  } else {
9973
10265
  await updateConversation((c) => {
10266
+ if (shouldRebuildCanonical && !c._harnessMessages?.length) {
10267
+ c._harnessMessages = c.messages;
10268
+ }
9974
10269
  c.runStatus = "idle";
9975
10270
  });
9976
10271
  }
@@ -9979,8 +10274,8 @@ ${resultBody}`,
9979
10274
  runOwners.delete(latestRunId);
9980
10275
  runConversations.delete(latestRunId);
9981
10276
  }
9982
- console.log("[messaging-runner] run complete, response length:", assistantResponse.length, runContinuation2 ? "(continuation)" : "");
9983
- const response = assistantResponse;
10277
+ console.log("[messaging-runner] run complete, response length:", draft.assistantResponse.length, runContinuation2 ? "(continuation)" : "");
10278
+ const response = draft.assistantResponse;
9984
10279
  return {
9985
10280
  response,
9986
10281
  continuation: runContinuation2,
@@ -10113,6 +10408,7 @@ ${resultBody}`,
10113
10408
  async function* runContinuation(conversationId) {
10114
10409
  const conversation = await conversationStore.get(conversationId);
10115
10410
  if (!conversation) return;
10411
+ if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
10116
10412
  if (!conversation._continuationMessages?.length) return;
10117
10413
  if (conversation.runStatus === "running") return;
10118
10414
  const count = (conversation._continuationCount ?? 0) + 1;
@@ -10261,7 +10557,11 @@ ${resultBody}`,
10261
10557
  freshConv._continuationMessages = void 0;
10262
10558
  freshConv._continuationCount = void 0;
10263
10559
  }
10264
- if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
10560
+ if (nextHarnessMessages) {
10561
+ freshConv._harnessMessages = nextHarnessMessages;
10562
+ } else {
10563
+ freshConv._harnessMessages = freshConv.messages;
10564
+ }
10265
10565
  freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
10266
10566
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
10267
10567
  freshConv.pendingApprovals = [];
@@ -10383,6 +10683,8 @@ ${resultBody}`,
10383
10683
  }
10384
10684
  if (runResult?.continuationMessages) {
10385
10685
  conv._harnessMessages = runResult.continuationMessages;
10686
+ } else {
10687
+ conv._harnessMessages = conv.messages;
10386
10688
  }
10387
10689
  conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
10388
10690
  conv.lastActivityAt = Date.now();
@@ -10575,29 +10877,23 @@ ${resultBody}`,
10575
10877
  }
10576
10878
  return;
10577
10879
  }
10578
- const conversations = await conversationStore.list("local-owner");
10579
- let foundConversation;
10580
- let foundApproval;
10581
- for (const conv of conversations) {
10582
- if (!Array.isArray(conv.pendingApprovals)) continue;
10583
- const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
10584
- if (match) {
10585
- foundConversation = conv;
10586
- foundApproval = match;
10587
- break;
10588
- }
10589
- }
10880
+ const found = await findPendingApproval(approvalId, "local-owner");
10881
+ let foundConversation = found?.conversation;
10882
+ let foundApproval = found?.approval;
10590
10883
  if (!foundConversation || !foundApproval) {
10591
10884
  console.warn("[telegram-approval] approval not found:", approvalId);
10592
10885
  return;
10593
10886
  }
10887
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
10594
10888
  await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
10595
10889
  foundApproval.decision = approved ? "approved" : "denied";
10596
10890
  broadcastEvent(
10597
10891
  foundConversation.conversationId,
10598
10892
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
10599
10893
  );
10600
- const allApprovals = foundConversation.pendingApprovals ?? [];
10894
+ const allApprovals = (foundConversation.pendingApprovals ?? []).map(
10895
+ (approval) => normalizeApprovalCheckpoint(approval, foundConversation.messages)
10896
+ );
10601
10897
  const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
10602
10898
  if (!allDecided) {
10603
10899
  await conversationStore.update(foundConversation);
@@ -10946,6 +11242,9 @@ ${resultBody}`,
10946
11242
  return;
10947
11243
  }
10948
11244
  if (requireAuth && session && !hasBearerToken && requiresCsrfValidation && pathname !== "/api/auth/login" && request.headers["x-csrf-token"] !== session?.csrfToken) {
11245
+ console.warn(
11246
+ `[poncho][csrf] blocked request method=${request.method} path="${pathname}" session=${session.sessionId}`
11247
+ );
10949
11248
  writeJson(response, 403, {
10950
11249
  code: "CSRF_ERROR",
10951
11250
  message: "Invalid CSRF token"
@@ -11141,6 +11440,7 @@ data: ${JSON.stringify(frame)}
11141
11440
  const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
11142
11441
  const body = await readRequestBody(request);
11143
11442
  const approved = body.approved === true;
11443
+ const hintedConversationId = typeof body.conversationId === "string" && body.conversationId.trim().length > 0 ? body.conversationId.trim() : void 0;
11144
11444
  const pendingSubagent = pendingSubagentApprovals.get(approvalId);
11145
11445
  if (pendingSubagent) {
11146
11446
  pendingSubagent.checkpoint.decision = approved ? "approved" : "denied";
@@ -11166,18 +11466,23 @@ data: ${JSON.stringify(frame)}
11166
11466
  writeJson(response, 200, { ok: true, approvalId, approved });
11167
11467
  return;
11168
11468
  }
11169
- const conversations = await conversationStore.list(ownerId);
11170
11469
  let foundConversation;
11171
11470
  let foundApproval;
11172
- for (const conv of conversations) {
11173
- if (!Array.isArray(conv.pendingApprovals)) continue;
11174
- const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
11175
- if (match) {
11176
- foundConversation = conv;
11177
- foundApproval = match;
11178
- break;
11471
+ if (hintedConversationId) {
11472
+ const hintedConversation = await conversationStore.get(hintedConversationId);
11473
+ if (hintedConversation && hintedConversation.ownerId === ownerId && Array.isArray(hintedConversation.pendingApprovals)) {
11474
+ const hintedMatch = hintedConversation.pendingApprovals.find((approval) => approval.approvalId === approvalId);
11475
+ if (hintedMatch) {
11476
+ foundConversation = hintedConversation;
11477
+ foundApproval = hintedMatch;
11478
+ }
11179
11479
  }
11180
11480
  }
11481
+ if (!foundConversation || !foundApproval) {
11482
+ const found = await findPendingApproval(approvalId, ownerId);
11483
+ foundConversation = found?.conversation;
11484
+ foundApproval = found?.approval;
11485
+ }
11181
11486
  if (!foundConversation || !foundApproval) {
11182
11487
  writeJson(response, 404, {
11183
11488
  code: "APPROVAL_NOT_FOUND",
@@ -11186,37 +11491,32 @@ data: ${JSON.stringify(frame)}
11186
11491
  return;
11187
11492
  }
11188
11493
  const conversationId = foundConversation.conversationId;
11494
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
11189
11495
  if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
11190
- foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).filter((a) => a.approvalId !== approvalId);
11191
- await conversationStore.update(foundConversation);
11192
- writeJson(response, 404, {
11193
- code: "APPROVAL_NOT_FOUND",
11194
- message: "Approval request is no longer active (no checkpoint data)"
11496
+ writeJson(response, 409, {
11497
+ code: "APPROVAL_NOT_READY",
11498
+ message: "Approval checkpoint is not ready yet. Please retry shortly."
11195
11499
  });
11196
11500
  return;
11197
11501
  }
11198
- let batchDecisions = approvalDecisionTracker.get(conversationId);
11199
- if (!batchDecisions) {
11200
- batchDecisions = /* @__PURE__ */ new Map();
11201
- approvalDecisionTracker.set(conversationId, batchDecisions);
11202
- }
11203
- batchDecisions.set(approvalId, approved);
11204
- foundApproval.decision = approved ? "approved" : "denied";
11502
+ const approvalDecision = approved ? "approved" : "denied";
11503
+ foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).map(
11504
+ (approval) => approval.approvalId === approvalId ? { ...normalizeApprovalCheckpoint(approval, foundConversation.messages), decision: approvalDecision } : normalizeApprovalCheckpoint(approval, foundConversation.messages)
11505
+ );
11506
+ await conversationStore.update(foundConversation);
11205
11507
  broadcastEvent(
11206
11508
  conversationId,
11207
11509
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
11208
11510
  );
11209
- const allApprovals = foundConversation.pendingApprovals ?? [];
11210
- const allDecided = allApprovals.length > 0 && allApprovals.every((a) => batchDecisions.has(a.approvalId));
11511
+ const refreshedConversation = await conversationStore.get(conversationId);
11512
+ const allApprovals = (refreshedConversation?.pendingApprovals ?? []).map(
11513
+ (approval) => normalizeApprovalCheckpoint(approval, refreshedConversation.messages)
11514
+ );
11515
+ const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
11211
11516
  if (!allDecided) {
11212
- await conversationStore.update(foundConversation);
11213
11517
  writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
11214
11518
  return;
11215
11519
  }
11216
- for (const a of allApprovals) {
11217
- const d = batchDecisions.get(a.approvalId);
11218
- if (d != null) a.decision = d ? "approved" : "denied";
11219
- }
11220
11520
  approvalDecisionTracker.delete(conversationId);
11221
11521
  foundConversation.pendingApprovals = [];
11222
11522
  foundConversation.runStatus = "running";
@@ -11421,7 +11721,8 @@ data: ${JSON.stringify(frame)}
11421
11721
  approvalId: a.approvalId,
11422
11722
  runId: a.runId,
11423
11723
  tool: a.tool,
11424
- input: a.input
11724
+ input: a.input,
11725
+ decision: a.decision
11425
11726
  })) : [];
11426
11727
  const subagentPending = [];
11427
11728
  if (!conversation.parentConversationId) {
@@ -11438,25 +11739,14 @@ data: ${JSON.stringify(frame)}
11438
11739
  }
11439
11740
  const activeStream = conversationEventStreams.get(conversationId);
11440
11741
  const hasActiveRun = !!activeStream && !activeStream.finished || conversation.runStatus === "running";
11441
- let hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11442
- (r) => r.parentConversationId === conversationId
11443
- );
11444
- if (!hasRunningSubagents && !conversation.parentConversationId) {
11445
- const summaries = await conversationStore.listSummaries(conversation.ownerId);
11446
- for (const s of summaries) {
11447
- if (s.parentConversationId !== conversationId) continue;
11448
- const c = await conversationStore.get(s.conversationId);
11449
- if (c?.subagentMeta?.status === "running") {
11450
- hasRunningSubagents = true;
11451
- break;
11452
- }
11453
- }
11454
- }
11742
+ const hasRunningSubagents = !conversation.parentConversationId ? await hasRunningSubagentsForParent(conversationId, conversation.ownerId) : false;
11455
11743
  const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults) && conversation.pendingSubagentResults.length > 0;
11456
- const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0;
11744
+ const hasPendingApprovals = Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0;
11745
+ const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0 && !hasPendingApprovals;
11457
11746
  writeJson(response, 200, {
11458
11747
  conversation: {
11459
11748
  ...conversation,
11749
+ messages: conversation.messages.map(normalizeMessageForClient),
11460
11750
  pendingApprovals: storedPending,
11461
11751
  _continuationMessages: void 0,
11462
11752
  _harnessMessages: void 0
@@ -11666,11 +11956,9 @@ data: ${JSON.stringify(frame)}
11666
11956
  eventCount++;
11667
11957
  let sseEvent = event;
11668
11958
  if (sseEvent.type === "run:completed") {
11669
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11670
- (r) => r.parentConversationId === conversationId
11671
- );
11959
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
11672
11960
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
11673
- sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11961
+ sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11674
11962
  }
11675
11963
  try {
11676
11964
  response.write(formatSseEvent(sseEvent));
@@ -11698,7 +11986,7 @@ data: ${JSON.stringify(frame)}
11698
11986
  }
11699
11987
  } else {
11700
11988
  const freshConv = await conversationStore.get(conversationId);
11701
- if (freshConv?._continuationMessages?.length) {
11989
+ if (freshConv?._continuationMessages?.length && (!Array.isArray(freshConv.pendingApprovals) || freshConv.pendingApprovals.length === 0)) {
11702
11990
  doWaitUntil(
11703
11991
  new Promise((r) => setTimeout(r, 3e3)).then(
11704
11992
  () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
@@ -11780,9 +12068,17 @@ data: ${JSON.stringify(frame)}
11780
12068
  Connection: "keep-alive",
11781
12069
  "X-Accel-Buffering": "no"
11782
12070
  });
11783
- const harnessMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
12071
+ const canonicalHistory = resolveRunRequest(conversation, {
12072
+ conversationId,
12073
+ messages: conversation.messages
12074
+ });
12075
+ const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
12076
+ const harnessMessages = [...canonicalHistory.messages];
11784
12077
  const historyMessages = [...conversation.messages];
11785
12078
  const preRunMessages = [...conversation.messages];
12079
+ console.info(
12080
+ `[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`
12081
+ );
11786
12082
  let latestRunId = conversation.runtimeRunId ?? "";
11787
12083
  let assistantResponse = "";
11788
12084
  const toolTimeline = [];
@@ -11977,6 +12273,24 @@ data: ${JSON.stringify(frame)}
11977
12273
  const toolText = `- approval required \`${event.tool}\``;
11978
12274
  toolTimeline.push(toolText);
11979
12275
  currentTools.push(toolText);
12276
+ const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
12277
+ if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
12278
+ conversation.pendingApprovals = [
12279
+ ...existingApprovals,
12280
+ {
12281
+ approvalId: event.approvalId,
12282
+ runId: latestRunId || conversation.runtimeRunId || "",
12283
+ tool: event.tool,
12284
+ toolCallId: void 0,
12285
+ input: event.input ?? {},
12286
+ checkpointMessages: void 0,
12287
+ baseMessageCount: historyMessages.length,
12288
+ pendingToolCalls: []
12289
+ }
12290
+ ];
12291
+ conversation.updatedAt = Date.now();
12292
+ await conversationStore.update(conversation);
12293
+ }
11980
12294
  await persistDraftAssistantTurn();
11981
12295
  }
11982
12296
  if (event.type === "tool:approval:checkpoint") {
@@ -11996,16 +12310,13 @@ data: ${JSON.stringify(frame)}
11996
12310
  metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
11997
12311
  }] : []
11998
12312
  ];
11999
- conversation.pendingApprovals = event.approvals.map((a) => ({
12000
- approvalId: a.approvalId,
12313
+ conversation.pendingApprovals = buildApprovalCheckpoints({
12314
+ approvals: event.approvals,
12001
12315
  runId: latestRunId,
12002
- tool: a.tool,
12003
- toolCallId: a.toolCallId,
12004
- input: a.input,
12005
12316
  checkpointMessages: event.checkpointMessages,
12006
12317
  baseMessageCount: historyMessages.length,
12007
12318
  pendingToolCalls: event.pendingToolCalls
12008
- }));
12319
+ });
12009
12320
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12010
12321
  conversation.updatedAt = Date.now();
12011
12322
  await conversationStore.update(conversation);
@@ -12036,26 +12347,28 @@ data: ${JSON.stringify(frame)}
12036
12347
  conversation._harnessMessages = runContinuationMessages;
12037
12348
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12038
12349
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12039
- conversation.pendingApprovals = [];
12350
+ if (!checkpointedRun) {
12351
+ conversation.pendingApprovals = [];
12352
+ }
12040
12353
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
12041
12354
  if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
12042
12355
  conversation.updatedAt = Date.now();
12043
12356
  await conversationStore.update(conversation);
12044
- doWaitUntil(
12045
- new Promise((r) => setTimeout(r, 3e3)).then(
12046
- () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12047
- )
12048
- );
12357
+ if (!checkpointedRun) {
12358
+ doWaitUntil(
12359
+ new Promise((r) => setTimeout(r, 3e3)).then(
12360
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12361
+ )
12362
+ );
12363
+ }
12049
12364
  }
12050
12365
  }
12051
12366
  await telemetry.emit(event);
12052
12367
  let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
12053
12368
  if (sseEvent.type === "run:completed") {
12054
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
12055
- (r) => r.parentConversationId === conversationId
12056
- );
12369
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
12057
12370
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
12058
- if (hasRunningSubagents) {
12371
+ if (hasPendingSubagents) {
12059
12372
  sseEvent = { ...stripped, pendingSubagents: true };
12060
12373
  } else {
12061
12374
  sseEvent = stripped;
@@ -12092,6 +12405,10 @@ data: ${JSON.stringify(frame)}
12092
12405
  conversation._continuationMessages = void 0;
12093
12406
  if (runHarnessMessages) {
12094
12407
  conversation._harnessMessages = runHarnessMessages;
12408
+ } else if (shouldRebuildCanonical) {
12409
+ conversation._harnessMessages = conversation.messages;
12410
+ } else {
12411
+ conversation._harnessMessages = conversation.messages;
12095
12412
  }
12096
12413
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12097
12414
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
@@ -12126,7 +12443,9 @@ data: ${JSON.stringify(frame)}
12126
12443
  conversation.updatedAt = Date.now();
12127
12444
  await conversationStore.update(conversation);
12128
12445
  }
12129
- await clearPendingApprovalsForConversation(conversationId);
12446
+ if (!checkpointedRun) {
12447
+ await clearPendingApprovalsForConversation(conversationId);
12448
+ }
12130
12449
  return;
12131
12450
  }
12132
12451
  try {
@@ -12171,18 +12490,24 @@ data: ${JSON.stringify(frame)}
12171
12490
  if (active && active.abortController === abortController) {
12172
12491
  activeConversationRuns.delete(conversationId);
12173
12492
  }
12174
- finishConversationStream(conversationId);
12175
12493
  if (latestRunId) {
12176
12494
  runOwners.delete(latestRunId);
12177
12495
  runConversations.delete(latestRunId);
12178
12496
  }
12497
+ const hadDeferred = pendingCallbackNeeded.delete(conversationId);
12498
+ const freshConv = await conversationStore.get(conversationId);
12499
+ const needsCallback = hadDeferred || !!freshConv?.pendingSubagentResults?.length;
12500
+ const hasRunningChildren = Array.from(activeSubagentRuns.values()).some(
12501
+ (run) => run.parentConversationId === conversationId
12502
+ );
12503
+ if (!needsCallback && !hasRunningChildren) {
12504
+ finishConversationStream(conversationId);
12505
+ }
12179
12506
  try {
12180
12507
  response.end();
12181
12508
  } catch {
12182
12509
  }
12183
- const hadDeferred = pendingCallbackNeeded.delete(conversationId);
12184
- const freshConv = await conversationStore.get(conversationId);
12185
- if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
12510
+ if (needsCallback) {
12186
12511
  processSubagentCallback(conversationId, true).catch(
12187
12512
  (err) => console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err)
12188
12513
  );
@@ -12239,43 +12564,41 @@ data: ${JSON.stringify(frame)}
12239
12564
  if (!conv) continue;
12240
12565
  const task = `[Scheduled: ${jobName}]
12241
12566
  ${cronJob.task}`;
12242
- const historyMessages = conv._harnessMessages?.length ? [...conv._harnessMessages] : [...conv.messages];
12567
+ const historySelection = resolveRunRequest(conv, {
12568
+ conversationId: conv.conversationId,
12569
+ messages: conv.messages
12570
+ });
12571
+ const historyMessages = [...historySelection.messages];
12243
12572
  try {
12244
- let assistantResponse = "";
12245
- let steps = 0;
12246
- let cronHarnessMessages;
12247
- for await (const event of harness.runWithTelemetry({
12248
- task,
12249
- conversationId: conv.conversationId,
12250
- parameters: withToolResultArchiveParam(
12251
- { __activeConversationId: conv.conversationId },
12252
- conv
12253
- ),
12254
- messages: historyMessages
12255
- })) {
12256
- if (event.type === "model:chunk") {
12257
- assistantResponse += event.content;
12258
- }
12259
- if (event.type === "run:completed") {
12260
- steps = event.result.steps;
12261
- if (!assistantResponse && event.result.response) {
12262
- assistantResponse = event.result.response;
12263
- }
12264
- if (event.result.continuationMessages) {
12265
- cronHarnessMessages = event.result.continuationMessages;
12266
- }
12573
+ const execution = await executeConversationTurn({
12574
+ harness,
12575
+ runInput: {
12576
+ task,
12577
+ conversationId: conv.conversationId,
12578
+ parameters: withToolResultArchiveParam(
12579
+ { __activeConversationId: conv.conversationId },
12580
+ conv
12581
+ ),
12582
+ messages: historyMessages
12583
+ },
12584
+ onEvent: async (event) => {
12585
+ await telemetry.emit(event);
12267
12586
  }
12268
- await telemetry.emit(event);
12269
- }
12587
+ });
12588
+ const assistantResponse = execution.draft.assistantResponse;
12270
12589
  conv.messages = [
12271
12590
  ...historyMessages,
12272
12591
  { role: "user", content: task },
12273
12592
  ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12274
12593
  ];
12275
- if (cronHarnessMessages) {
12276
- conv._harnessMessages = cronHarnessMessages;
12594
+ if (execution.runHarnessMessages) {
12595
+ conv._harnessMessages = execution.runHarnessMessages;
12596
+ } else if (historySelection.shouldRebuildCanonical) {
12597
+ conv._harnessMessages = conv.messages;
12277
12598
  }
12278
12599
  conv._toolResultArchive = harness.getToolResultArchive(conv.conversationId);
12600
+ if (execution.runContextTokens > 0) conv.contextTokens = execution.runContextTokens;
12601
+ if (execution.runContextWindow > 0) conv.contextWindow = execution.runContextWindow;
12279
12602
  conv.updatedAt = Date.now();
12280
12603
  await conversationStore.update(conv);
12281
12604
  if (assistantResponse) {
@@ -12291,7 +12614,7 @@ ${cronJob.task}`;
12291
12614
  console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
12292
12615
  }
12293
12616
  }
12294
- chatResults.push({ chatId, status: "completed", steps });
12617
+ chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
12295
12618
  } catch (runError) {
12296
12619
  chatResults.push({ chatId, status: "error" });
12297
12620
  console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
@@ -12540,86 +12863,30 @@ var startDevServer = async (port, options) => {
12540
12863
  const { Cron } = await import("croner");
12541
12864
  let activeJobs = [];
12542
12865
  const runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
12543
- let assistantResponse = "";
12544
- let steps = 0;
12545
- let contextTokens = 0;
12546
- let contextWindow = 0;
12547
- let harnessMessages;
12548
- const toolTimeline = [];
12549
- const sections = [];
12550
- let currentTools = [];
12551
- let currentText = "";
12552
- for await (const event of harnessRef.runWithTelemetry({
12553
- task,
12554
- conversationId,
12555
- parameters: {
12556
- __activeConversationId: conversationId,
12557
- [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
12866
+ const execution = await executeConversationTurn({
12867
+ harness: harnessRef,
12868
+ runInput: {
12869
+ task,
12870
+ conversationId,
12871
+ parameters: {
12872
+ __activeConversationId: conversationId,
12873
+ [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
12874
+ },
12875
+ messages: historyMessages
12558
12876
  },
12559
- messages: historyMessages
12560
- })) {
12561
- onEvent?.(event);
12562
- if (event.type === "model:chunk") {
12563
- if (currentTools.length > 0) {
12564
- sections.push({ type: "tools", content: currentTools });
12565
- currentTools = [];
12566
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
12567
- assistantResponse += " ";
12568
- }
12569
- }
12570
- assistantResponse += event.content;
12571
- currentText += event.content;
12572
- }
12573
- if (event.type === "tool:started") {
12574
- if (currentText.length > 0) {
12575
- sections.push({ type: "text", content: currentText });
12576
- currentText = "";
12577
- }
12578
- const toolText = `- start \`${event.tool}\``;
12579
- toolTimeline.push(toolText);
12580
- currentTools.push(toolText);
12581
- }
12582
- if (event.type === "tool:completed") {
12583
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
12584
- toolTimeline.push(toolText);
12585
- currentTools.push(toolText);
12586
- }
12587
- if (event.type === "tool:error") {
12588
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
12589
- toolTimeline.push(toolText);
12590
- currentTools.push(toolText);
12591
- }
12592
- if (event.type === "run:completed") {
12593
- steps = event.result.steps;
12594
- contextTokens = event.result.contextTokens ?? 0;
12595
- contextWindow = event.result.contextWindow ?? 0;
12596
- if (event.result.continuationMessages) {
12597
- harnessMessages = event.result.continuationMessages;
12598
- }
12599
- if (!assistantResponse && event.result.response) {
12600
- assistantResponse = event.result.response;
12601
- }
12602
- }
12603
- }
12604
- if (currentTools.length > 0) {
12605
- sections.push({ type: "tools", content: currentTools });
12606
- }
12607
- if (currentText.length > 0) {
12608
- sections.push({ type: "text", content: currentText });
12609
- }
12610
- const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
12611
- const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
12612
- toolActivity: [...toolTimeline],
12613
- sections: sections.length > 0 ? sections : void 0
12614
- } : void 0;
12877
+ onEvent
12878
+ });
12879
+ flushTurnDraft(execution.draft);
12880
+ const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
12881
+ const assistantMetadata = buildAssistantMetadata(execution.draft);
12615
12882
  return {
12616
- response: assistantResponse,
12617
- steps,
12883
+ response: execution.draft.assistantResponse,
12884
+ steps: execution.runSteps,
12618
12885
  assistantMetadata,
12619
12886
  hasContent,
12620
- contextTokens,
12621
- contextWindow,
12622
- harnessMessages,
12887
+ contextTokens: execution.runContextTokens,
12888
+ contextWindow: execution.runContextWindow,
12889
+ harnessMessages: execution.runHarnessMessages,
12623
12890
  toolResultArchive: harnessRef.getToolResultArchive(conversationId)
12624
12891
  };
12625
12892
  };
@@ -12680,7 +12947,11 @@ var startDevServer = async (port, options) => {
12680
12947
  if (!conversation) continue;
12681
12948
  const task = `[Scheduled: ${jobName}]
12682
12949
  ${config.task}`;
12683
- const historyMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
12950
+ const historySelection = resolveRunRequest(conversation, {
12951
+ conversationId: conversation.conversationId,
12952
+ messages: conversation.messages
12953
+ });
12954
+ const historyMessages = [...historySelection.messages];
12684
12955
  const convId = conversation.conversationId;
12685
12956
  activeRuns?.set(convId, {
12686
12957
  ownerId: "local-owner",
@@ -12703,6 +12974,8 @@ ${config.task}`;
12703
12974
  freshConv.messages = buildCronMessages(task, historyMessages, result);
12704
12975
  if (result.harnessMessages) {
12705
12976
  freshConv._harnessMessages = result.harnessMessages;
12977
+ } else if (historySelection.shouldRebuildCanonical) {
12978
+ freshConv._harnessMessages = freshConv.messages;
12706
12979
  }
12707
12980
  if (result.toolResultArchive) {
12708
12981
  freshConv._toolResultArchive = result.toolResultArchive;
@@ -12920,7 +13193,7 @@ var runInteractive = async (workingDir, params) => {
12920
13193
  await harness.initialize();
12921
13194
  const identity = await ensureAgentIdentity2(workingDir);
12922
13195
  try {
12923
- const { runInteractiveInk } = await import("./run-interactive-ink-GD3IRICQ.js");
13196
+ const { runInteractiveInk } = await import("./run-interactive-ink-VGKSZJDO.js");
12924
13197
  await runInteractiveInk({
12925
13198
  harness,
12926
13199
  params,
@@ -13654,6 +13927,7 @@ export {
13654
13927
  inferConversationTitle,
13655
13928
  consumeFirstRunIntro,
13656
13929
  resolveHarnessEnvironment,
13930
+ __internalRunOrchestration,
13657
13931
  initProject,
13658
13932
  updateAgentGuidance,
13659
13933
  createRequestHandler,