@poncho-ai/cli 0.32.4 → 0.32.6

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,76 @@ ${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"}]`;
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
+ });
10028
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
+ };
10285
+ },
10286
+ async resetConversation(conversationId) {
10287
+ const existing = await conversationStore.get(conversationId);
10288
+ if (!existing) return;
10289
+ existing.messages = [];
10290
+ existing._harnessMessages = void 0;
10291
+ existing._continuationMessages = void 0;
10292
+ existing._toolResultArchive = void 0;
10293
+ existing.pendingApprovals = void 0;
10294
+ existing.runStatus = void 0;
10295
+ existing.updatedAt = Date.now();
10296
+ await conversationStore.update(existing);
10297
+ console.log(`[messaging-runner] conversation reset: ${conversationId}`);
10029
10298
  }
10030
10299
  };
10031
10300
  let waitUntilHook;
@@ -10152,6 +10421,7 @@ ${resultBody}`,
10152
10421
  async function* runContinuation(conversationId) {
10153
10422
  const conversation = await conversationStore.get(conversationId);
10154
10423
  if (!conversation) return;
10424
+ if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
10155
10425
  if (!conversation._continuationMessages?.length) return;
10156
10426
  if (conversation.runStatus === "running") return;
10157
10427
  const count = (conversation._continuationCount ?? 0) + 1;
@@ -10300,7 +10570,11 @@ ${resultBody}`,
10300
10570
  freshConv._continuationMessages = void 0;
10301
10571
  freshConv._continuationCount = void 0;
10302
10572
  }
10303
- if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
10573
+ if (nextHarnessMessages) {
10574
+ freshConv._harnessMessages = nextHarnessMessages;
10575
+ } else {
10576
+ freshConv._harnessMessages = freshConv.messages;
10577
+ }
10304
10578
  freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
10305
10579
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
10306
10580
  freshConv.pendingApprovals = [];
@@ -10422,6 +10696,8 @@ ${resultBody}`,
10422
10696
  }
10423
10697
  if (runResult?.continuationMessages) {
10424
10698
  conv._harnessMessages = runResult.continuationMessages;
10699
+ } else {
10700
+ conv._harnessMessages = conv.messages;
10425
10701
  }
10426
10702
  conv._toolResultArchive = childHarness.getToolResultArchive(conversationId);
10427
10703
  conv.lastActivityAt = Date.now();
@@ -10614,29 +10890,23 @@ ${resultBody}`,
10614
10890
  }
10615
10891
  return;
10616
10892
  }
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
- }
10893
+ const found = await findPendingApproval(approvalId, "local-owner");
10894
+ let foundConversation = found?.conversation;
10895
+ let foundApproval = found?.approval;
10629
10896
  if (!foundConversation || !foundApproval) {
10630
10897
  console.warn("[telegram-approval] approval not found:", approvalId);
10631
10898
  return;
10632
10899
  }
10900
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
10633
10901
  await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
10634
10902
  foundApproval.decision = approved ? "approved" : "denied";
10635
10903
  broadcastEvent(
10636
10904
  foundConversation.conversationId,
10637
10905
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
10638
10906
  );
10639
- const allApprovals = foundConversation.pendingApprovals ?? [];
10907
+ const allApprovals = (foundConversation.pendingApprovals ?? []).map(
10908
+ (approval) => normalizeApprovalCheckpoint(approval, foundConversation.messages)
10909
+ );
10640
10910
  const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
