@poncho-ai/cli 0.32.4 → 0.32.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: [] };
@@ -7546,6 +7615,209 @@ var normalizeMessageForClient = (message) => {
7546
7615
  }
7547
7616
  return message;
7548
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
+ };
7549
7821
  var AGENT_TEMPLATE = (name, id, options) => `---
7550
7822
  name: ${name}
7551
7823
  id: ${id}
@@ -8729,6 +9001,60 @@ data: ${JSON.stringify(statusPayload)}
8729
9001
  const activeSubagentRuns = /* @__PURE__ */ new Map();
8730
9002
  const pendingSubagentApprovals = /* @__PURE__ */ new Map();
8731
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
+ };
8732
9058
  const getSubagentDepth = async (conversationId) => {
8733
9059
  let depth = 0;
8734
9060
  let current = await conversationStore.get(conversationId);
@@ -8772,7 +9098,9 @@ data: ${JSON.stringify(statusPayload)}
8772
9098
  const resumeSubagentFromCheckpoint = async (subagentId) => {
8773
9099
  const conv = await conversationStore.get(subagentId);
8774
9100
  if (!conv || !conv.parentConversationId) return;
8775
- const allApprovals = conv.pendingApprovals ?? [];
9101
+ const allApprovals = (conv.pendingApprovals ?? []).map(
9102
+ (approval) => normalizeApprovalCheckpoint(approval, conv.messages)
9103
+ );
8776
9104
  if (allApprovals.length === 0) return;
8777
9105
  const allDecided = allApprovals.every((a) => a.decision != null);
8778
9106
  if (!allDecided) return;
@@ -8843,7 +9171,11 @@ data: ${JSON.stringify(statusPayload)}
8843
9171
  if (conversation.subagentMeta?.status === "stopped") return;
8844
9172
  conversation.lastActivityAt = Date.now();
8845
9173
  await conversationStore.update(conversation);
8846
- 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];
8847
9179
  for await (const event of childHarness.runWithTelemetry({
8848
9180
  task,
8849
9181
  conversationId: childConversationId,
@@ -8905,16 +9237,13 @@ data: ${JSON.stringify(statusPayload)}
8905
9237
  if (event.type === "tool:approval:checkpoint") {
8906
9238
  const cpConv = await conversationStore.get(childConversationId);
8907
9239
  if (cpConv) {
8908
- const allCpData = event.approvals.map((a) => ({
8909
- approvalId: a.approvalId,
9240
+ const allCpData = buildApprovalCheckpoints({
9241
+ approvals: event.approvals,
8910
9242
  runId: latestRunId,
8911
- tool: a.tool,
8912
- toolCallId: a.toolCallId,
8913
- input: a.input,
8914
9243
  checkpointMessages: [...harnessMessages, ...event.checkpointMessages],
8915
9244
  baseMessageCount: 0,
8916
9245
  pendingToolCalls: event.pendingToolCalls
8917
- }));
9246
+ });
8918
9247
  cpConv.pendingApprovals = allCpData;
8919
9248
  cpConv.updatedAt = Date.now();
8920
9249
  await conversationStore.update(cpConv);
@@ -8929,7 +9258,7 @@ data: ${JSON.stringify(statusPayload)}
8929
9258
  });
8930
9259
  }
8931
9260
  });
8932
- const checkpointRef = allCpData[0];
9261
+ const checkpointRef = normalizeApprovalCheckpoint(allCpData[0], [...harnessMessages]);
8933
9262
  const toolContext = {
8934
9263
  runId: checkpointRef.runId,
8935
9264
  agentId: identity.id,
@@ -9051,6 +9380,8 @@ data: ${JSON.stringify(statusPayload)}
9051
9380
  }
9052
9381
  if (runResult?.continuationMessages) {
9053
9382
  conv._harnessMessages = runResult.continuationMessages;
9383
+ } else if (runOutcome.shouldRebuildCanonical) {
9384
+ conv._harnessMessages = conv.messages;
9054
9385
  }
9055
9386
  conv._toolResultArchive = childHarness.getToolResultArchive(childConversationId);
9056
9387
  conv.lastActivityAt = Date.now();
@@ -9193,6 +9524,7 @@ ${resultBody}`,
9193
9524
  metadata: { _subagentCallback: true, subagentId: pr.subagentId, task: pr.task, timestamp: pr.timestamp }
9194
9525
  });
9195
9526
  }
9527
+ conversation._harnessMessages = [...conversation.messages];
9196
9528
  conversation.updatedAt = Date.now();
9197
9529
  await conversationStore.update(conversation);
9198
9530
  if (callbackCount > MAX_SUBAGENT_CALLBACK_COUNT) {
@@ -9221,109 +9553,70 @@ ${resultBody}`,
9221
9553
  finished: false
9222
9554
  });
9223
9555
  }
9224
- const historyMessages = isContinuationResume && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
9225
- let assistantResponse = "";
9226
- let latestRunId = "";
9227
- let runContinuation2 = false;
9228
- let runContinuationMessages;
9229
- let runHarnessMessages;
9230
- let runContextTokens = conversation.contextTokens ?? 0;
9231
- let runContextWindow = conversation.contextWindow ?? 0;
9232
- const toolTimeline = [];
9233
- const sections = [];
9234
- let currentTools = [];
9235
- 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;
9236
9566
  try {
9237
- for await (const event of harness.runWithTelemetry({
9238
- task: void 0,
9239
- conversationId,
9240
- parameters: withToolResultArchiveParam({
9241
- __activeConversationId: conversationId,
9242
- __ownerId: conversation.ownerId
9243
- }, conversation),
9244
- messages: historyMessages,
9245
- abortSignal: abortController.signal
9246
- })) {
9247
- if (event.type === "run:started") {
9248
- latestRunId = event.runId;
9249
- const active = activeConversationRuns.get(conversationId);
9250
- if (active) active.runId = event.runId;
9251
- }
9252
- if (event.type === "model:chunk") {
9253
- if (currentTools.length > 0) {
9254
- sections.push({ type: "tools", content: currentTools });
9255
- currentTools = [];
9256
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
9257
- assistantResponse += " ";
9258
- }
9259
- }
9260
- assistantResponse += event.content;
9261
- currentText += event.content;
9262
- }
9263
- if (event.type === "tool:started") {
9264
- if (currentText.length > 0) {
9265
- sections.push({ type: "text", content: currentText });
9266
- currentText = "";
9267
- }
9268
- const toolText = `- start \`${event.tool}\``;
9269
- toolTimeline.push(toolText);
9270
- currentTools.push(toolText);
9271
- }
9272
- if (event.type === "tool:completed") {
9273
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
9274
- toolTimeline.push(toolText);
9275
- currentTools.push(toolText);
9276
- }
9277
- if (event.type === "tool:error") {
9278
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
9279
- toolTimeline.push(toolText);
9280
- currentTools.push(toolText);
9281
- }
9282
- if (event.type === "run:completed") {
9283
- if (assistantResponse.length === 0 && event.result.response) {
9284
- assistantResponse = event.result.response;
9285
- }
9286
- runContextTokens = event.result.contextTokens ?? runContextTokens;
9287
- runContextWindow = event.result.contextWindow ?? runContextWindow;
9288
- if (event.result.continuationMessages) {
9289
- runHarnessMessages = event.result.continuationMessages;
9290
- }
9291
- if (event.result.continuation) {
9292
- runContinuation2 = true;
9293
- if (event.result.continuationMessages) {
9294
- runContinuationMessages = event.result.continuationMessages;
9295
- }
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;
9296
9585
  }
9586
+ broadcastEvent(conversationId, event);
9297
9587
  }