10641
10911
  if (!allDecided) {
10642
10912
  await conversationStore.update(foundConversation);
@@ -10985,6 +11255,9 @@ ${resultBody}`,
10985
11255
  return;
10986
11256
  }
10987
11257
  if (requireAuth && session && !hasBearerToken && requiresCsrfValidation && pathname !== "/api/auth/login" && request.headers["x-csrf-token"] !== session?.csrfToken) {
11258
+ console.warn(
11259
+ `[poncho][csrf] blocked request method=${request.method} path="${pathname}" session=${session.sessionId}`
11260
+ );
10988
11261
  writeJson(response, 403, {
10989
11262
  code: "CSRF_ERROR",
10990
11263
  message: "Invalid CSRF token"
@@ -11180,6 +11453,7 @@ data: ${JSON.stringify(frame)}
11180
11453
  const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
11181
11454
  const body = await readRequestBody(request);
11182
11455
  const approved = body.approved === true;
11456
+ const hintedConversationId = typeof body.conversationId === "string" && body.conversationId.trim().length > 0 ? body.conversationId.trim() : void 0;
11183
11457
  const pendingSubagent = pendingSubagentApprovals.get(approvalId);
11184
11458
  if (pendingSubagent) {
11185
11459
  pendingSubagent.checkpoint.decision = approved ? "approved" : "denied";
@@ -11205,18 +11479,23 @@ data: ${JSON.stringify(frame)}
11205
11479
  writeJson(response, 200, { ok: true, approvalId, approved });
11206
11480
  return;
11207
11481
  }
11208
- const conversations = await conversationStore.list(ownerId);
11209
11482
  let foundConversation;
11210
11483
  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;
11484
+ if (hintedConversationId) {
11485
+ const hintedConversation = await conversationStore.get(hintedConversationId);
11486
+ if (hintedConversation && hintedConversation.ownerId === ownerId && Array.isArray(hintedConversation.pendingApprovals)) {
11487
+ const hintedMatch = hintedConversation.pendingApprovals.find((approval) => approval.approvalId === approvalId);
11488
+ if (hintedMatch) {
11489
+ foundConversation = hintedConversation;
11490
+ foundApproval = hintedMatch;
11491
+ }
11218
11492
  }
11219
11493
  }
11494
+ if (!foundConversation || !foundApproval) {
11495
+ const found = await findPendingApproval(approvalId, ownerId);
11496
+ foundConversation = found?.conversation;
11497
+ foundApproval = found?.approval;
11498
+ }
11220
11499
  if (!foundConversation || !foundApproval) {
11221
11500
  writeJson(response, 404, {
11222
11501
  code: "APPROVAL_NOT_FOUND",
@@ -11225,37 +11504,32 @@ data: ${JSON.stringify(frame)}
11225
11504
  return;
11226
11505
  }
11227
11506
  const conversationId = foundConversation.conversationId;
11507
+ foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
11228
11508
  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)"
11509
+ writeJson(response, 409, {
11510
+ code: "APPROVAL_NOT_READY",
11511
+ message: "Approval checkpoint is not ready yet. Please retry shortly."
11234
11512
  });
11235
11513
  return;
11236
11514
  }
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";
11515
+ const approvalDecision = approved ? "approved" : "denied";
11516
+ foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).map(
11517
+ (approval) => approval.approvalId === approvalId ? { ...normalizeApprovalCheckpoint(approval, foundConversation.messages), decision: approvalDecision } : normalizeApprovalCheckpoint(approval, foundConversation.messages)
11518
+ );
11519
+ await conversationStore.update(foundConversation);
11244
11520
  broadcastEvent(
11245
11521
  conversationId,
11246
11522
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
11247
11523
  );
11248
- const allApprovals = foundConversation.pendingApprovals ?? [];
11249
- const allDecided = allApprovals.length > 0 && allApprovals.every((a) => batchDecisions.has(a.approvalId));
11524
+ const refreshedConversation = await conversationStore.get(conversationId);
11525
+ const allApprovals = (refreshedConversation?.pendingApprovals ?? []).map(
11526
+ (approval) => normalizeApprovalCheckpoint(approval, refreshedConversation.messages)
11527
+ );
11528
+ const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
11250
11529
  if (!allDecided) {
11251
- await conversationStore.update(foundConversation);
11252
11530
  writeJson(response, 200, { ok: true, approvalId, approved, batchComplete: false });
11253
11531
  return;
11254
11532
  }
11255
- for (const a of allApprovals) {
11256
- const d = batchDecisions.get(a.approvalId);
11257
- if (d != null) a.decision = d ? "approved" : "denied";
11258
- }
11259
11533
  approvalDecisionTracker.delete(conversationId);
11260
11534
  foundConversation.pendingApprovals = [];
11261
11535
  foundConversation.runStatus = "running";
@@ -11460,7 +11734,8 @@ data: ${JSON.stringify(frame)}
11460
11734
  approvalId: a.approvalId,
11461
11735
  runId: a.runId,
11462
11736
  tool: a.tool,
11463
- input: a.input
11737
+ input: a.input,
11738
+ decision: a.decision
11464
11739
  })) : [];
11465
11740
  const subagentPending = [];
11466
11741
  if (!conversation.parentConversationId) {
@@ -11477,22 +11752,10 @@ data: ${JSON.stringify(frame)}
11477
11752
  }
11478
11753
  const activeStream = conversationEventStreams.get(conversationId);
11479
11754
  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
- }
11755
+ const hasRunningSubagents = !conversation.parentConversationId ? await hasRunningSubagentsForParent(conversationId, conversation.ownerId) : false;
11494
11756
  const hasPendingCallbackResults = Array.isArray(conversation.pendingSubagentResults) && conversation.pendingSubagentResults.length > 0;
11495
- const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0;
11757
+ const hasPendingApprovals = Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0;
11758
+ const needsContinuation = !hasActiveRun && Array.isArray(conversation._continuationMessages) && conversation._continuationMessages.length > 0 && !hasPendingApprovals;
11496
11759
  writeJson(response, 200, {
11497
11760
  conversation: {
11498
11761
  ...conversation,
@@ -11706,11 +11969,9 @@ data: ${JSON.stringify(frame)}
11706
11969
  eventCount++;
11707
11970
  let sseEvent = event;
11708
11971
  if (sseEvent.type === "run:completed") {
11709
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11710
- (r) => r.parentConversationId === conversationId
11711
- );
11972
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
11712
11973
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
11713
- sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11974
+ sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11714
11975
  }
11715
11976
  try {
11716
11977
  response.write(formatSseEvent(sseEvent));
@@ -11738,7 +11999,7 @@ data: ${JSON.stringify(frame)}
11738
11999
  }
11739
12000
  } else {
11740
12001
  const freshConv = await conversationStore.get(conversationId);
11741
- if (freshConv?._continuationMessages?.length) {
12002
+ if (freshConv?._continuationMessages?.length && (!Array.isArray(freshConv.pendingApprovals) || freshConv.pendingApprovals.length === 0)) {
11742
12003
  doWaitUntil(
11743
12004
  new Promise((r) => setTimeout(r, 3e3)).then(
11744
12005
  () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
@@ -11820,9 +12081,17 @@ data: ${JSON.stringify(frame)}
11820
12081
  Connection: "keep-alive",
11821
12082
  "X-Accel-Buffering": "no"
11822
12083
  });
11823
- const harnessMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
12084
+ const canonicalHistory = resolveRunRequest(conversation, {
12085
+ conversationId,
12086
+ messages: conversation.messages
12087
+ });
12088
+ const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
12089
+ const harnessMessages = [...canonicalHistory.messages];
11824
12090
  const historyMessages = [...conversation.messages];
11825
12091
  const preRunMessages = [...conversation.messages];
12092
+ console.info(
12093
+ `[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`
12094
+ );
11826
12095
  let latestRunId = conversation.runtimeRunId ?? "";
11827
12096
  let assistantResponse = "";
11828
12097
  const toolTimeline = [];
@@ -12017,6 +12286,24 @@ data: ${JSON.stringify(frame)}
12017
12286
  const toolText = `- approval required \`${event.tool}\``;
12018
12287
  toolTimeline.push(toolText);
12019
12288
  currentTools.push(toolText);
12289
+ const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
12290
+ if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
12291
+ conversation.pendingApprovals = [
12292
+ ...existingApprovals,
12293
+ {
12294
+ approvalId: event.approvalId,
12295
+ runId: latestRunId || conversation.runtimeRunId || "",
12296
+ tool: event.tool,
12297
+ toolCallId: void 0,
12298
+ input: event.input ?? {},
12299
+ checkpointMessages: void 0,
12300
+ baseMessageCount: historyMessages.length,
12301
+ pendingToolCalls: []
12302
+ }
12303
+ ];
12304
+ conversation.updatedAt = Date.now();
12305
+ await conversationStore.update(conversation);
12306
+ }
12020
12307
  await persistDraftAssistantTurn();
12021
12308
  }