9298
- broadcastEvent(conversationId, event);
9299
- }
9300
- if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
9301
- if (currentText.length > 0) sections.push({ type: "text", content: currentText });
9302
- 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) {
9303
9592
  const freshConv = await conversationStore.get(conversationId);
9304
9593
  if (freshConv) {
9305
- if (runContinuationMessages) {
9306
- freshConv._continuationMessages = runContinuationMessages;
9594
+ if (callbackNeedsContinuation) {
9595
+ freshConv._continuationMessages = execution.runContinuationMessages;
9307
9596
  } else {
9308
9597
  freshConv._continuationMessages = void 0;
9309
9598
  freshConv.messages.push({
9310
9599
  role: "assistant",
9311
- content: assistantResponse,
9312
- 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)
9313
9602
  });
9314
9603
  }
9315
- if (runHarnessMessages) {
9316
- 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;
9317
9610
  }
9318
9611
  freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
9319
- freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
9612
+ freshConv.runtimeRunId = execution.latestRunId || freshConv.runtimeRunId;
9320
9613
  freshConv.runningCallbackSince = void 0;
9321
9614
  freshConv.runStatus = "idle";
9322
- if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
9323
- if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
9615
+ if (execution.runContextTokens > 0) freshConv.contextTokens = execution.runContextTokens;
9616
+ if (execution.runContextWindow > 0) freshConv.contextWindow = execution.runContextWindow;
9324
9617
  freshConv.updatedAt = Date.now();
9325
9618
  await conversationStore.update(freshConv);
9326
- if (freshConv.channelMeta && assistantResponse.length > 0) {
9619
+ if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
9327
9620
  const adapter = messagingAdapters.get(freshConv.channelMeta.platform);
9328
9621
  if (adapter) {
9329
9622
  try {
@@ -9332,7 +9625,7 @@ ${resultBody}`,
9332
9625
  channelId: freshConv.channelMeta.channelId,
9333
9626
  platformThreadId: freshConv.channelMeta.platformThreadId
9334
9627
  },
9335
- assistantResponse
9628
+ execution.draft.assistantResponse
9336
9629
  );
9337
9630
  } catch (sendErr) {
9338
9631
  console.error(`[poncho][subagent-callback] Messaging notify failed:`, sendErr instanceof Error ? sendErr.message : sendErr);
@@ -9341,7 +9634,7 @@ ${resultBody}`,
9341
9634
  }
9342
9635
  }
9343
9636
  }
9344
- if (runContinuation2) {
9637
+ if (execution.runContinuation) {
9345
9638
  if (isServerless) {
9346
9639
  const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
9347
9640
  (err) => console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
@@ -9363,10 +9656,15 @@ ${resultBody}`,
9363
9656
  }
9364
9657
  } finally {
9365
9658
  activeConversationRuns.delete(conversationId);
9366
- finishConversationStream(conversationId);
9367
9659
  const hadDeferredTrigger = pendingCallbackNeeded.delete(conversationId);
9368
9660
  const freshConv = await conversationStore.get(conversationId);
9369
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
+ }
9370
9668
  if (hadDeferredTrigger || hasPendingInStore) {
9371
9669
  if (isServerless) {
9372
9670
  selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
@@ -9514,8 +9812,9 @@ ${resultBody}`,
9514
9812
  let runContextTokens = conversation.contextTokens ?? 0;
9515
9813
  let runContextWindow = conversation.contextWindow ?? 0;
9516
9814
  let resumeHarnessMessages;
9517
- const baseMessages = checkpoint.baseMessageCount != null ? conversation.messages.slice(0, checkpoint.baseMessageCount) : [];
9518
- 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];
9519
9818
  let resumeToolResultMsg;
9520
9819
  const lastCpMsg = fullCheckpointMessages[fullCheckpointMessages.length - 1];
9521
9820
  if (lastCpMsg?.role === "assistant") {
@@ -9596,24 +9895,26 @@ ${resultBody}`,
9596
9895
  if (event.type === "tool:approval:checkpoint") {
9597
9896
  const conv = await conversationStore.get(conversationId);
9598
9897
  if (conv) {
9599
- conv.pendingApprovals = event.approvals.map((a) => ({
9600
- approvalId: a.approvalId,
9898
+ conv.pendingApprovals = buildApprovalCheckpoints({
9899
+ approvals: event.approvals,
9601
9900
  runId: latestRunId,
9602
- tool: a.tool,
9603
- toolCallId: a.toolCallId,
9604
- input: a.input,
9605
9901
  checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
9606
9902
  baseMessageCount: 0,
9607
9903
  pendingToolCalls: event.pendingToolCalls
9608
- }));
9904
+ });
9609
9905
  conv.updatedAt = Date.now();
9610
9906
  await conversationStore.update(conv);
9611
9907
  if (conv.channelMeta?.platform === "telegram") {
9612
9908
  const tgAdapter = messagingAdapters.get("telegram");
9613
9909
  if (tgAdapter) {
9910
+ const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
9911
+ conv.channelMeta.platformThreadId,
9912
+ conv.channelMeta.channelId
9913
+ );
9614
9914
  void tgAdapter.sendApprovalRequest(
9615
9915
  conv.channelMeta.channelId,
9616
- 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 }
9617
9918
  ).catch(() => {
9618
9919
  });
9619
9920
  }
@@ -9691,6 +9992,8 @@ ${resultBody}`,
9691
9992
  }
9692
9993
  if (resumeHarnessMessages) {
9693
9994
  conv._harnessMessages = resumeHarnessMessages;
9995
+ } else {
9996
+ conv._harnessMessages = conv.messages;
9694
9997
  }
9695
9998
  conv.runtimeRunId = latestRunId || conv.runtimeRunId;
9696
9999
  conv.pendingApprovals = [];
@@ -9708,7 +10011,6 @@ ${resultBody}`,
9708
10011
  await conversationStore.update(conv);
9709
10012
  }
9710
10013
  }
9711
- finishConversationStream(conversationId);
9712
10014
  activeConversationRuns.delete(conversationId);
9713
10015
  if (latestRunId) {
9714
10016
  runOwners.delete(latestRunId);
@@ -9717,14 +10019,20 @@ ${resultBody}`,
9717
10019
  console.log("[resume-run] complete for", conversationId);
9718
10020
  const hadDeferred = pendingCallbackNeeded.delete(conversationId);
9719
10021
  const postConv = await conversationStore.get(conversationId);
9720
- 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) {
9721
10030
  processSubagentCallback(conversationId, true).catch(
9722
10031
  (err) => console.error(`[poncho][subagent-callback] Post-resume callback failed:`, err instanceof Error ? err.message : err)
9723
10032
  );
9724
10033
  }
9725
10034
  };
9726
10035
  const messagingRoutes = /* @__PURE__ */ new Map();
9727
- const messagingRunQueues = /* @__PURE__ */ new Map();
9728
10036
  const messagingRouteRegistrar = (method, path, routeHandler) => {
9729
10037
  let byMethod = messagingRoutes.get(path);
9730
10038
  if (!byMethod) {
@@ -9745,7 +10053,7 @@ ${resultBody}`,
9745
10053
  };
9746
10054
  await conversationStore.update(existing);
9747
10055
  }
9748
- return { messages: existing._harnessMessages?.length ? existing._harnessMessages : existing.messages };
10056
+ return { messages: loadCanonicalHistory(existing).messages };
9749
10057
  }
9750
10058
  const now = Date.now();
9751
10059
  const channelMeta = meta.channelId ? {
@@ -9767,159 +10075,116 @@ ${resultBody}`,
9767
10075
  return { messages: [] };
9768
10076
  },
9769
10077
  async run(conversationId, input2) {
9770
- const previous = messagingRunQueues.get(conversationId) ?? Promise.resolve();
9771
- let releaseQueue;
9772
- const current = new Promise((resolve4) => {
9773
- releaseQueue = resolve4;
10078
+ const latestConversation = await conversationStore.get(conversationId);
10079
+ const canonicalHistory = latestConversation ? loadCanonicalHistory(latestConversation) : { messages: [...input2.messages], source: "messages" };
10080
+ const shouldRebuildCanonical = canonicalHistory.source !== "harness";
10081
+ const isContinuation = input2.task == null;
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];
10087
+ const userContent = input2.task;
10088
+ const updateConversation = async (patch) => {
10089
+ const fresh = await conversationStore.get(conversationId);
10090
+ if (!fresh) return;
10091
+ patch(fresh);
10092
+ fresh.updatedAt = Date.now();
10093
+ await conversationStore.update(fresh);
10094
+ };
10095
+ await updateConversation((c) => {
10096
+ if (!isContinuation) {
10097
+ c.messages = [...historyMessages, { role: "user", content: userContent }];
10098
+ }
10099
+ c.runStatus = "running";
9774
10100
  });
9775
- const chained = previous.then(() => current);
9776
- messagingRunQueues.set(conversationId, chained);
9777
- await previous;
9778
- try {
9779
- const latestConversation = await conversationStore.get(conversationId);
9780
- const latestMessages = latestConversation ? latestConversation._harnessMessages?.length ? [...latestConversation._harnessMessages] : [...latestConversation.messages] : [...input2.messages];
9781
- const isContinuation = input2.task == null;
9782
- console.log("[messaging-runner] starting run for", conversationId, isContinuation ? "(continuation)" : `task: ${input2.task.slice(0, 80)}`);
9783
- const historyMessages = [...latestMessages];
9784
- const preRunMessages = [...latestMessages];
9785
- const userContent = input2.task;
9786
- const updateConversation = async (patch) => {
9787
- const fresh = await conversationStore.get(conversationId);
9788
- if (!fresh) return;
9789
- patch(fresh);
9790
- fresh.updatedAt = Date.now();
9791
- await conversationStore.update(fresh);
9792
- };
9793
- await updateConversation((c) => {
9794
- if (!isContinuation) {
9795
- c.messages = [...historyMessages, { role: "user", content: userContent }];
10101
+ let latestRunId = "";
10102
+ const draft = createTurnDraftState();
10103
+ let checkpointedRun = false;
10104
+ let runContextTokens = 0;
10105
+ let runContextWindow = 0;
10106
+ let runContinuation2 = false;
10107
+ let runContinuationMessages;
10108
+ let runSteps = 0;
10109
+ let runMaxSteps;
10110
+ const buildMessages = () => {
10111
+ const draftSections = cloneSections(draft.sections);
10112
+ if (draft.currentTools.length > 0) {
10113
+ draftSections.push({ type: "tools", content: [...draft.currentTools] });
10114
+ }
10115
+ if (draft.currentText.length > 0) {
10116
+ draftSections.push({ type: "text", content: draft.currentText });
10117
+ }
10118
+ const userTurn = userContent != null ? [{ role: "user", content: userContent }] : [];
10119
+ const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
10120
+ if (!hasDraftContent) {
10121
+ return [...historyMessages, ...userTurn];
10122
+ }
10123
+ return [
10124
+ ...historyMessages,
10125
+ ...userTurn,
10126
+ {
10127
+ role: "assistant",
10128
+ content: draft.assistantResponse,
10129
+ metadata: buildAssistantMetadata(draft, draftSections)
9796
10130
  }
9797
- c.runStatus = "running";
10131
+ ];
10132
+ };
10133
+ const persistDraftAssistantTurn = async () => {
10134
+ if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
10135
+ await updateConversation((c) => {
10136
+ c.messages = buildMessages();
9798
10137
  });
9799
- let latestRunId = "";
9800
- let assistantResponse = "";
9801
- const toolTimeline = [];
9802
- const sections = [];
9803
- let currentTools = [];
9804
- let currentText = "";
9805
- let checkpointedRun = false;
9806
- let runContextTokens = 0;
9807
- let runContextWindow = 0;
9808
- let runContinuation2 = false;
9809
- let runContinuationMessages;
9810
- let runSteps = 0;
9811
- let runMaxSteps;
9812
- const buildMessages = () => {
9813
- const draftSections = [
9814
- ...sections.map((s) => ({
9815
- type: s.type,
9816
- content: Array.isArray(s.content) ? [...s.content] : s.content
9817
- }))
9818
- ];
9819
- if (currentTools.length > 0) {
9820
- draftSections.push({ type: "tools", content: [...currentTools] });
9821
- }
9822
- if (currentText.length > 0) {
9823
- draftSections.push({ type: "text", content: currentText });
9824
- }
9825
- const userTurn = userContent != null ? [{ role: "user", content: userContent }] : [];
9826
- const hasDraftContent = assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
9827
- if (!hasDraftContent) {
9828
- return [...historyMessages, ...userTurn];
9829
- }
9830
- return [
9831
- ...historyMessages,
9832
- ...userTurn,
9833
- {
9834
- role: "assistant",
9835
- content: assistantResponse,
9836
- metadata: toolTimeline.length > 0 || draftSections.length > 0 ? {
9837
- toolActivity: [...toolTimeline],
9838
- sections: draftSections.length > 0 ? draftSections : void 0
9839
- } : void 0
9840
- }
9841
- ];
9842
- };
9843
- const persistDraftAssistantTurn = async () => {
9844
- if (assistantResponse.length === 0 && toolTimeline.length === 0) return;
9845
- await updateConversation((c) => {
9846
- c.messages = buildMessages();
9847
- });
9848
- };
9849
- const runInput = {
9850
- task: input2.task,
9851
- conversationId,
9852
- messages: historyMessages,
9853
- files: input2.files,
9854
- parameters: {
9855
- ...input2.metadata ? {
9856
- __messaging_platform: input2.metadata.platform,
9857
- __messaging_sender_id: input2.metadata.sender.id,
9858
- __messaging_sender_name: input2.metadata.sender.name ?? "",
9859
- __messaging_thread_id: input2.metadata.threadId
9860
- } : {},
9861
- __activeConversationId: conversationId
9862
- }
9863
- };
9864
- try {
9865
- for await (const event of harness.runWithTelemetry(runInput)) {
10138
+ };
10139
+ const runInput = {
10140
+ task: input2.task,
10141
+ conversationId,
10142
+ messages: historyMessages,
10143
+ files: input2.files,
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
+ }
10153
+ };
10154
+ try {
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;
9866
10164
  if (event.type === "run:started") {
9867
10165
  latestRunId = event.runId;
9868
10166
  runOwners.set(event.runId, "local-owner");
9869
10167
  runConversations.set(event.runId, conversationId);
9870
10168
  }
9871
- if (event.type === "model:chunk") {
9872
- if (currentTools.length > 0) {
9873
- sections.push({ type: "tools", content: currentTools });
9874
- currentTools = [];
9875
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
9876
- assistantResponse += " ";
9877
- }
9878
- }
9879
- assistantResponse += event.content;
9880
- currentText += event.content;
9881
- }
9882
- if (event.type === "tool:started") {
9883
- if (currentText.length > 0) {
9884
- sections.push({ type: "text", content: currentText });
9885
- currentText = "";
9886
- }
9887
- const toolText = `- start \`${event.tool}\``;
9888
- toolTimeline.push(toolText);
9889
- currentTools.push(toolText);
9890
- }
9891
- if (event.type === "tool:completed") {
9892
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
9893
- toolTimeline.push(toolText);
9894
- currentTools.push(toolText);
9895
- }
9896
- if (event.type === "tool:error") {
9897
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
9898
- toolTimeline.push(toolText);
9899
- currentTools.push(toolText);
9900
- }
9901
10169
  if (event.type === "step:completed") {
9902
10170
  await persistDraftAssistantTurn();
9903
10171
  }
9904
10172
  if (event.type === "tool:approval:required") {
9905
10173
  const toolText = `- approval required \`${event.tool}\``;
9906
- toolTimeline.push(toolText);
9907
- currentTools.push(toolText);
10174
+ draft.toolTimeline.push(toolText);
10175
+ draft.currentTools.push(toolText);
9908
10176
  await persistDraftAssistantTurn();
9909
10177
  }
9910
10178
  if (event.type === "tool:approval:checkpoint") {
9911
10179
  await updateConversation((c) => {
9912
10180
  c.messages = buildMessages();
9913
- c.pendingApprovals = event.approvals.map((a) => ({
9914
- approvalId: a.approvalId,
10181
+ c.pendingApprovals = buildApprovalCheckpoints({
10182
+ approvals: event.approvals,
9915
10183
  runId: latestRunId,
9916
- tool: a.tool,
9917
- toolCallId: a.toolCallId,
9918
- input: a.input,
9919
10184
  checkpointMessages: event.checkpointMessages,
9920
10185
  baseMessageCount: historyMessages.length,
9921
10186
  pendingToolCalls: event.pendingToolCalls
9922
- }));
10187
+ });
9923
10188
  });
9924
10189
  checkpointedRun = true;
9925
10190
  const conv = await conversationStore.get(conversationId);
@@ -9931,9 +10196,14 @@ ${resultBody}`,
9931
10196
  tool: a.tool,
9932
10197
  input: a.input
9933
10198
  }));
10199
+ const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
10200
+ conv.channelMeta.platformThreadId,
10201
+ conv.channelMeta.channelId
10202
+ );
9934
10203
  void tgAdapter.sendApprovalRequest(
9935
10204
  conv.channelMeta.channelId,
9936
- approvals
10205
+ approvals,
10206
+ { message_thread_id: messageThreadId }
9937
10207
  ).catch((err) => {
9938
10208
  console.error("[messaging-runner] failed to send Telegram approval request:", err instanceof Error ? err.message : err);
9939
10209
  });
@@ -9955,77 +10225,63 @@ ${resultBody}`,
9955
10225
  });
9956
10226
  }
9957
10227
  }
9958
- if (event.type === "run:completed") {
9959
- if (assistantResponse.length === 0 && event.result.response) {
9960
- assistantResponse = event.result.response;
9961
- }
9962
- runContinuation2 = event.result.continuation === true;
9963
- if (event.result.continuationMessages) {
9964
- runContinuationMessages = event.result.continuationMessages;
9965
- }
9966
- runSteps = event.result.steps;
9967
- if (typeof event.result.maxSteps === "number") runMaxSteps = event.result.maxSteps;
9968
- runContextTokens = event.result.contextTokens ?? runContextTokens;
9969
- runContextWindow = event.result.contextWindow ?? runContextWindow;
9970
- }
9971
- if (event.type === "run:error") {
9972
- assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
9973
- }
9974
10228
  broadcastEvent(conversationId, event);
9975
10229
  }
9976
- } catch (err) {
9977
- console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
9978
- assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
9979
- }
9980
- if (currentTools.length > 0) {
9981
- sections.push({ type: "tools", content: currentTools });
9982
- currentTools = [];
9983
- }
9984
- if (currentText.length > 0) {
9985
- sections.push({ type: "text", content: currentText });
9986
- currentText = "";
9987
- }
9988
- if (!checkpointedRun) {
9989
- await updateConversation((c) => {
9990
- if (runContinuation2 && runContinuationMessages) {
9991
- c._continuationMessages = runContinuationMessages;
9992
- } else {
9993
- c._continuationMessages = void 0;
9994
- c.messages = buildMessages();
9995
- }
9996
- if (runContinuationMessages) {
9997
- c._harnessMessages = runContinuationMessages;
9998
- }
9999
- c.runtimeRunId = latestRunId || c.runtimeRunId;
10000
- c.pendingApprovals = [];
10001
- c.runStatus = "idle";
10002
- if (runContextTokens > 0) c.contextTokens = runContextTokens;
10003
- if (runContextWindow > 0) c.contextWindow = runContextWindow;
10004
- });
10005
- } else {
10006
- await updateConversation((c) => {
10007
- c.runStatus = "idle";
10008
- });
10009
- }
10010
- finishConversationStream(conversationId);
10011
- if (latestRunId) {
10012
- runOwners.delete(latestRunId);
10013
- runConversations.delete(latestRunId);
10014
- }
10015
- console.log("[messaging-runner] run complete, response length:", assistantResponse.length, runContinuation2 ? "(continuation)" : "");
10016
- const response = assistantResponse;
10017
- return {
10018
- response,
10019
- continuation: runContinuation2,
10020
- steps: runSteps,
10021
- maxSteps: runMaxSteps
10022
- };
10023
- } finally {
10024
- releaseQueue?.();
10025
- if (messagingRunQueues.get(conversationId) === chained) {
10026
- messagingRunQueues.delete(conversationId);
10027
- }
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;
10238
+ } catch (err) {
10239
+ console.error("[messaging-runner] run failed:", err instanceof Error ? err.message : err);
10240
+ draft.assistantResponse = draft.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
10028
10241
  }
10242
+ flushTurnDraft(draft);
10243
+ if (!checkpointedRun) {
10244
+ await updateConversation((c) => {
10245
+ if (runContinuation2 && runContinuationMessages) {
10246
+ c._continuationMessages = runContinuationMessages;
10247
+ } else {
10248
+ c._continuationMessages = void 0;
10249
+ c.messages = buildMessages();
10250
+ }
10251
+ if (runContinuationMessages) {
10252
+ c._harnessMessages = runContinuationMessages;
10253
+ } else if (shouldRebuildCanonical) {
10254
+ c._harnessMessages = c.messages;
10255
+ } else {
10256
+ c._harnessMessages = c.messages;
10257
+ }
10258
+ c.runtimeRunId = latestRunId || c.runtimeRunId;
10259
+ c.pendingApprovals = [];
10260
+ c.runStatus = "idle";
10261
+ if (runContextTokens > 0) c.contextTokens = runContextTokens;
10262
+ if (runContextWindow > 0) c.contextWindow = runContextWindow;
10263
+ });
10264
+ } else {
10265
+ await updateConversation((c) => {
10266
+ if (shouldRebuildCanonical && !c._harnessMessages?.length) {
10267
+ c._harnessMessages = c.messages;
10268
+ }
10269
+ c.runStatus = "idle";
10270
+ });
10271
+ }
10272
+ finishConversationStream(conversationId);
10273
+ if (latestRunId) {
10274
+ runOwners.delete(latestRunId);
10275
+ runConversations.delete(latestRunId);
10276
+ }
10277
+ console.log("[messaging-runner] run complete, response length:", draft.assistantResponse.length, runContinuation2 ? "(continuation)" : "");
10278
+ const response = draft.assistantResponse;
10279
+ return {
10280
+ response,
10281
+ continuation: runContinuation2,
10282
+ steps: runSteps,
10283
+ maxSteps: runMaxSteps
10284
+ };
10029
10285
  }
10030
10286
  };
10031
10287
  let waitUntilHook;
@@ -10152,6 +10408,7 @@ ${resultBody}`,
10152
10408
  async function* runContinuation(conversationId) {
10153
10409
  const conversation = await conversationStore.get(conversationId);
10154
10410
  if (!conversation) return;
10411
+ if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
10155
10412
  if (!conversation._continuationMessages?.length) return;
10156
10413
  if (conversation.runStatus === "running") return;
10157
10414
  const count = (conversation._continuationCount ?? 0) + 1;
@@ -10300,7 +10557,11 @@ ${resultBody}`,
10300
10557
  freshConv._continuationMessages = void 0;
10301
10558
  freshConv._continuationCount = void 0;
10302
10559
  }
10303
- if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
10560
+ if (nextHarnessMessages) {
10561
+ freshConv._harnessMessages = nextHarnessMessages;
10562
+ } else {
10563
+ freshConv._harnessMessages = freshConv.messages;
10564
+ }
10304
10565
  freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
10305
10566
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
10306
10567
  freshConv.pendingApprovals = [];
@@ -10422,6 +10683,8 @@ ${resultBody}`,
10422
10683
  }
10423
10684
  if (runResult?.continuationMessages) {
10424
10685
  conv._harnessMessages = runResult.continuationMessages;
10686
+ } else {
10687
+ conv._harnessMessages = conv.messages;
10425
10688
  }
10426
10689
  conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
10427
10690
  conv.lastActivityAt = Date.now();
@@ -10614,29 +10877,23 @@ ${resultBody}`,
10614
10877
  }
10615
10878
  return;
10616
10879
  }
10617
- const conversations = await conversationStore.list("local-owner");
10618
- let foundConversation;
10619
- let foundApproval;
10620
- for (const conv of conversations) {
10621
- if (!Array.isArray(conv.pendingApprovals)) continue;
10622
- const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
10623
- if (match) {
10624
- foundConversation = conv;
10625
- foundApproval = match;
10626
- break;
10627
- }
10628
- }
10880
+ const found = await findPendingApproval(approvalId, "local-owner");
10881
+ let foundConversation = found?.conversation;
10882
+ let foundApproval = found?.approval;
10629
10883
  if (!foundConversation || !foundApproval) {
10630
10884
  console.warn("[telegram-approval] approval not found:", approvalId);
10631
10885
  return;
10632
10886
  }
10887
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
10633
10888
  await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
10634
10889
  foundApproval.decision = approved ? "approved" : "denied";
10635
10890
  broadcastEvent(
10636
10891
  foundConversation.conversationId,
10637
10892
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
10638
10893
  );
10639
- const allApprovals = foundConversation.pendingApprovals ?? [];
10894
+ const allApprovals = (foundConversation.pendingApprovals ?? []).map(
10895
+ (approval) => normalizeApprovalCheckpoint(approval, foundConversation.messages)
10896
+ );
10640
10897
  const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