12022
12309
  if (event.type === "tool:approval:checkpoint") {
@@ -12036,16 +12323,13 @@ data: ${JSON.stringify(frame)}
12036
12323
  metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
12037
12324
  }] : []
12038
12325
  ];
12039
- conversation.pendingApprovals = event.approvals.map((a) => ({
12040
- approvalId: a.approvalId,
12326
+ conversation.pendingApprovals = buildApprovalCheckpoints({
12327
+ approvals: event.approvals,
12041
12328
  runId: latestRunId,
12042
- tool: a.tool,
12043
- toolCallId: a.toolCallId,
12044
- input: a.input,
12045
12329
  checkpointMessages: event.checkpointMessages,
12046
12330
  baseMessageCount: historyMessages.length,
12047
12331
  pendingToolCalls: event.pendingToolCalls
12048
- }));
12332
+ });
12049
12333
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12050
12334
  conversation.updatedAt = Date.now();
12051
12335
  await conversationStore.update(conversation);
@@ -12076,26 +12360,28 @@ data: ${JSON.stringify(frame)}
12076
12360
  conversation._harnessMessages = runContinuationMessages;
12077
12361
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12078
12362
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12079
- conversation.pendingApprovals = [];
12363
+ if (!checkpointedRun) {
12364
+ conversation.pendingApprovals = [];
12365
+ }
12080
12366
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
12081
12367
  if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