10641
10898
  if (!allDecided) {
10642
10899
  await conversationStore.update(foundConversation);
@@ -10985,6 +11242,9 @@ ${resultBody}`,
10985
11242
  return;
10986
11243
  }
10987
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
+ );
10988
11248
  writeJson(response, 403, {
10989
11249
  code: "CSRF_ERROR",
10990
11250
  message: "Invalid CSRF token"
@@ -11180,6 +11440,7 @@ data: ${JSON.stringify(frame)}
11180
11440
  const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
11181
11441
  const body = await readRequestBody(request);
11182
11442
  const approved = body.approved === true;
11443
+ const hintedConversationId = typeof body.conversationId === "string" && body.conversationId.trim().length > 0 ? body.conversationId.trim() : void 0;
11183
11444
  const pendingSubagent = pendingSubagentApprovals.get(approvalId);
11184
11445
  if (pendingSubagent) {
11185
11446
  pendingSubagent.checkpoint.decision = approved ? "approved" : "denied";
@@ -11205,18 +11466,23 @@ data: ${JSON.stringify(frame)}
11205
11466
  writeJson(response, 200, { ok: true, approvalId, approved });
11206
11467
  return;
11207
11468
  }
11208
- const conversations = await conversationStore.list(ownerId);
11209
11469
  let foundConversation;
11210
11470
  let foundApproval;
11211
- for (const conv of conversations) {
11212
- if (!Array.isArray(conv.pendingApprovals)) continue;
11213
- const match = conv.pendingApprovals.find((a) => a.approvalId === approvalId);
11214
- if (match) {
11215
- foundConversation = conv;
11216
- foundApproval = match;
11217
- 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
+ }
11218
11479
  }
11219
11480
  }
11481
+ if (!foundConversation || !foundApproval) {
11482
+ const found = await findPendingApproval(approvalId, ownerId);
11483
+ foundConversation = found?.conversation;
11484
+ foundApproval = found?.approval;
11485
+ }
11220
11486
  if (!foundConversation || !foundApproval) {
11221
11487
  writeJson(response, 404, {
11222
11488
  code: "APPROVAL_NOT_FOUND",
@@ -11225,37 +11491,32 @@ data: ${JSON.stringify(frame)}
11225
11491
  return;
11226
11492
  }
11227
11493
  const conversationId = foundConversation.conversationId;
11494
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
11228
11495
  if (!foundApproval.checkpointMessages || !foundApproval.toolCallId) {
11229
- foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).filter((a) => a.approvalId !== approvalId);
11230
- await conversationStore.update(foundConversation);
11231
- writeJson(response, 404, {
11232
- code: "APPROVAL_NOT_FOUND",
11233
- 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."
11234
11499
  });
11235
11500
  return;
11236
11501
  }
11237
- let batchDecisions = approvalDecisionTracker.get(conversationId);
11238
- if (!batchDecisions) {
11239
- batchDecisions = /* @__PURE__ */ new Map();
11240
- approvalDecisionTracker.set(conversationId, batchDecisions);
11241
- }
11242
- batchDecisions.set(approvalId, approved);
11243
- 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);
11244
11507
  broadcastEvent(
11245
11508
  conversationId,
11246
11509
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
11247
11510
  );
11248
- const allApprovals = foundConversation.pendingApprovals ?? [];
11249
- 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);
11250
11516
  if (!allDecided) {
11251
- await conversationStore.update(foundConversation);
11252
11517
  writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
11253
11518
  return;
11254
11519
  }
11255
- for (const a of allApprovals) {
11256
- const d = batchDecisions.get(a.approvalId);
11257
- if (d != null) a.decision = d ? "approved" : "denied";
11258
- }
11259
11520
  approvalDecisionTracker.delete(conversationId);
11260
11521
  foundConversation.pendingApprovals = [];
11261
11522
  foundConversation.runStatus = "running";
@@ -11460,7 +11721,8 @@ data: ${JSON.stringify(frame)}
11460
11721
  approvalId: a.approvalId,
11461
11722
  runId: a.runId,
11462
11723
  tool: a.tool,
11463
- input: a.input
11724
+ input: a.input,
11725
+ decision: a.decision
11464
11726
  })) : [];
11465
11727
  const subagentPending = [];
11466
11728
  if (!conversation.parentConversationId) {
@@ -11477,22 +11739,10 @@ data: ${JSON.stringify(frame)}
11477
11739
  }
11478
11740
  const activeStream = conversationEventStreams.get(conversationId);
11479
11741
  const hasActiveRun = !!activeStream && !activeStream.finished || conversation.runStatus === "running";
11480
- let hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11481
- (r) => r.parentConversationId === conversationId
11482
- );
11483
- if (!hasRunningSubagents && !conversation.parentConversationId) {
11484
- const summaries = await conversationStore.listSummaries(conversation.ownerId);
11485
- for (const s of summaries) {
11486
- if (s.parentConversationId !== conversationId) continue;
11487
- const c = await conversationStore.get(s.conversationId);
11488
- if (c?.subagentMeta?.status === "running") {
11489
- hasRunningSubagents = true;
11490
- break;
11491
- }
11492
- }
11493
- }
11742
+ const hasRunningSubagents = !conversation.parentConversationId ? await hasRunningSubagentsForParent(conversationId, conversation.ownerId) : false;
11494
11743
  const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults) && conversation.pendingSubagentResults.length > 0;
11495
- 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;
11496
11746
  writeJson(response, 200, {
11497
11747
  conversation: {
11498
11748
  ...conversation,
@@ -11706,11 +11956,9 @@ data: ${JSON.stringify(frame)}
11706
11956
  eventCount++;
11707
11957
  let sseEvent = event;
11708
11958
  if (sseEvent.type === "run:completed") {
11709
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11710
- (r) => r.parentConversationId === conversationId
11711
- );
11959
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
11712
11960
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
11713
- sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11961
+ sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11714
11962
  }
11715
11963
  try {
11716
11964
  response.write(formatSseEvent(sseEvent));
@@ -11738,7 +11986,7 @@ data: ${JSON.stringify(frame)}
11738
11986
  }
11739
11987
  } else {
11740
11988
  const freshConv = await conversationStore.get(conversationId);
11741
- if (freshConv?._continuationMessages?.length) {
11989
+ if (freshConv?._continuationMessages?.length && (!Array.isArray(freshConv.pendingApprovals) || freshConv.pendingApprovals.length === 0)) {
11742
11990
  doWaitUntil(
11743
11991
  new Promise((r) => setTimeout(r, 3e3)).then(
11744
11992
  () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
@@ -11820,9 +12068,17 @@ data: ${JSON.stringify(frame)}
11820
12068
  Connection: "keep-alive",
11821
12069
  "X-Accel-Buffering": "no"
11822
12070
  });
11823
- 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];
11824
12077
  const historyMessages = [...conversation.messages];
11825
12078
  const preRunMessages = [...conversation.messages];
12079
+ console.info(
12080
+ `[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`
12081
+ );
11826
12082
  let latestRunId = conversation.runtimeRunId ?? "";
11827
12083
  let assistantResponse = "";
11828
12084
  const toolTimeline = [];
@@ -12017,6 +12273,24 @@ data: ${JSON.stringify(frame)}
12017
12273
  const toolText = `- approval required \`${event.tool}\``;
12018
12274
  toolTimeline.push(toolText);
12019
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
+ }
12020
12294
  await persistDraftAssistantTurn();
12021
12295
  }
12022
12296
  if (event.type === "tool:approval:checkpoint") {
@@ -12036,16 +12310,13 @@ data: ${JSON.stringify(frame)}
12036
12310
  metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
12037
12311
  }] : []
12038
12312
  ];
12039
- conversation.pendingApprovals = event.approvals.map((a) => ({
12040
- approvalId: a.approvalId,
12313
+ conversation.pendingApprovals = buildApprovalCheckpoints({
12314
+ approvals: event.approvals,
12041
12315
  runId: latestRunId,
12042
- tool: a.tool,
12043
- toolCallId: a.toolCallId,
12044
- input: a.input,
12045
12316
  checkpointMessages: event.checkpointMessages,
12046
12317
  baseMessageCount: historyMessages.length,
12047
12318
  pendingToolCalls: event.pendingToolCalls
12048
- }));
12319
+ });
12049
12320
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12050
12321
  conversation.updatedAt = Date.now();
12051
12322
  await conversationStore.update(conversation);
@@ -12076,26 +12347,28 @@ data: ${JSON.stringify(frame)}
12076
12347
  conversation._harnessMessages = runContinuationMessages;
12077
12348
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12078
12349
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12079
- conversation.pendingApprovals = [];
12350
+ if (!checkpointedRun) {
12351
+ conversation.pendingApprovals = [];
12352
+ }
12080
12353
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
12081
12354
  if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