12082
12368
  conversation.updatedAt = Date.now();
12083
12369
  await conversationStore.update(conversation);
12084
- doWaitUntil(
12085
- new Promise((r) => setTimeout(r, 3e3)).then(
12086
- () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12087
- )
12088
- );
12370
+ if (!checkpointedRun) {
12371
+ doWaitUntil(
12372
+ new Promise((r) => setTimeout(r, 3e3)).then(
12373
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12374
+ )
12375
+ );
12376
+ }
12089
12377
  }
12090
12378
  }
12091
12379
  await telemetry.emit(event);
12092
12380
  let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
12093
12381
  if (sseEvent.type === "run:completed") {
12094
- const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
12095
- (r) => r.parentConversationId === conversationId
12096
- );
12382
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
12097
12383
  const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
12098
- if (hasRunningSubagents) {
12384
+ if (hasPendingSubagents) {
12099
12385
  sseEvent = { ...stripped, pendingSubagents: true };
12100
12386
  } else {
12101
12387
  sseEvent = stripped;
@@ -12132,6 +12418,10 @@ data: ${JSON.stringify(frame)}
12132
12418
  conversation._continuationMessages = void 0;
12133
12419
  if (runHarnessMessages) {
12134
12420
  conversation._harnessMessages = runHarnessMessages;
12421
+ } else if (shouldRebuildCanonical) {
12422
+ conversation._harnessMessages = conversation.messages;
12423
+ } else {
12424
+ conversation._harnessMessages = conversation.messages;
12135
12425
  }
12136
12426
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12137
12427
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
@@ -12166,7 +12456,9 @@ data: ${JSON.stringify(frame)}
12166
12456
  conversation.updatedAt = Date.now();
12167
12457
  await conversationStore.update(conversation);
12168
12458
  }
12169
- await clearPendingApprovalsForConversation(conversationId);
12459
+ if (!checkpointedRun) {
12460
+ await clearPendingApprovalsForConversation(conversationId);
12461
+ }
12170
12462
  return;
12171
12463
  }