12082
12355
  conversation.updatedAt = Date.now();
12083
12356
  await conversationStore.update(conversation);
12084
- doWaitUntil(
12085
- new Promise((r) => setTimeout(r, 3e3)).then(
12086
- () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12087
- )
12088
- );
12357
+ if (!checkpointedRun) {
12358
+ doWaitUntil(
12359
+ new Promise((r) => setTimeout(r, 3e3)).then(
12360
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12361
+ )
12362
+ );
12363
+ }
12089
12364
  }
12090
12365
  }
12091
12366
  await telemetry.emit(event);
12092
12367
  let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
12093
12368
  if (sseEvent.type === "run:completed") {
12094
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
12095
- (r) => r.parentConversationId === conversationId
12096
- );
12369
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
12097
12370
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
12098
- if (hasRunningSubagents) {
12371
+ if (hasPendingSubagents) {
12099
12372
  sseEvent = { ...stripped, pendingSubagents: true };
12100
12373
  } else {
12101
12374
  sseEvent = stripped;
@@ -12132,6 +12405,10 @@ data: ${JSON.stringify(frame)}
12132
12405
  conversation._continuationMessages = void 0;
12133
12406
  if (runHarnessMessages) {
12134
12407
  conversation._harnessMessages = runHarnessMessages;
12408
+ } else if (shouldRebuildCanonical) {
12409
+ conversation._harnessMessages = conversation.messages;
12410
+ } else {
12411
+ conversation._harnessMessages = conversation.messages;
12135
12412
  }
12136
12413
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12137
12414
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
@@ -12166,7 +12443,9 @@ data: ${JSON.stringify(frame)}
12166
12443
  conversation.updatedAt = Date.now();
12167
12444
  await conversationStore.update(conversation);
12168
12445
  }
12169
- await clearPendingApprovalsForConversation(conversationId);
12446
+ if (!checkpointedRun) {
12447
+ await clearPendingApprovalsForConversation(conversationId);
12448
+ }
12170
12449
  return;
12171
12450
  }