12172
12464
  try {
@@ -12211,18 +12503,24 @@ data: ${JSON.stringify(frame)}
12211
12503
  if (active && active.abortController === abortController) {
12212
12504
  activeConversationRuns.delete(conversationId);
12213
12505
  }
12214
- finishConversationStream(conversationId);
12215
12506
  if (latestRunId) {
12216
12507
  runOwners.delete(latestRunId);
12217
12508
  runConversations.delete(latestRunId);
12218
12509
  }
12510
+ const hadDeferred = pendingCallbackNeeded.delete(conversationId);
12511
+ const freshConv = await conversationStore.get(conversationId);
12512
+ const needsCallback = hadDeferred || !!freshConv?.pendingSubagentResults?.length;
12513
+ const hasRunningChildren = Array.from(activeSubagentRuns.values()).some(
12514
+ (run) => run.parentConversationId === conversationId
12515
+ );
12516
+ if (!needsCallback && !hasRunningChildren) {
12517
+ finishConversationStream(conversationId);
12518
+ }
12219
12519
  try {
12220
12520
  response.end();
12221
12521
  } catch {
12222
12522
  }
12223
- const hadDeferred = pendingCallbackNeeded.delete(conversationId);
12224
- const freshConv = await conversationStore.get(conversationId);
12225
- if (hadDeferred || freshConv?.pendingSubagentResults?.length) {
12523
+ if (needsCallback) {
12226
12524
  processSubagentCallback(conversationId, true).catch(
12227
12525
  (err) => console.error(`[poncho][subagent-callback] Post-run callback failed:`, err instanceof Error ? err.message : err)
12228
12526
  );
@@ -12279,43 +12577,41 @@ data: ${JSON.stringify(frame)}
12279
12577
  if (!conv) continue;
12280
12578
  const task = `[Scheduled: ${jobName}]
12281
12579
  ${cronJob.task}`;
12282
- const historyMessages = conv._harnessMessages?.length ? [...conv._harnessMessages] : [...conv.messages];
12580
+ const historySelection = resolveRunRequest(conv, {
12581
+ conversationId: conv.conversationId,
12582
+ messages: conv.messages
12583
+ });
12584
+ const historyMessages = [...historySelection.messages];
12283
12585
  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
- }
12586
+ const execution = await executeConversationTurn({
12587
+ harness,
12588
+ runInput: {
12589
+ task,
12590
+ conversationId: conv.conversationId,
12591
+ parameters: withToolResultArchiveParam(
12592
+ { __activeConversationId: conv.conversationId },
12593
+ conv
12594
+ ),
12595
+ messages: historyMessages
12596
+ },
12597
+ onEvent: async (event) => {
12598
+ await telemetry.emit(event);
12307
12599
  }
12308
- await telemetry.emit(event);
12309
- }
12600
+ });
12601
+ const assistantResponse = execution.draft.assistantResponse;
12310
12602
  conv.messages = [
12311
12603
  ...historyMessages,
12312
12604
  { role: "user", content: task },
12313
12605
  ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12314
12606
  ];
12315
- if (cronHarnessMessages) {
12316
- conv._harnessMessages = cronHarnessMessages;
12607
+ if (execution.runHarnessMessages) {
12608
+ conv._harnessMessages = execution.runHarnessMessages;
12609
+ } else if (historySelection.shouldRebuildCanonical) {
12610
+ conv._harnessMessages = conv.messages;
12317
12611
  }
12318
12612
  conv._toolResultArchive = harness.getToolResultArchive(conv.conversationId);
12613
+ if (execution.runContextTokens > 0) conv.contextTokens = execution.runContextTokens;
12614
+ if (execution.runContextWindow > 0) conv.contextWindow = execution.runContextWindow;
12319
12615
  conv.updatedAt = Date.now();
12320
12616
  await conversationStore.update(conv);
12321
12617
  if (assistantResponse) {
@@ -12331,7 +12627,7 @@ ${cronJob.task}`;
12331
12627
  console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
12332
12628
  }
12333
12629
  }
12334
- chatResults.push({ chatId, status: "completed", steps });
12630
+ chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
12335
12631
  } catch (runError) {
12336
12632
  chatResults.push({ chatId, status: "error" });
12337
12633
  console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
@@ -12580,86 +12876,30 @@ var startDevServer = async (port, options) => {
12580
12876
  const { Cron } = await import("croner");
12581
12877
  let activeJobs = [];
12582
12878
  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 ?? {}
12879
+ const execution = await executeConversationTurn({
12880
+ harness: harnessRef,
12881
+ runInput: {
12882
+ task,
12883
+ conversationId,
12884
+ parameters: {
12885
+ __activeConversationId: conversationId,
12886
+ [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
12887
+ },
12888
+ messages: historyMessages
12598
12889
  },
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;
12890
+ onEvent
12891
+ });
12892
+ flushTurnDraft(execution.draft);
12893
+ const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
12894
+ const assistantMetadata = buildAssistantMetadata(execution.draft);
12655
12895
  return {
12656
- response: assistantResponse,
12657
- steps,
12896
+ response: execution.draft.assistantResponse,
12897
+ steps: execution.runSteps,
12658
12898
  assistantMetadata,
12659
12899
  hasContent,
12660
- contextTokens,
12661
- contextWindow,
12662
- harnessMessages,
12900
+ contextTokens: execution.runContextTokens,
12901
+ contextWindow: execution.runContextWindow,
12902
+ harnessMessages: execution.runHarnessMessages,
12663
12903
  toolResultArchive: harnessRef.getToolResultArchive(conversationId)
12664
12904
  };
12665
12905
  };
@@ -12720,7 +12960,11 @@ var startDevServer = async (port, options) => {
12720
12960
  if (!conversation) continue;
12721
12961
  const task = `[Scheduled: ${jobName}]
12722
12962
  ${config.task}`;
12723
- const historyMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
12963
+ const historySelection = resolveRunRequest(conversation, {
12964
+ conversationId: conversation.conversationId,
12965
+ messages: conversation.messages
12966
+ });
12967
+ const historyMessages = [...historySelection.messages];
12724
12968
  const convId = conversation.conversationId;
12725
12969
  activeRuns?.set(convId, {
12726
12970
  ownerId: "local-owner",
@@ -12743,6 +12987,8 @@ ${config.task}`;
12743
12987
  freshConv.messages = buildCronMessages(task, historyMessages, result);
12744
12988
  if (result.harnessMessages) {
12745
12989
  freshConv._harnessMessages = result.harnessMessages;
12990
+ } else if (historySelection.shouldRebuildCanonical) {
12991
+ freshConv._harnessMessages = freshConv.messages;
12746
12992
  }
12747
12993
  if (result.toolResultArchive) {
12748
12994
  freshConv._toolResultArchive = result.toolResultArchive;
@@ -12960,7 +13206,7 @@ var runInteractive = async (workingDir, params) => {
12960
13206
  await harness.initialize();
12961
13207
  const identity = await ensureAgentIdentity2(workingDir);
12962
13208
  try {
12963
- const { runInteractiveInk } = await import("./run-interactive-ink-GPCI4L6G.js");
13209
+ const { runInteractiveInk } = await import("./run-interactive-ink-LAL4PQVD.js");
12964
13210
  await runInteractiveInk({
12965
13211
  harness,
12966
13212
  params,
@@ -13694,6 +13940,7 @@ export {
13694
13940
  inferConversationTitle,
13695
13941
  consumeFirstRunIntro,
13696
13942
  resolveHarnessEnvironment,
13943
+ __internalRunOrchestration,
13697
13944
  initProject,
13698
13945
  updateAgentGuidance,
13699
13946
  createRequestHandler,