12172
12451
  try {
@@ -12211,18 +12490,24 @@ data: ${JSON.stringify(frame)}
12211
12490
  if (active && active.abortController === abortController) {
12212
12491
  activeConversationRuns.delete(conversationId);
12213
12492
  }
12214
- finishConversationStream(conversationId);
12215
12493
  if (latestRunId) {
12216
12494
  runOwners.delete(latestRunId);
12217
12495
  runConversations.delete(latestRunId);
12218
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
+ }
12219
12506
  try {
12220
12507
  response.end();
12221
12508
  } catch {
12222
12509
  }
12223
- const hadDeferred = pendingCallbackNeeded.delete(conversationId);
12224
- const freshConv = await conversationStore.get(conversationId);
12225
- if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
12510
+ if (needsCallback) {
12226
12511
  processSubagentCallback(conversationId, true).catch(
12227
12512
  (err) => console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err)
12228
12513
  );
@@ -12279,43 +12564,41 @@ data: ${JSON.stringify(frame)}
12279
12564
  if (!conv) continue;
12280
12565
  const task = `[Scheduled: ${jobName}]
12281
12566
  ${cronJob.task}`;
12282
- 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];
12283
12572
  try {
12284
- let assistantResponse = "";
12285
- let steps = 0;
12286
- let cronHarnessMessages;
12287
- for await (const event of harness.runWithTelemetry({
12288
- task,
12289
- conversationId: conv.conversationId,
12290
- parameters: withToolResultArchiveParam(
12291
- { __activeConversationId: conv.conversationId },
12292
- conv
12293
- ),
12294
- messages: historyMessages
12295
- })) {
12296
- if (event.type === "model:chunk") {
12297
- assistantResponse += event.content;
12298
- }
12299
- if (event.type === "run:completed") {
12300
- steps = event.result.steps;
12301
- if (!assistantResponse && event.result.response) {
12302
- assistantResponse = event.result.response;
12303
- }
12304
- if (event.result.continuationMessages) {
12305
- cronHarnessMessages = event.result.continuationMessages;
12306
- }
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);
12307
12586
  }
12308
- await telemetry.emit(event);
12309
- }
12587
+ });
12588
+ const assistantResponse = execution.draft.assistantResponse;
12310
12589
  conv.messages = [
12311
12590
  ...historyMessages,
12312
12591
  { role: "user", content: task },
12313
12592
  ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12314
12593
  ];
12315
- if (cronHarnessMessages) {
12316
- conv._harnessMessages = cronHarnessMessages;
12594
+ if (execution.runHarnessMessages) {
12595
+ conv._harnessMessages = execution.runHarnessMessages;
12596
+ } else if (historySelection.shouldRebuildCanonical) {
12597
+ conv._harnessMessages = conv.messages;
12317
12598
  }
12318
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;
12319
12602
  conv.updatedAt = Date.now();
12320
12603
  await conversationStore.update(conv);
12321
12604
  if (assistantResponse) {
@@ -12331,7 +12614,7 @@ ${cronJob.task}`;
12331
12614
  console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
12332
12615
  }
12333
12616
  }
12334
- chatResults.push({ chatId, status: "completed", steps });
12617
+ chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
12335
12618
  } catch (runError) {
12336
12619
  chatResults.push({ chatId, status: "error" });
12337
12620
  console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
@@ -12580,86 +12863,30 @@ var startDevServer = async (port, options) => {
12580
12863
  const { Cron } = await import("croner");
12581
12864
  let activeJobs = [];
12582
12865
  const runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
12583
- let assistantResponse = "";
12584
- let steps = 0;
12585
- let contextTokens = 0;
12586
- let contextWindow = 0;
12587
- let harnessMessages;
12588
- const toolTimeline = [];
12589
- const sections = [];
12590
- let currentTools = [];
12591
- let currentText = "";
12592
- for await (const event of harnessRef.runWithTelemetry({
12593
- task,
12594
- conversationId,
12595
- parameters: {
12596
- __activeConversationId: conversationId,
12597
- [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
12598
12876
  },
12599
- messages: historyMessages
12600
- })) {
12601
- onEvent?.(event);
12602
- if (event.type === "model:chunk") {
12603
- if (currentTools.length > 0) {
12604
- sections.push({ type: "tools", content: currentTools });
12605
- currentTools = [];
12606
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
12607
- assistantResponse += " ";
12608
- }
12609
- }
12610
- assistantResponse += event.content;
12611
- currentText += event.content;
12612
- }
12613
- if (event.type === "tool:started") {
12614
- if (currentText.length > 0) {
12615
- sections.push({ type: "text", content: currentText });
12616
- currentText = "";
12617
- }
12618
- const toolText = `- start \`${event.tool}\``;
12619
- toolTimeline.push(toolText);
12620
- currentTools.push(toolText);
12621
- }
12622
- if (event.type === "tool:completed") {
12623
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
12624
- toolTimeline.push(toolText);
12625
- currentTools.push(toolText);
12626
- }
12627
- if (event.type === "tool:error") {
12628
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
12629
- toolTimeline.push(toolText);
12630
- currentTools.push(toolText);
12631
- }
12632
- if (event.type === "run:completed") {
12633
- steps = event.result.steps;
12634
- contextTokens = event.result.contextTokens ?? 0;
12635
- contextWindow = event.result.contextWindow ?? 0;
12636
- if (event.result.continuationMessages) {
12637
- harnessMessages = event.result.continuationMessages;
12638
- }
12639
- if (!assistantResponse && event.result.response) {
12640
- assistantResponse = event.result.response;
12641
- }
12642
- }
12643
- }
12644
- if (currentTools.length > 0) {
12645
- sections.push({ type: "tools", content: currentTools });
12646
- }
12647
- if (currentText.length > 0) {
12648
- sections.push({ type: "text", content: currentText });
12649
- }
12650
- const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
12651
- const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
12652
- toolActivity: [...toolTimeline],
12653
- sections: sections.length > 0 ? sections : void 0
12654
- } : 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);
12655
12882
  return {
12656
- response: assistantResponse,
12657
- steps,
12883
+ response: execution.draft.assistantResponse,
12884
+ steps: execution.runSteps,
12658
12885
  assistantMetadata,
12659
12886
  hasContent,
12660
- contextTokens,
12661
- contextWindow,
12662
- harnessMessages,
12887
+ contextTokens: execution.runContextTokens,
12888
+ contextWindow: execution.runContextWindow,
12889
+ harnessMessages: execution.runHarnessMessages,
12663
12890
  toolResultArchive: harnessRef.getToolResultArchive(conversationId)
12664
12891
  };
12665
12892
  };
@@ -12720,7 +12947,11 @@ var startDevServer = async (port, options) => {
12720
12947
  if (!conversation) continue;
12721
12948
  const task = `[Scheduled: ${jobName}]
12722
12949
  ${config.task}`;
12723
- 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];
12724
12955
  const convId = conversation.conversationId;
12725
12956
  activeRuns?.set(convId, {
12726
12957
  ownerId: "local-owner",
@@ -12743,6 +12974,8 @@ ${config.task}`;
12743
12974
  freshConv.messages = buildCronMessages(task, historyMessages, result);
12744
12975
  if (result.harnessMessages) {
12745
12976
  freshConv._harnessMessages = result.harnessMessages;
12977
+ } else if (historySelection.shouldRebuildCanonical) {
12978
+ freshConv._harnessMessages = freshConv.messages;
12746
12979
  }
12747
12980
  if (result.toolResultArchive) {
12748
12981
  freshConv._toolResultArchive = result.toolResultArchive;
@@ -12960,7 +13193,7 @@ var runInteractive = async (workingDir, params) => {
12960
13193
  await harness.initialize();
12961
13194
  const identity = await ensureAgentIdentity2(workingDir);
12962
13195
  try {
12963
- const { runInteractiveInk } = await import("./run-interactive-ink-GPCI4L6G.js");
13196
+ const { runInteractiveInk } = await import("./run-interactive-ink-VGKSZJDO.js");
12964
13197
  await runInteractiveInk({
12965
13198
  harness,
12966
13199
  params,
@@ -13694,6 +13927,7 @@ export {
13694
13927
  inferConversationTitle,
13695
13928
  consumeFirstRunIntro,
13696
13929
  resolveHarnessEnvironment,
13930
+ __internalRunOrchestration,
13697
13931
  initProject,
13698
13932
  updateAgentGuidance,
13699
13933
  createRequestHandler,