@poncho-ai/cli 0.33.1 → 0.33.3

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.
@@ -7175,12 +7175,8 @@ var collectEnvFileLines = (answers) => {
7175
7175
  }
7176
7176
  const telemetryEnabled = Boolean(answers["telemetry.enabled"] ?? true);
7177
7177
  if (telemetryEnabled) {
7178
- lines.push("# Telemetry (optional)");
7179
- lines.push("# Latitude telemetry setup: https://docs.latitude.so/");
7180
- lines.push("# If not using Latitude yet, you can leave these empty.");
7181
- lines.push("LATITUDE_API_KEY=");
7182
- lines.push("LATITUDE_PROJECT_ID=");
7183
- lines.push("LATITUDE_PATH=");
7178
+ lines.push("# Telemetry (optional) \u2014 set an OTLP endpoint to export traces");
7179
+ lines.push("# OTEL_EXPORTER_OTLP_ENDPOINT=");
7184
7180
  lines.push("");
7185
7181
  }
7186
7182
  while (lines.length > 0 && lines[lines.length - 1] === "") {
@@ -7599,14 +7595,17 @@ var parseParams = (values) => {
7599
7595
  return params;
7600
7596
  };
7601
7597
  var normalizeMessageForClient = (message) => {
7598
+ if (message.role === "tool" || message.role === "system") {
7599
+ return null;
7600
+ }
7602
7601
  if (message.role !== "assistant" || typeof message.content !== "string") {
7603
7602
  return message;
7604
7603
  }
7605
7604
  try {
7606
7605
  const parsed = JSON.parse(message.content);
7607
- const text = typeof parsed.text === "string" ? parsed.text : void 0;
7608
7606
  const toolCalls = Array.isArray(parsed.tool_calls) ? parsed.tool_calls : void 0;
7609
- if (typeof text === "string" && toolCalls) {
7607
+ if (toolCalls) {
7608
+ const text = typeof parsed.text === "string" ? parsed.text : "";
7610
7609
  const meta = { ...message.metadata ?? {} };
7611
7610
  if (!meta.sections && toolCalls.length > 0) {
7612
7611
  const toolLabels = toolCalls.map((tc) => {
@@ -7724,6 +7723,7 @@ var buildAssistantMetadata = (draft, sectionsOverride) => {
7724
7723
  var executeConversationTurn = async ({
7725
7724
  harness,
7726
7725
  runInput,
7726
+ events,
7727
7727
  initialContextTokens = 0,
7728
7728
  initialContextWindow = 0,
7729
7729
  onEvent
@@ -7738,7 +7738,8 @@ var executeConversationTurn = async ({
7738
7738
  let runContextWindow = initialContextWindow;
7739
7739
  let runSteps = 0;
7740
7740
  let runMaxSteps;
7741
- for await (const event of harness.runWithTelemetry(runInput)) {
7741
+ const source = events ?? harness.runWithTelemetry(runInput);
7742
+ for await (const event of source) {
7742
7743
  recordStandardTurnEvent(draft, event);
7743
7744
  if (event.type === "run:started") {
7744
7745
  latestRunId = event.runId;
@@ -7831,6 +7832,76 @@ var __internalRunOrchestration = {
7831
7832
  recordStandardTurnEvent,
7832
7833
  executeConversationTurn
7833
7834
  };
7835
+ var applyTurnMetadata = (conv, meta, opts = {}) => {
7836
+ const {
7837
+ clearContinuation = true,
7838
+ clearApprovals = true,
7839
+ setIdle = true,
7840
+ shouldRebuildCanonical = false
7841
+ } = opts;
7842
+ if (meta.continuation && meta.continuationMessages) {
7843
+ conv._continuationMessages = meta.continuationMessages;
7844
+ } else if (clearContinuation) {
7845
+ conv._continuationMessages = void 0;
7846
+ conv._continuationCount = void 0;
7847
+ }
7848
+ if (meta.harnessMessages) {
7849
+ conv._harnessMessages = meta.harnessMessages;
7850
+ } else if (shouldRebuildCanonical) {
7851
+ conv._harnessMessages = conv.messages;
7852
+ }
7853
+ if (meta.toolResultArchive !== void 0) {
7854
+ conv._toolResultArchive = meta.toolResultArchive;
7855
+ }
7856
+ conv.runtimeRunId = meta.latestRunId || conv.runtimeRunId;
7857
+ if (clearApprovals) conv.pendingApprovals = [];
7858
+ if (setIdle) conv.runStatus = "idle";
7859
+ if (meta.contextTokens > 0) conv.contextTokens = meta.contextTokens;
7860
+ if (meta.contextWindow > 0) conv.contextWindow = meta.contextWindow;
7861
+ conv.updatedAt = Date.now();
7862
+ };
7863
+ var runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
7864
+ const execution = await executeConversationTurn({
7865
+ harness: harnessRef,
7866
+ runInput: {
7867
+ task,
7868
+ conversationId,
7869
+ parameters: {
7870
+ __activeConversationId: conversationId,
7871
+ [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
7872
+ },
7873
+ messages: historyMessages
7874
+ },
7875
+ onEvent
7876
+ });
7877
+ flushTurnDraft(execution.draft);
7878
+ const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
7879
+ const assistantMetadata = buildAssistantMetadata(execution.draft);
7880
+ return {
7881
+ response: execution.draft.assistantResponse,
7882
+ steps: execution.runSteps,
7883
+ assistantMetadata,
7884
+ hasContent,
7885
+ contextTokens: execution.runContextTokens,
7886
+ contextWindow: execution.runContextWindow,
7887
+ harnessMessages: execution.runHarnessMessages,
7888
+ toolResultArchive: harnessRef.getToolResultArchive(conversationId),
7889
+ latestRunId: execution.latestRunId,
7890
+ continuation: execution.runContinuation,
7891
+ continuationMessages: execution.runContinuationMessages
7892
+ };
7893
+ };
7894
+ var buildCronMessages = (task, historyMessages, result) => [
7895
+ ...historyMessages,
7896
+ { role: "user", content: task },
7897
+ ...result.hasContent ? [{ role: "assistant", content: result.response, metadata: result.assistantMetadata }] : []
7898
+ ];
7899
+ var appendCronTurn = (conv, task, result) => {
7900
+ conv.messages.push(
7901
+ { role: "user", content: task },
7902
+ ...result.hasContent ? [{ role: "assistant", content: result.response, metadata: result.assistantMetadata }] : []
7903
+ );
7904
+ };
7834
7905
  var AGENT_TEMPLATE = (name, id, options) => `---
7835
7906
  name: ${name}
7836
7907
  id: ${id}
@@ -9651,30 +9722,23 @@ ${resultBody}`,
9651
9722
  if (callbackNeedsContinuation || execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0) {
9652
9723
  const freshConv = await conversationStore.get(conversationId);
9653
9724
  if (freshConv) {
9654
- if (callbackNeedsContinuation) {
9655
- freshConv._continuationMessages = execution.runContinuationMessages;
9656
- } else {
9657
- freshConv._continuationMessages = void 0;
9725
+ if (!callbackNeedsContinuation) {
9658
9726
  freshConv.messages.push({
9659
9727
  role: "assistant",
9660
9728
  content: execution.draft.assistantResponse,
9661
9729
  metadata: buildAssistantMetadata(execution.draft)
9662
9730
  });
9663
9731
  }
9664
- if (callbackNeedsContinuation && execution.runHarnessMessages) {
9665
- freshConv._harnessMessages = execution.runHarnessMessages;
9666
- } else if (historySelection.shouldRebuildCanonical) {
9667
- freshConv._harnessMessages = freshConv.messages;
9668
- } else {
9669
- freshConv._harnessMessages = freshConv.messages;
9670
- }
9671
- freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
9672
- freshConv.runtimeRunId = execution.latestRunId || freshConv.runtimeRunId;
9732
+ applyTurnMetadata(freshConv, {
9733
+ latestRunId: execution.latestRunId,
9734
+ contextTokens: execution.runContextTokens,
9735
+ contextWindow: execution.runContextWindow,
9736
+ continuation: !!callbackNeedsContinuation,
9737
+ continuationMessages: execution.runContinuationMessages,
9738
+ harnessMessages: callbackNeedsContinuation ? execution.runHarnessMessages : void 0,
9739
+ toolResultArchive: harness.getToolResultArchive(conversationId)
9740
+ }, { shouldRebuildCanonical: true, clearApprovals: false });
9673
9741
  freshConv.runningCallbackSince = void 0;
9674
- freshConv.runStatus = "idle";
9675
- if (execution.runContextTokens > 0) freshConv.contextTokens = execution.runContextTokens;
9676
- if (execution.runContextWindow > 0) freshConv.contextWindow = execution.runContextWindow;
9677
- freshConv.updatedAt = Date.now();
9678
9742
  await conversationStore.update(freshConv);
9679
9743
  if (freshConv.channelMeta && execution.draft.assistantResponse.length > 0) {
9680
9744
  const adapter = messagingAdapters.get(freshConv.channelMeta.platform);
@@ -9863,15 +9927,7 @@ ${resultBody}`,
9863
9927
  runId: null
9864
9928
  });
9865
9929
  let latestRunId = conversation.runtimeRunId ?? "";
9866
- let assistantResponse = "";
9867
- const toolTimeline = [];
9868
- const sections = [];
9869
- let currentText = "";
9870
- let currentTools = [];
9871
9930
  let checkpointedRun = false;
9872
- let runContextTokens = conversation.contextTokens ?? 0;
9873
- let runContextWindow = conversation.contextWindow ?? 0;
9874
- let resumeHarnessMessages;
9875
9931
  const normalizedCheckpoint = normalizeApprovalCheckpoint(checkpoint, conversation.messages);
9876
9932
  const baseMessages = normalizedCheckpoint.baseMessageCount != null ? conversation.messages.slice(0, normalizedCheckpoint.baseMessageCount) : [];
9877
9933
  const fullCheckpointMessages = [...baseMessages, ...normalizedCheckpoint.checkpointMessages];
@@ -9901,133 +9957,99 @@ ${resultBody}`,
9901
9957
  }
9902
9958
  }
9903
9959
  const fullCheckpointWithResults = resumeToolResultMsg ? [...fullCheckpointMessages, resumeToolResultMsg] : fullCheckpointMessages;
9960
+ let draftRef;
9961
+ let execution;
9904
9962
  try {
9905
- for await (const event of harness.continueFromToolResult({
9906
- messages: fullCheckpointMessages,
9907
- toolResults,
9908
- conversationId,
9909
- abortSignal: abortController.signal
9910
- })) {
9911
- if (event.type === "run:started") {
9912
- latestRunId = event.runId;
9913
- runOwners.set(event.runId, conversation.ownerId);
9914
- runConversations.set(event.runId, conversationId);
9915
- const active = activeConversationRuns.get(conversationId);
9916
- if (active && active.abortController === abortController) {
9917
- active.runId = event.runId;
9918
- }
9919
- }
9920
- if (event.type === "model:chunk") {
9921
- if (currentTools.length > 0) {
9922
- sections.push({ type: "tools", content: currentTools });
9923
- currentTools = [];
9924
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
9925
- assistantResponse += " ";
9963
+ execution = await executeConversationTurn({
9964
+ harness,
9965
+ events: harness.continueFromToolResult({
9966
+ messages: fullCheckpointMessages,
9967
+ toolResults,
9968
+ conversationId,
9969
+ abortSignal: abortController.signal
9970
+ }),
9971
+ initialContextTokens: conversation.contextTokens ?? 0,
9972
+ initialContextWindow: conversation.contextWindow ?? 0,
9973
+ onEvent: async (event, draft2) => {
9974
+ draftRef = draft2;
9975
+ if (event.type === "run:started") {
9976
+ latestRunId = event.runId;
9977
+ runOwners.set(event.runId, conversation.ownerId);
9978
+ runConversations.set(event.runId, conversationId);
9979
+ const active = activeConversationRuns.get(conversationId);
9980
+ if (active && active.abortController === abortController) {
9981
+ active.runId = event.runId;
9926
9982
  }
9927
9983
  }
9928
- assistantResponse += event.content;
9929
- currentText += event.content;
9930
- }
9931
- if (event.type === "tool:started") {
9932
- if (currentText.length > 0) {
9933
- sections.push({ type: "text", content: currentText });
9934
- currentText = "";
9984
+ if (event.type === "tool:approval:required") {
9985
+ const toolText = `- approval required \`${event.tool}\``;
9986
+ draft2.toolTimeline.push(toolText);
9987
+ draft2.currentTools.push(toolText);
9935
9988
  }
9936
- const toolText = `- start \`${event.tool}\``;
9937
- toolTimeline.push(toolText);
9938
- currentTools.push(toolText);
9939
- }
9940
- if (event.type === "tool:completed") {
9941
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
9942
- toolTimeline.push(toolText);
9943
- currentTools.push(toolText);
9944
- }
9945
- if (event.type === "tool:error") {
9946
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
9947
- toolTimeline.push(toolText);
9948
- currentTools.push(toolText);
9949
- }
9950
- if (event.type === "tool:approval:required") {
9951
- const toolText = `- approval required \`${event.tool}\``;
9952
- toolTimeline.push(toolText);
9953
- currentTools.push(toolText);
9954
- }
9955
- if (event.type === "tool:approval:checkpoint") {
9956
- const conv = await conversationStore.get(conversationId);
9957
- if (conv) {
9958
- conv.pendingApprovals = buildApprovalCheckpoints({
9959
- approvals: event.approvals,
9960
- runId: latestRunId,
9961
- checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
9962
- baseMessageCount: 0,
9963
- pendingToolCalls: event.pendingToolCalls
9964
- });
9965
- conv.updatedAt = Date.now();
9966
- await conversationStore.update(conv);
9967
- if (conv.channelMeta?.platform === "telegram") {
9968
- const tgAdapter = messagingAdapters.get("telegram");
9969
- if (tgAdapter) {
9970
- const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
9971
- conv.channelMeta.platformThreadId,
9972
- conv.channelMeta.channelId
9973
- );
9974
- void tgAdapter.sendApprovalRequest(
9975
- conv.channelMeta.channelId,
9976
- event.approvals.map((a) => ({ approvalId: a.approvalId, tool: a.tool, input: a.input })),
9977
- { message_thread_id: messageThreadId }
9978
- ).catch(() => {
9979
- });
9989
+ if (event.type === "tool:approval:checkpoint") {
9990
+ const conv = await conversationStore.get(conversationId);
9991
+ if (conv) {
9992
+ conv.pendingApprovals = buildApprovalCheckpoints({
9993
+ approvals: event.approvals,
9994
+ runId: latestRunId,
9995
+ checkpointMessages: [...fullCheckpointWithResults, ...event.checkpointMessages],
9996
+ baseMessageCount: 0,
9997
+ pendingToolCalls: event.pendingToolCalls
9998
+ });
9999
+ conv.updatedAt = Date.now();
10000
+ await conversationStore.update(conv);
10001
+ if (conv.channelMeta?.platform === "telegram") {
10002
+ const tgAdapter = messagingAdapters.get("telegram");
10003
+ if (tgAdapter) {
10004
+ const messageThreadId = parseTelegramMessageThreadIdFromPlatformThreadId(
10005
+ conv.channelMeta.platformThreadId,
10006
+ conv.channelMeta.channelId
10007
+ );
10008
+ void tgAdapter.sendApprovalRequest(
10009
+ conv.channelMeta.channelId,
10010
+ event.approvals.map((a) => ({ approvalId: a.approvalId, tool: a.tool, input: a.input })),
10011
+ { message_thread_id: messageThreadId }
10012
+ ).catch(() => {
10013
+ });
10014
+ }
9980
10015
  }
9981
10016
  }
10017
+ checkpointedRun = true;
9982
10018
  }
9983
- checkpointedRun = true;
9984
- }
9985
- if (event.type === "run:completed") {
9986
- if (assistantResponse.length === 0 && event.result.response) {
9987
- assistantResponse = event.result.response;
9988
- }
9989
- runContextTokens = event.result.contextTokens ?? runContextTokens;
9990
- runContextWindow = event.result.contextWindow ?? runContextWindow;
9991
- if (event.result.continuationMessages) {
9992
- resumeHarnessMessages = event.result.continuationMessages;
9993
- }
9994
- }
9995
- if (event.type === "run:error") {
9996
- assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
10019
+ await telemetry.emit(event);
10020
+ broadcastEvent(conversationId, event);
10021
+ emitBrowserStatusIfActive(conversationId, event);
9997
10022
  }
9998
- await telemetry.emit(event);
9999
- broadcastEvent(conversationId, event);
10000
- emitBrowserStatusIfActive(conversationId, event);
10001
- }
10023
+ });
10024
+ flushTurnDraft(execution.draft);
10025
+ latestRunId = execution.latestRunId || latestRunId;
10002
10026
  } catch (err) {
10003
10027
  console.error("[resume-run] error:", err instanceof Error ? err.message : err);
10004
- assistantResponse = assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
10005
- }
10006
- if (currentTools.length > 0) {
10007
- sections.push({ type: "tools", content: currentTools });
10008
- }
10009
- if (currentText.length > 0) {
10010
- sections.push({ type: "text", content: currentText });
10028
+ if (draftRef) {
10029
+ draftRef.assistantResponse = draftRef.assistantResponse || `[Error: ${err instanceof Error ? err.message : "Unknown error"}]`;
10030
+ flushTurnDraft(draftRef);
10031
+ }
10011
10032
  }
10033
+ const draft = execution?.draft ?? draftRef ?? createTurnDraftState();
10012
10034
  if (!checkpointedRun) {
10013
10035
  const conv = await conversationStore.get(conversationId);
10014
10036
  if (conv) {
10015
- const prevMessages = conv.messages;
10016
- const hasAssistantContent = assistantResponse.length > 0 || toolTimeline.length > 0 || sections.length > 0;
10037
+ const hasAssistantContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0;
10017
10038
  if (hasAssistantContent) {
10039
+ const prevMessages = conv.messages;
10018
10040
  const lastMsg = prevMessages[prevMessages.length - 1];
10019
10041
  if (lastMsg && lastMsg.role === "assistant" && lastMsg.metadata) {
10020
10042
  const existingToolActivity = lastMsg.metadata.toolActivity;
10021
10043
  const existingSections = lastMsg.metadata.sections;
10022
10044
  const mergedTimeline = [
10023
10045
  ...Array.isArray(existingToolActivity) ? existingToolActivity : [],
10024
- ...toolTimeline
10046
+ ...draft.toolTimeline
10025
10047
  ];
10026
10048
  const mergedSections = [
10027
10049
  ...Array.isArray(existingSections) ? existingSections : [],
10028
- ...sections
10050
+ ...draft.sections
10029
10051
  ];
10030
- const mergedText = (typeof lastMsg.content === "string" ? lastMsg.content : "") + assistantResponse;
10052
+ const mergedText = (typeof lastMsg.content === "string" ? lastMsg.content : "") + draft.assistantResponse;
10031
10053
  conv.messages = [
10032
10054
  ...prevMessages.slice(0, -1),
10033
10055
  {
@@ -10044,23 +10066,18 @@ ${resultBody}`,
10044
10066
  ...prevMessages,
10045
10067
  {
10046
10068
  role: "assistant",
10047
- content: assistantResponse,
10048
- metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
10069
+ content: draft.assistantResponse,
10070
+ metadata: buildAssistantMetadata(draft)
10049
10071
  }
10050
10072
  ];
10051
10073
  }
10052
10074
  }
10053
- if (resumeHarnessMessages) {
10054
- conv._harnessMessages = resumeHarnessMessages;
10055
- } else {
10056
- conv._harnessMessages = conv.messages;
10057
- }
10058
- conv.runtimeRunId = latestRunId || conv.runtimeRunId;
10059
- conv.pendingApprovals = [];
10060
- conv.runStatus = "idle";
10061
- if (runContextTokens > 0) conv.contextTokens = runContextTokens;
10062
- if (runContextWindow > 0) conv.contextWindow = runContextWindow;
10063
- conv.updatedAt = Date.now();
10075
+ applyTurnMetadata(conv, {
10076
+ latestRunId,
10077
+ contextTokens: execution?.runContextTokens ?? 0,
10078
+ contextWindow: execution?.runContextWindow ?? 0,
10079
+ harnessMessages: execution?.runHarnessMessages
10080
+ }, { shouldRebuildCanonical: true });
10064
10081
  await conversationStore.update(conv);
10065
10082
  }
10066
10083
  } else {
@@ -10201,7 +10218,7 @@ ${resultBody}`,
10201
10218
  conversationId,
10202
10219
  messages: historyMessages,
10203
10220
  files: input2.files,
10204
- parameters: {
10221
+ parameters: withToolResultArchiveParam({
10205
10222
  ...input2.metadata ? {
10206
10223
  __messaging_platform: input2.metadata.platform,
10207
10224
  __messaging_sender_id: input2.metadata.sender.id,
@@ -10209,7 +10226,7 @@ ${resultBody}`,
10209
10226
  __messaging_thread_id: input2.metadata.threadId
10210
10227
  } : {},
10211
10228
  __activeConversationId: conversationId
10212
- }
10229
+ }, latestConversation ?? { _toolResultArchive: {} })
10213
10230
  };
10214
10231
  try {
10215
10232
  const execution = await executeConversationTurn({
@@ -10302,31 +10319,31 @@ ${resultBody}`,
10302
10319
  flushTurnDraft(draft);
10303
10320
  if (!checkpointedRun) {
10304
10321
  await updateConversation((c) => {
10305
- if (runContinuation2 && runContinuationMessages) {
10306
- c._continuationMessages = runContinuationMessages;
10307
- } else {
10308
- c._continuationMessages = void 0;
10322
+ if (!(runContinuation2 && runContinuationMessages)) {
10309
10323
  c.messages = buildMessages();
10310
10324
  }
10311
- if (runContinuationMessages) {
10312
- c._harnessMessages = runContinuationMessages;
10313
- } else if (shouldRebuildCanonical) {
10314
- c._harnessMessages = c.messages;
10315
- } else {
10316
- c._harnessMessages = c.messages;
10317
- }
10318
- c.runtimeRunId = latestRunId || c.runtimeRunId;
10319
- c.pendingApprovals = [];
10320
- c.runStatus = "idle";
10321
- if (runContextTokens > 0) c.contextTokens = runContextTokens;
10322
- if (runContextWindow > 0) c.contextWindow = runContextWindow;
10325
+ applyTurnMetadata(c, {
10326
+ latestRunId,
10327
+ contextTokens: runContextTokens,
10328
+ contextWindow: runContextWindow,
10329
+ continuation: runContinuation2,
10330
+ continuationMessages: runContinuationMessages,
10331
+ harnessMessages: runContinuationMessages,
10332
+ toolResultArchive: harness.getToolResultArchive(conversationId)
10333
+ }, { shouldRebuildCanonical: true });
10323
10334
  });
10324
10335
  } else {
10325
10336
  await updateConversation((c) => {
10326
- if (shouldRebuildCanonical && !c._harnessMessages?.length) {
10327
- c._harnessMessages = c.messages;
10328
- }
10329
- c.runStatus = "idle";
10337
+ applyTurnMetadata(c, {
10338
+ latestRunId: "",
10339
+ contextTokens: 0,
10340
+ contextWindow: 0,
10341
+ toolResultArchive: harness.getToolResultArchive(conversationId)
10342
+ }, {
10343
+ clearContinuation: false,
10344
+ clearApprovals: false,
10345
+ shouldRebuildCanonical: shouldRebuildCanonical && !c._harnessMessages?.length
10346
+ });
10330
10347
  });
10331
10348
  }
10332
10349
  finishConversationStream(conversationId);
@@ -10477,7 +10494,7 @@ ${resultBody}`,
10477
10494
  return typeof headerValue === "string" && headerValue === internalSecret;
10478
10495
  };
10479
10496
  const MAX_CONTINUATION_COUNT = 20;
10480
- async function* runContinuation(conversationId) {
10497
+ async function runContinuation(conversationId, onYield) {
10481
10498
  const conversation = await conversationStore.get(conversationId);
10482
10499
  if (!conversation) return;
10483
10500
  if (Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0) return;
@@ -10515,132 +10532,69 @@ ${resultBody}`,
10515
10532
  }
10516
10533
  try {
10517
10534
  if (conversation.parentConversationId) {
10518
- yield* runSubagentContinuation(conversationId, conversation, continuationMessages);
10535
+ for await (const event of runSubagentContinuation(conversationId, conversation, continuationMessages)) {
10536
+ if (onYield) await onYield(event);
10537
+ }
10519
10538
  } else {
10520
- yield* runChatContinuation(conversationId, conversation, continuationMessages);
10539
+ await runChatContinuation(conversationId, conversation, continuationMessages, onYield);
10521
10540
  }
10522
10541
  } finally {
10523
10542
  activeConversationRuns.delete(conversationId);
10524
10543
  finishConversationStream(conversationId);
10525
10544
  }
10526
10545
  }
10527
- async function* runChatContinuation(conversationId, conversation, continuationMessages) {
10528
- let assistantResponse = "";
10529
- let latestRunId = conversation.runtimeRunId ?? "";
10530
- const toolTimeline = [];
10531
- const sections = [];
10532
- let currentTools = [];
10533
- let currentText = "";
10534
- let runContextTokens = conversation.contextTokens ?? 0;
10535
- let runContextWindow = conversation.contextWindow ?? 0;
10536
- let nextContinuationMessages;
10537
- let nextHarnessMessages;
10538
- for await (const event of harness.runWithTelemetry({
10539
- conversationId,
10540
- parameters: withToolResultArchiveParam({
10541
- __activeConversationId: conversationId,
10542
- __ownerId: conversation.ownerId
10543
- }, conversation),
10544
- messages: continuationMessages,
10545
- abortSignal: activeConversationRuns.get(conversationId)?.abortController.signal
10546
- })) {
10547
- if (event.type === "run:started") {
10548
- latestRunId = event.runId;
10549
- runOwners.set(event.runId, conversation.ownerId);
10550
- runConversations.set(event.runId, conversationId);
10551
- const active = activeConversationRuns.get(conversationId);
10552
- if (active) active.runId = event.runId;
10553
- }
10554
- if (event.type === "model:chunk") {
10555
- if (currentTools.length > 0) {
10556
- sections.push({ type: "tools", content: currentTools });
10557
- currentTools = [];
10558
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
10559
- assistantResponse += " ";
10560
- }
10561
- }
10562
- assistantResponse += event.content;
10563
- currentText += event.content;
10564
- }
10565
- if (event.type === "tool:started") {
10566
- if (currentText.length > 0) {
10567
- sections.push({ type: "text", content: currentText });
10568
- currentText = "";
10569
- }
10570
- const toolText = `- start \`${event.tool}\``;
10571
- toolTimeline.push(toolText);
10572
- currentTools.push(toolText);
10573
- }
10574
- if (event.type === "tool:completed") {
10575
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
10576
- toolTimeline.push(toolText);
10577
- currentTools.push(toolText);
10578
- }
10579
- if (event.type === "tool:error") {
10580
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
10581
- toolTimeline.push(toolText);
10582
- currentTools.push(toolText);
10583
- }
10584
- if (event.type === "run:completed") {
10585
- runContextTokens = event.result.contextTokens ?? runContextTokens;
10586
- runContextWindow = event.result.contextWindow ?? runContextWindow;
10587
- if (event.result.continuation && event.result.continuationMessages) {
10588
- nextContinuationMessages = event.result.continuationMessages;
10589
- }
10590
- if (event.result.continuationMessages) {
10591
- nextHarnessMessages = event.result.continuationMessages;
10592
- }
10593
- if (!assistantResponse && event.result.response) {
10594
- assistantResponse = event.result.response;
10546
+ async function runChatContinuation(conversationId, conversation, continuationMessages, onYield) {
10547
+ const execution = await executeConversationTurn({
10548
+ harness,
10549
+ runInput: {
10550
+ conversationId,
10551
+ parameters: withToolResultArchiveParam({
10552
+ __activeConversationId: conversationId,
10553
+ __ownerId: conversation.ownerId
10554
+ }, conversation),
10555
+ messages: continuationMessages,
10556
+ abortSignal: activeConversationRuns.get(conversationId)?.abortController.signal
10557
+ },
10558
+ initialContextTokens: conversation.contextTokens ?? 0,
10559
+ initialContextWindow: conversation.contextWindow ?? 0,
10560
+ onEvent: async (event) => {
10561
+ if (event.type === "run:started") {
10562
+ runOwners.set(event.runId, conversation.ownerId);
10563
+ runConversations.set(event.runId, conversationId);
10564
+ const active = activeConversationRuns.get(conversationId);
10565
+ if (active) active.runId = event.runId;
10595
10566
  }
10567
+ await telemetry.emit(event);
10568
+ broadcastEvent(conversationId, event);
10569
+ if (onYield) await onYield(event);
10596
10570
  }
10597
- if (event.type === "run:error") {
10598
- assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
10599
- }
10600
- await telemetry.emit(event);
10601
- broadcastEvent(conversationId, event);
10602
- yield event;
10603
- }
10604
- if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
10605
- if (currentText.length > 0) sections.push({ type: "text", content: currentText });
10571
+ });
10572
+ flushTurnDraft(execution.draft);
10606
10573
  const freshConv = await conversationStore.get(conversationId);
10607
10574
  if (!freshConv) return;
10608
- const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
10609
- const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
10610
- toolActivity: [...toolTimeline],
10611
- sections: sections.length > 0 ? sections : void 0
10612
- } : void 0;
10613
- if (nextContinuationMessages) {
10614
- if (hasContent) {
10615
- freshConv.messages = [
10616
- ...freshConv.messages,
10617
- { role: "assistant", content: assistantResponse, metadata: assistantMetadata }
10618
- ];
10619
- }
10620
- freshConv._continuationMessages = nextContinuationMessages;
10575
+ const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
10576
+ if (hasContent) {
10577
+ freshConv.messages = [
10578
+ ...freshConv.messages,
10579
+ {
10580
+ role: "assistant",
10581
+ content: execution.draft.assistantResponse,
10582
+ metadata: buildAssistantMetadata(execution.draft)
10583
+ }
10584
+ ];
10585
+ }
10586
+ applyTurnMetadata(freshConv, {
10587
+ latestRunId: execution.latestRunId,
10588
+ contextTokens: execution.runContextTokens,
10589
+ contextWindow: execution.runContextWindow,
10590
+ continuation: execution.runContinuation,
10591
+ continuationMessages: execution.runContinuationMessages,
10592
+ harnessMessages: execution.runHarnessMessages,
10593
+ toolResultArchive: harness.getToolResultArchive(conversationId)
10594
+ }, { shouldRebuildCanonical: true });
10595
+ if (execution.runContinuation) {
10621
10596
  freshConv._continuationCount = conversation._continuationCount;
10622
- } else {
10623
- if (hasContent) {
10624
- freshConv.messages = [
10625
- ...freshConv.messages,
10626
- { role: "assistant", content: assistantResponse, metadata: assistantMetadata }
10627
- ];
10628
- }
10629
- freshConv._continuationMessages = void 0;
10630
- freshConv._continuationCount = void 0;
10631
10597
  }
10632
- if (nextHarnessMessages) {
10633
- freshConv._harnessMessages = nextHarnessMessages;
10634
- } else {
10635
- freshConv._harnessMessages = freshConv.messages;
10636
- }
10637
- freshConv._toolResultArchive = harness.getToolResultArchive(conversationId);
10638
- freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
10639
- freshConv.pendingApprovals = [];
10640
- if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
10641
- if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
10642
- freshConv.runStatus = "idle";
10643
- freshConv.updatedAt = Date.now();
10644
10598
  await conversationStore.update(freshConv);
10645
10599
  }
10646
10600
  async function* runSubagentContinuation(conversationId, conversation, continuationMessages) {
@@ -10951,26 +10905,30 @@ ${resultBody}`,
10951
10905
  }
10952
10906
  const found = await findPendingApproval(approvalId, "local-owner");
10953
10907
  let foundConversation = found?.conversation;
10954
- let foundApproval = found?.approval;
10908
+ const foundApproval = found?.approval;
10955
10909
  if (!foundConversation || !foundApproval) {
10956
10910
  console.warn("[telegram-approval] approval not found:", approvalId);
10957
10911
  return;
10958
10912
  }
10959
- foundApproval = normalizeApprovalCheckpoint(foundApproval, foundConversation.messages);
10960
- await adapter.updateApprovalMessage(approvalId, approved ? "approved" : "denied", foundApproval.tool);
10961
- foundApproval.decision = approved ? "approved" : "denied";
10913
+ const approvalDecision = approved ? "approved" : "denied";
10914
+ await adapter.updateApprovalMessage(approvalId, approvalDecision, foundApproval.tool);
10915
+ foundConversation.pendingApprovals = (foundConversation.pendingApprovals ?? []).map(
10916
+ (approval) => approval.approvalId === approvalId ? { ...normalizeApprovalCheckpoint(approval, foundConversation.messages), decision: approvalDecision } : normalizeApprovalCheckpoint(approval, foundConversation.messages)
10917
+ );
10918
+ await conversationStore.update(foundConversation);
10962
10919
  broadcastEvent(
10963
10920
  foundConversation.conversationId,
10964
10921
  approved ? { type: "tool:approval:granted", approvalId } : { type: "tool:approval:denied", approvalId }
10965
10922
  );
10966
- const allApprovals = (foundConversation.pendingApprovals ?? []).map(
10967
- (approval) => normalizeApprovalCheckpoint(approval, foundConversation.messages)
10923
+ const refreshedConversation = await conversationStore.get(foundConversation.conversationId);
10924
+ const allApprovals = (refreshedConversation?.pendingApprovals ?? []).map(
10925
+ (approval) => normalizeApprovalCheckpoint(approval, refreshedConversation.messages)
10968
10926
  );
10969
10927
  const allDecided = allApprovals.length > 0 && allApprovals.every((a) => a.decision != null);
10970
10928
  if (!allDecided) {
10971
- await conversationStore.update(foundConversation);
10972
10929
  return;
10973
10930
  }
10931
+ foundConversation = refreshedConversation;
10974
10932
  const conversationId = foundConversation.conversationId;
10975
10933
  const checkpointRef = allApprovals[0];
10976
10934
  foundConversation.pendingApprovals = [];
@@ -11206,8 +11164,7 @@ ${resultBody}`,
11206
11164
  writeJson(response, 202, { ok: true });
11207
11165
  const work = (async () => {
11208
11166
  try {
11209
- for await (const _event of runContinuation(conversationId)) {
11210
- }
11167
+ await runContinuation(conversationId);
11211
11168
  const conv = await conversationStore.get(conversationId);
11212
11169
  if (conv?._continuationMessages?.length) {
11213
11170
  await selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`);
@@ -11818,7 +11775,7 @@ data: ${JSON.stringify(frame)}
11818
11775
  writeJson(response, 200, {
11819
11776
  conversation: {
11820
11777
  ...conversation,
11821
- messages: conversation.messages.map(normalizeMessageForClient),
11778
+ messages: conversation.messages.map(normalizeMessageForClient).filter((m) => m !== null),
11822
11779
  pendingApprovals: storedPending,
11823
11780
  _continuationMessages: void 0,
11824
11781
  _harnessMessages: void 0
@@ -12024,7 +11981,7 @@ data: ${JSON.stringify(frame)}
12024
11981
  });
12025
11982
  let eventCount = 0;
12026
11983
  try {
12027
- for await (const event of runContinuation(conversationId)) {
11984
+ await runContinuation(conversationId, async (event) => {
12028
11985
  eventCount++;
12029
11986
  let sseEvent = event;
12030
11987
  if (sseEvent.type === "run:completed") {
@@ -12037,7 +11994,7 @@ data: ${JSON.stringify(frame)}
12037
11994
  } catch {
12038
11995
  }
12039
11996
  emitBrowserStatusIfActive(conversationId, event, response);
12040
- }
11997
+ });
12041
11998
  } catch (err) {
12042
11999
  const errorEvent = {
12043
12000
  type: "run:error",
@@ -12152,18 +12109,6 @@ data: ${JSON.stringify(frame)}
12152
12109
  `[poncho] conversation="${conversationId}" history_source=${canonicalHistory.source}`
12153
12110
  );
12154
12111
  let latestRunId = conversation.runtimeRunId ?? "";
12155
- let assistantResponse = "";
12156
- const toolTimeline = [];
12157
- const sections = [];
12158
- let currentText = "";
12159
- let currentTools = [];
12160
- let runCancelled = false;
12161
- let checkpointedRun = false;
12162
- let didCompact = false;
12163
- let runContextTokens = conversation.contextTokens ?? 0;
12164
- let runContextWindow = conversation.contextWindow ?? 0;
12165
- let runContinuationMessages;
12166
- let runHarnessMessages;
12167
12112
  let userContent = messageText;
12168
12113
  if (files.length > 0) {
12169
12114
  try {
@@ -12207,6 +12152,39 @@ data: ${JSON.stringify(frame)}
12207
12152
  }
12208
12153
  }
12209
12154
  });
12155
+ const draft = createTurnDraftState();
12156
+ let checkpointedRun = false;
12157
+ let runCancelled = false;
12158
+ let runContinuationMessages;
12159
+ const buildMessages = () => {
12160
+ const draftSections = cloneSections(draft.sections);
12161
+ if (draft.currentTools.length > 0) {
12162
+ draftSections.push({ type: "tools", content: [...draft.currentTools] });
12163
+ }
12164
+ if (draft.currentText.length > 0) {
12165
+ draftSections.push({ type: "text", content: draft.currentText });
12166
+ }
12167
+ const userTurn = userContent != null ? [{ role: "user", content: userContent }] : [];
12168
+ const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
12169
+ if (!hasDraftContent) {
12170
+ return [...historyMessages, ...userTurn];
12171
+ }
12172
+ return [
12173
+ ...historyMessages,
12174
+ ...userTurn,
12175
+ {
12176
+ role: "assistant",
12177
+ content: draft.assistantResponse,
12178
+ metadata: buildAssistantMetadata(draft, draftSections)
12179
+ }
12180
+ ];
12181
+ };
12182
+ const persistDraftAssistantTurn = async () => {
12183
+ if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
12184
+ conversation.messages = buildMessages();
12185
+ conversation.updatedAt = Date.now();
12186
+ await conversationStore.update(conversation);
12187
+ };
12210
12188
  try {
12211
12189
  {
12212
12190
  conversation.messages = [...historyMessages, { role: "user", content: userContent }];
@@ -12217,38 +12195,6 @@ data: ${JSON.stringify(frame)}
12217
12195
  console.error("[poncho] Failed to persist user turn:", err);
12218
12196
  });
12219
12197
  }
12220
- const persistDraftAssistantTurn = async () => {
12221
- const draftSections = [
12222
- ...sections.map((section) => ({
12223
- type: section.type,
12224
- content: Array.isArray(section.content) ? [...section.content] : section.content
12225
- }))
12226
- ];
12227
- if (currentTools.length > 0) {
12228
- draftSections.push({ type: "tools", content: [...currentTools] });
12229
- }
12230
- if (currentText.length > 0) {
12231
- draftSections.push({ type: "text", content: currentText });
12232
- }
12233
- const hasDraftContent = assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
12234
- if (!hasDraftContent) {
12235
- return;
12236
- }
12237
- conversation.messages = [
12238
- ...historyMessages,
12239
- ...userContent != null ? [{ role: "user", content: userContent }] : [],
12240
- {
12241
- role: "assistant",
12242
- content: assistantResponse,
12243
- metadata: toolTimeline.length > 0 || draftSections.length > 0 ? {
12244
- toolActivity: [...toolTimeline],
12245
- sections: draftSections.length > 0 ? draftSections : void 0
12246
- } : void 0
12247
- }
12248
- ];
12249
- conversation.updatedAt = Date.now();
12250
- await conversationStore.update(conversation);
12251
- };
12252
12198
  let cachedRecallCorpus;
12253
12199
  const lazyRecallCorpus = async () => {
12254
12200
  if (cachedRecallCorpus) return cachedRecallCorpus;
@@ -12269,249 +12215,156 @@ data: ${JSON.stringify(frame)}
12269
12215
  console.info(`[poncho] recall corpus fetched lazily (${cachedRecallCorpus.length} items, ${(performance.now() - _rc0).toFixed(1)}ms)`);
12270
12216
  return cachedRecallCorpus;
12271
12217
  };
12272
- for await (const event of harness.runWithTelemetry({
12273
- task: messageText,
12274
- conversationId,
12275
- parameters: withToolResultArchiveParam({
12276
- ...bodyParameters ?? {},
12277
- __conversationRecallCorpus: lazyRecallCorpus,
12278
- __activeConversationId: conversationId,
12279
- __ownerId: ownerId
12280
- }, conversation),
12281
- messages: harnessMessages,
12282
- files: files.length > 0 ? files : void 0,
12283
- abortSignal: abortController.signal
12284
- })) {
12285
- if (event.type === "run:started") {
12286
- latestRunId = event.runId;
12287
- runOwners.set(event.runId, ownerId);
12288
- runConversations.set(event.runId, conversationId);
12289
- const active = activeConversationRuns.get(conversationId);
12290
- if (active && active.abortController === abortController) {
12291
- active.runId = event.runId;
12292
- }
12293
- }
12294
- if (event.type === "run:cancelled") {
12295
- runCancelled = true;
12296
- }
12297
- if (event.type === "model:chunk") {
12298
- if (currentTools.length > 0) {
12299
- sections.push({ type: "tools", content: currentTools });
12300
- currentTools = [];
12301
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
12302
- assistantResponse += " ";
12218
+ const execution = await executeConversationTurn({
12219
+ harness,
12220
+ runInput: {
12221
+ task: messageText,
12222
+ conversationId,
12223
+ parameters: withToolResultArchiveParam({
12224
+ ...bodyParameters ?? {},
12225
+ __conversationRecallCorpus: lazyRecallCorpus,
12226
+ __activeConversationId: conversationId,
12227
+ __ownerId: ownerId
12228
+ }, conversation),
12229
+ messages: harnessMessages,
12230
+ files: files.length > 0 ? files : void 0,
12231
+ abortSignal: abortController.signal
12232
+ },
12233
+ initialContextTokens: conversation.contextTokens ?? 0,
12234
+ initialContextWindow: conversation.contextWindow ?? 0,
12235
+ onEvent: async (event, eventDraft) => {
12236
+ draft.assistantResponse = eventDraft.assistantResponse;
12237
+ draft.toolTimeline = eventDraft.toolTimeline;
12238
+ draft.sections = eventDraft.sections;
12239
+ draft.currentTools = eventDraft.currentTools;
12240
+ draft.currentText = eventDraft.currentText;
12241
+ if (event.type === "run:started") {
12242
+ latestRunId = event.runId;
12243
+ runOwners.set(event.runId, ownerId);
12244
+ runConversations.set(event.runId, conversationId);
12245
+ const active = activeConversationRuns.get(conversationId);
12246
+ if (active && active.abortController === abortController) {
12247
+ active.runId = event.runId;
12303
12248
  }
12304
12249
  }
12305
- assistantResponse += event.content;
12306
- currentText += event.content;
12307
- }
12308
- if (event.type === "tool:started") {
12309
- if (currentText.length > 0) {
12310
- sections.push({ type: "text", content: currentText });
12311
- currentText = "";
12312
- }
12313
- const toolText = `- start \`${event.tool}\``;
12314
- toolTimeline.push(toolText);
12315
- currentTools.push(toolText);
12316
- }
12317
- if (event.type === "tool:completed") {
12318
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
12319
- toolTimeline.push(toolText);
12320
- currentTools.push(toolText);
12321
- }
12322
- if (event.type === "tool:error") {
12323
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
12324
- toolTimeline.push(toolText);
12325
- currentTools.push(toolText);
12326
- }
12327
- if (event.type === "compaction:completed") {
12328
- didCompact = true;
12329
- if (event.compactedMessages) {
12330
- historyMessages.length = 0;
12331
- historyMessages.push(...event.compactedMessages);
12332
- const preservedFromHistory = historyMessages.length - 1;
12333
- const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
12334
- const existingHistory = conversation.compactedHistory ?? [];
12335
- conversation.compactedHistory = [
12336
- ...existingHistory,
12337
- ...preRunMessages.slice(0, removedCount)
12338
- ];
12339
- }
12340
- }
12341
- if (event.type === "step:completed") {
12342
- await persistDraftAssistantTurn();
12343
- }
12344
- if (event.type === "tool:approval:required") {
12345
- const toolText = `- approval required \`${event.tool}\``;
12346
- toolTimeline.push(toolText);
12347
- currentTools.push(toolText);
12348
- const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
12349
- if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
12350
- conversation.pendingApprovals = [
12351
- ...existingApprovals,
12352
- {
12353
- approvalId: event.approvalId,
12354
- runId: latestRunId || conversation.runtimeRunId || "",
12355
- tool: event.tool,
12356
- toolCallId: void 0,
12357
- input: event.input ?? {},
12358
- checkpointMessages: void 0,
12359
- baseMessageCount: historyMessages.length,
12360
- pendingToolCalls: []
12361
- }
12362
- ];
12363
- conversation.updatedAt = Date.now();
12364
- await conversationStore.update(conversation);
12365
- }
12366
- await persistDraftAssistantTurn();
12367
- }
12368
- if (event.type === "tool:approval:checkpoint") {
12369
- const checkpointSections = [...sections];
12370
- if (currentTools.length > 0) {
12371
- checkpointSections.push({ type: "tools", content: [...currentTools] });
12250
+ if (event.type === "run:cancelled") {
12251
+ runCancelled = true;
12372
12252
  }
12373
- if (currentText.length > 0) {
12374
- checkpointSections.push({ type: "text", content: currentText });
12253
+ if (event.type === "compaction:completed") {
12254
+ if (event.compactedMessages) {
12255
+ historyMessages.length = 0;
12256
+ historyMessages.push(...event.compactedMessages);
12257
+ const preservedFromHistory = historyMessages.length - 1;
12258
+ const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
12259
+ const existingHistory = conversation.compactedHistory ?? [];
12260
+ conversation.compactedHistory = [
12261
+ ...existingHistory,
12262
+ ...preRunMessages.slice(0, removedCount)
12263
+ ];
12264
+ }
12375
12265
  }
12376
- conversation.messages = [
12377
- ...historyMessages,
12378
- ...userContent != null ? [{ role: "user", content: userContent }] : [],
12379
- ...assistantResponse.length > 0 || toolTimeline.length > 0 || checkpointSections.length > 0 ? [{
12380
- role: "assistant",
12381
- content: assistantResponse,
12382
- metadata: toolTimeline.length > 0 || checkpointSections.length > 0 ? { toolActivity: [...toolTimeline], sections: checkpointSections.length > 0 ? checkpointSections : void 0 } : void 0
12383
- }] : []
12384
- ];
12385
- conversation.pendingApprovals = buildApprovalCheckpoints({
12386
- approvals: event.approvals,
12387
- runId: latestRunId,
12388
- checkpointMessages: event.checkpointMessages,
12389
- baseMessageCount: historyMessages.length,
12390
- pendingToolCalls: event.pendingToolCalls
12391
- });
12392
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12393
- conversation.updatedAt = Date.now();
12394
- await conversationStore.update(conversation);
12395
- checkpointedRun = true;
12396
- }
12397
- if (event.type === "run:completed") {
12398
- if (assistantResponse.length === 0 && event.result.response) {
12399
- assistantResponse = event.result.response;
12266
+ if (event.type === "step:completed") {
12267
+ await persistDraftAssistantTurn();
12400
12268
  }
12401
- runContextTokens = event.result.contextTokens ?? runContextTokens;
12402
- runContextWindow = event.result.contextWindow ?? runContextWindow;
12403
- if (event.result.continuationMessages) {
12404
- runHarnessMessages = event.result.continuationMessages;
12269
+ if (event.type === "tool:approval:required") {
12270
+ const toolText = `- approval required \`${event.tool}\``;
12271
+ draft.toolTimeline.push(toolText);
12272
+ draft.currentTools.push(toolText);
12273
+ const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
12274
+ if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
12275
+ conversation.pendingApprovals = [
12276
+ ...existingApprovals,
12277
+ {
12278
+ approvalId: event.approvalId,
12279
+ runId: latestRunId || conversation.runtimeRunId || "",
12280
+ tool: event.tool,
12281
+ toolCallId: void 0,
12282
+ input: event.input ?? {},
12283
+ checkpointMessages: void 0,
12284
+ baseMessageCount: historyMessages.length,
12285
+ pendingToolCalls: []
12286
+ }
12287
+ ];
12288
+ conversation.updatedAt = Date.now();
12289
+ await conversationStore.update(conversation);
12290
+ }
12291
+ await persistDraftAssistantTurn();
12405
12292
  }
12406
- if (event.result.continuation && event.result.continuationMessages) {
12407
- runContinuationMessages = event.result.continuationMessages;
12408
- const intSections = [...sections];
12409
- if (currentTools.length > 0) intSections.push({ type: "tools", content: [...currentTools] });
12410
- if (currentText.length > 0) intSections.push({ type: "text", content: currentText });
12411
- const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0 || intSections.length > 0;
12412
- const intMetadata = toolTimeline.length > 0 || intSections.length > 0 ? { toolActivity: [...toolTimeline], sections: intSections.length > 0 ? intSections : void 0 } : void 0;
12413
- conversation.messages = [
12414
- ...historyMessages,
12415
- ...userContent != null ? [{ role: "user", content: userContent }] : [],
12416
- ...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: intMetadata }] : []
12417
- ];
12418
- conversation._continuationMessages = runContinuationMessages;
12419
- conversation._harnessMessages = runContinuationMessages;
12293
+ if (event.type === "tool:approval:checkpoint") {
12294
+ conversation.messages = buildMessages();
12295
+ conversation.pendingApprovals = buildApprovalCheckpoints({
12296
+ approvals: event.approvals,
12297
+ runId: latestRunId,
12298
+ checkpointMessages: event.checkpointMessages,
12299
+ baseMessageCount: historyMessages.length,
12300
+ pendingToolCalls: event.pendingToolCalls
12301
+ });
12420
12302
  conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12421
- conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12422
- if (!checkpointedRun) {
12423
- conversation.pendingApprovals = [];
12424
- }
12425
- if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
12426
- if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
12427
12303
  conversation.updatedAt = Date.now();
12428
12304
  await conversationStore.update(conversation);
12429
- if (!checkpointedRun) {
12430
- doWaitUntil(
12431
- new Promise((r) => setTimeout(r, 3e3)).then(
12432
- () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12433
- )
12434
- );
12305
+ checkpointedRun = true;
12306
+ }
12307
+ if (event.type === "run:completed") {
12308
+ if (event.result.continuation && event.result.continuationMessages) {
12309
+ runContinuationMessages = event.result.continuationMessages;
12310
+ conversation.messages = buildMessages();
12311
+ conversation._continuationMessages = runContinuationMessages;
12312
+ conversation._harnessMessages = runContinuationMessages;
12313
+ conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12314
+ conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12315
+ if (!checkpointedRun) {
12316
+ conversation.pendingApprovals = [];
12317
+ }
12318
+ if ((event.result.contextTokens ?? 0) > 0) conversation.contextTokens = event.result.contextTokens;
12319
+ if ((event.result.contextWindow ?? 0) > 0) conversation.contextWindow = event.result.contextWindow;
12320
+ conversation.updatedAt = Date.now();
12321
+ await conversationStore.update(conversation);
12322
+ if (!checkpointedRun) {
12323
+ doWaitUntil(
12324
+ new Promise((r) => setTimeout(r, 3e3)).then(
12325
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
12326
+ )
12327
+ );
12328
+ }
12435
12329
  }
12436
12330
  }
12437
- }
12438
- await telemetry.emit(event);
12439
- let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
12440
- if (sseEvent.type === "run:completed") {
12441
- const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
12442
- const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
12443
- if (hasPendingSubagents) {
12444
- sseEvent = { ...stripped, pendingSubagents: true };
12445
- } else {
12446
- sseEvent = stripped;
12331
+ await telemetry.emit(event);
12332
+ let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
12333
+ if (sseEvent.type === "run:completed") {
12334
+ const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
12335
+ const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
12336
+ if (hasPendingSubagents) {
12337
+ sseEvent = { ...stripped, pendingSubagents: true };
12338
+ } else {
12339
+ sseEvent = stripped;
12340
+ }
12447
12341
  }
12448
- }
12449
- broadcastEvent(conversationId, sseEvent);
12450
- try {
12451
- response.write(formatSseEvent(sseEvent));
12452
- } catch {
12453
- }
12454
- emitBrowserStatusIfActive(conversationId, event, response);
12455
- }
12456
- if (currentTools.length > 0) {
12457
- sections.push({ type: "tools", content: currentTools });
12458
- }
12459
- if (currentText.length > 0) {
12460
- sections.push({ type: "text", content: currentText });
12461
- }
12462
- if (!checkpointedRun && !runContinuationMessages) {
12463
- const hasAssistantContent = assistantResponse.length > 0 || toolTimeline.length > 0 || sections.length > 0;
12464
- const userTurn = userContent != null ? [{ role: "user", content: userContent }] : [];
12465
- conversation.messages = hasAssistantContent ? [
12466
- ...historyMessages,
12467
- ...userTurn,
12468
- {
12469
- role: "assistant",
12470
- content: assistantResponse,
12471
- metadata: toolTimeline.length > 0 || sections.length > 0 ? {
12472
- toolActivity: toolTimeline,
12473
- sections: sections.length > 0 ? sections : void 0
12474
- } : void 0
12342
+ broadcastEvent(conversationId, sseEvent);
12343
+ try {
12344
+ response.write(formatSseEvent(sseEvent));
12345
+ } catch {
12475
12346
  }
12476
- ] : [...historyMessages, ...userTurn];
12477
- conversation._continuationMessages = void 0;
12478
- if (runHarnessMessages) {
12479
- conversation._harnessMessages = runHarnessMessages;
12480
- } else if (shouldRebuildCanonical) {
12481
- conversation._harnessMessages = conversation.messages;
12482
- } else {
12483
- conversation._harnessMessages = conversation.messages;
12347
+ emitBrowserStatusIfActive(conversationId, event, response);
12484
12348
  }
12485
- conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
12486
- conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
12487
- conversation.pendingApprovals = [];
12488
- if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
12489
- if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
12490
- conversation.updatedAt = Date.now();
12349
+ });
12350
+ flushTurnDraft(draft);
12351
+ latestRunId = execution.latestRunId || latestRunId;
12352
+ if (!checkpointedRun && !runContinuationMessages) {
12353
+ conversation.messages = buildMessages();
12354
+ applyTurnMetadata(conversation, {
12355
+ latestRunId,
12356
+ contextTokens: execution.runContextTokens,
12357
+ contextWindow: execution.runContextWindow,
12358
+ harnessMessages: execution.runHarnessMessages,
12359
+ toolResultArchive: harness.getToolResultArchive(conversationId)
12360
+ }, { shouldRebuildCanonical });
12491
12361
  await conversationStore.update(conversation);
12492
12362
  }
12493
12363
  } catch (error) {
12364
+ flushTurnDraft(draft);
12494
12365
  if (abortController.signal.aborted || runCancelled) {
12495
- const fallbackSections = [...sections];
12496
- if (currentTools.length > 0) {
12497
- fallbackSections.push({ type: "tools", content: [...currentTools] });
12498
- }
12499
- if (currentText.length > 0) {
12500
- fallbackSections.push({ type: "text", content: currentText });
12501
- }
12502
- if (assistantResponse.length > 0 || toolTimeline.length > 0 || fallbackSections.length > 0) {
12503
- conversation.messages = [
12504
- ...historyMessages,
12505
- ...userContent != null ? [{ role: "user", content: userContent }] : [],
12506
- {
12507
- role: "assistant",
12508
- content: assistantResponse,
12509
- metadata: toolTimeline.length > 0 || fallbackSections.length > 0 ? {
12510
- toolActivity: [...toolTimeline],
12511
- sections: fallbackSections.length > 0 ? fallbackSections : void 0
12512
- } : void 0
12513
- }
12514
- ];
12366
+ if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
12367
+ conversation.messages = buildMessages();
12515
12368
  conversation.updatedAt = Date.now();
12516
12369
  await conversationStore.update(conversation);
12517
12370
  }
@@ -12532,26 +12385,8 @@ data: ${JSON.stringify(frame)}
12532
12385
  })
12533
12386
  );
12534
12387
  } catch {
12535
- const fallbackSections = [...sections];
12536
- if (currentTools.length > 0) {
12537
- fallbackSections.push({ type: "tools", content: [...currentTools] });
12538
- }
12539
- if (currentText.length > 0) {
12540
- fallbackSections.push({ type: "text", content: currentText });
12541
- }
12542
- if (assistantResponse.length > 0 || toolTimeline.length > 0 || fallbackSections.length > 0) {
12543
- conversation.messages = [
12544
- ...historyMessages,
12545
- ...userContent != null ? [{ role: "user", content: userContent }] : [],
12546
- {
12547
- role: "assistant",
12548
- content: assistantResponse,
12549
- metadata: toolTimeline.length > 0 || fallbackSections.length > 0 ? {
12550
- toolActivity: [...toolTimeline],
12551
- sections: fallbackSections.length > 0 ? fallbackSections : void 0
12552
- } : void 0
12553
- }
12554
- ];
12388
+ if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
12389
+ conversation.messages = buildMessages();
12555
12390
  conversation.updatedAt = Date.now();
12556
12391
  await conversationStore.update(conversation);
12557
12392
  }
@@ -12642,51 +12477,41 @@ ${cronJob.task}`;
12642
12477
  });
12643
12478
  const historyMessages = [...historySelection.messages];
12644
12479
  try {
12645
- const execution = await executeConversationTurn({
12480
+ const result = await runCronAgent(
12646
12481
  harness,
12647
- runInput: {
12648
- task,
12649
- conversationId: conv.conversationId,
12650
- parameters: withToolResultArchiveParam(
12651
- { __activeConversationId: conv.conversationId },
12652
- conv
12653
- ),
12654
- messages: historyMessages
12655
- },
12656
- onEvent: async (event) => {
12482
+ task,
12483
+ conv.conversationId,
12484
+ historyMessages,
12485
+ conv._toolResultArchive,
12486
+ async (event) => {
12657
12487
  await telemetry.emit(event);
12658
12488
  }
12659
- });
12660
- const assistantResponse = execution.draft.assistantResponse;
12661
- conv.messages = [
12662
- ...historyMessages,
12663
- { role: "user", content: task },
12664
- ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12665
- ];
12666
- if (execution.runHarnessMessages) {
12667
- conv._harnessMessages = execution.runHarnessMessages;
12668
- } else if (historySelection.shouldRebuildCanonical) {
12669
- conv._harnessMessages = conv.messages;
12489
+ );
12490
+ const freshConv = await conversationStore.get(conv.conversationId);
12491
+ if (freshConv) {
12492
+ appendCronTurn(freshConv, task, result);
12493
+ applyTurnMetadata(freshConv, result, {
12494
+ clearContinuation: false,
12495
+ clearApprovals: false,
12496
+ setIdle: false,
12497
+ shouldRebuildCanonical: historySelection.shouldRebuildCanonical
12498
+ });
12499
+ await conversationStore.update(freshConv);
12670
12500
  }
12671
- conv._toolResultArchive = harness.getToolResultArchive(conv.conversationId);
12672
- if (execution.runContextTokens > 0) conv.contextTokens = execution.runContextTokens;
12673
- if (execution.runContextWindow > 0) conv.contextWindow = execution.runContextWindow;
12674
- conv.updatedAt = Date.now();
12675
- await conversationStore.update(conv);
12676
- if (assistantResponse) {
12501
+ if (result.response) {
12677
12502
  try {
12678
12503
  await adapter.sendReply(
12679
12504
  {
12680
12505
  channelId: chatId,
12681
- platformThreadId: conv.channelMeta?.platformThreadId ?? chatId
12506
+ platformThreadId: (freshConv ?? conv).channelMeta?.platformThreadId ?? chatId
12682
12507
  },
12683
- assistantResponse
12508
+ result.response
12684
12509
  );
12685
12510
  } catch (sendError) {
12686
12511
  console.error(`[cron] ${jobName}: send to ${chatId} failed:`, sendError instanceof Error ? sendError.message : sendError);
12687
12512
  }
12688
12513
  }
12689
- chatResults.push({ chatId, status: "completed", steps: execution.runSteps });
12514
+ chatResults.push({ chatId, status: "completed", steps: result.steps });
12690
12515
  } catch (runError) {
12691
12516
  chatResults.push({ chatId, status: "error" });
12692
12517
  console.error(`[cron] ${jobName}: run for chat ${chatId} failed:`, runError instanceof Error ? runError.message : runError);
@@ -12712,7 +12537,6 @@ ${cronJob.task}`;
12712
12537
  cronOwnerId,
12713
12538
  `[cron] ${jobName} ${timestamp}`
12714
12539
  );
12715
- const historyMessages = [];
12716
12540
  const convId = conversation.conversationId;
12717
12541
  activeConversationRuns.set(convId, {
12718
12542
  ownerId: conversation.ownerId,
@@ -12720,117 +12544,28 @@ ${cronJob.task}`;
12720
12544
  runId: null
12721
12545
  });
12722
12546
  try {
12723
- const abortController = new AbortController();
12724
- let assistantResponse = "";
12725
- let latestRunId = "";
12726
- let runContinuationMessages;
12727
- const toolTimeline = [];
12728
- const sections = [];
12729
- let currentTools = [];
12730
- let currentText = "";
12731
- let runResult = {
12732
- status: "completed",
12733
- steps: 0
12734
- };
12735
- const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
12736
- const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
12737
- for await (const event of harness.runWithTelemetry({
12738
- task: cronJob.task,
12739
- conversationId: convId,
12740
- parameters: withToolResultArchiveParam({ __activeConversationId: convId }, conversation),
12741
- messages: historyMessages,
12742
- abortSignal: abortController.signal
12743
- })) {
12744
- if (event.type === "run:started") {
12745
- latestRunId = event.runId;
12746
- }
12747
- if (event.type === "model:chunk") {
12748
- if (currentTools.length > 0) {
12749
- sections.push({ type: "tools", content: currentTools });
12750
- currentTools = [];
12751
- if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
12752
- assistantResponse += " ";
12753
- }
12754
- }
12755
- assistantResponse += event.content;
12756
- currentText += event.content;
12757
- }
12758
- if (event.type === "tool:started") {
12759
- if (currentText.length > 0) {
12760
- sections.push({ type: "text", content: currentText });
12761
- currentText = "";
12762
- }
12763
- const toolText = `- start \`${event.tool}\``;
12764
- toolTimeline.push(toolText);
12765
- currentTools.push(toolText);
12766
- }
12767
- if (event.type === "tool:completed") {
12768
- const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
12769
- toolTimeline.push(toolText);
12770
- currentTools.push(toolText);
12771
- }
12772
- if (event.type === "tool:error") {
12773
- const toolText = `- error \`${event.tool}\`: ${event.error}`;
12774
- toolTimeline.push(toolText);
12775
- currentTools.push(toolText);
12776
- }
12777
- if (event.type === "run:completed") {
12778
- runResult = {
12779
- status: event.result.status,
12780
- steps: event.result.steps,
12781
- continuation: event.result.continuation,
12782
- contextTokens: event.result.contextTokens,
12783
- contextWindow: event.result.contextWindow,
12784
- harnessMessages: event.result.continuationMessages
12785
- };
12786
- if (event.result.continuation && event.result.continuationMessages) {
12787
- runContinuationMessages = event.result.continuationMessages;
12788
- }
12789
- if (!assistantResponse && event.result.response) {
12790
- assistantResponse = event.result.response;
12791
- }
12547
+ const result = await runCronAgent(
12548
+ harness,
12549
+ cronJob.task,
12550
+ convId,
12551
+ [],
12552
+ conversation._toolResultArchive,
12553
+ async (event) => {
12554
+ broadcastEvent(convId, event);
12555
+ await telemetry.emit(event);
12792
12556
  }
12793
- broadcastEvent(convId, event);
12794
- await telemetry.emit(event);
12795
- }
12557
+ );
12796
12558
  finishConversationStream(convId);
12797
- if (currentTools.length > 0) {
12798
- sections.push({ type: "tools", content: currentTools });
12799
- }
12800
- if (currentText.length > 0) {
12801
- sections.push({ type: "text", content: currentText });
12802
- currentText = "";
12803
- }
12804
- const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
12805
- const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
12806
- toolActivity: [...toolTimeline],
12807
- sections: sections.length > 0 ? sections : void 0
12808
- } : void 0;
12809
- const messages = [
12810
- ...historyMessages,
12811
- { role: "user", content: cronJob.task },
12812
- ...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: assistantMetadata }] : []
12813
- ];
12814
12559
  const freshConv = await conversationStore.get(convId);
12815
12560
  if (freshConv) {
12816
- freshConv.messages = messages;
12817
- if (runContinuationMessages) {
12818
- freshConv._continuationMessages = runContinuationMessages;
12819
- } else {
12820
- freshConv._continuationMessages = void 0;
12821
- freshConv._continuationCount = void 0;
12822
- }
12823
- if (runResult.harnessMessages) {
12824
- freshConv._harnessMessages = runResult.harnessMessages;
12825
- }
12826
- freshConv._toolResultArchive = harness.getToolResultArchive(convId);
12827
- freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
12828
- if (runResult.contextTokens) freshConv.contextTokens = runResult.contextTokens;
12829
- if (runResult.contextWindow) freshConv.contextWindow = runResult.contextWindow;
12830
- freshConv.updatedAt = Date.now();
12561
+ freshConv.messages = buildCronMessages(cronJob.task, [], result);
12562
+ applyTurnMetadata(freshConv, result, {
12563
+ clearApprovals: false,
12564
+ setIdle: false
12565
+ });
12831
12566
  await conversationStore.update(freshConv);
12832
12567
  }
12833
- if (runResult.continuation) {
12568
+ if (result.continuation) {
12834
12569
  const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(
12835
12570
  (err) => console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
12836
12571
  );
@@ -12844,10 +12579,10 @@ ${cronJob.task}`;
12844
12579
  }
12845
12580
  writeJson(response, 200, {
12846
12581
  conversationId: convId,
12847
- status: runResult.status,
12848
- response: assistantResponse.slice(0, 500),
12582
+ status: "completed",
12583
+ response: result.response.slice(0, 500),
12849
12584
  duration: Date.now() - start,
12850
- steps: runResult.steps
12585
+ steps: result.steps
12851
12586
  });
12852
12587
  } finally {
12853
12588
  activeConversationRuns.delete(convId);
@@ -12909,26 +12644,21 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}`;
12909
12644
  if (channelMeta) {
12910
12645
  const adapter = messagingAdapters.get(channelMeta.platform);
12911
12646
  if (adapter && originConv) {
12912
- const historyMessages = originConv.messages ?? [];
12913
- let assistantResponse = "";
12914
- for await (const event of harness.runWithTelemetry({
12915
- task: framedMessage,
12916
- conversationId: originConv.conversationId,
12917
- parameters: { __activeConversationId: originConv.conversationId },
12918
- messages: historyMessages
12919
- })) {
12920
- if (event.type === "model:chunk") {
12921
- assistantResponse += event.content;
12922
- }
12923
- }
12924
- if (assistantResponse) {
12647
+ const result = await runCronAgent(
12648
+ harness,
12649
+ framedMessage,
12650
+ originConv.conversationId,
12651
+ originConv.messages ?? [],
12652
+ originConv._toolResultArchive
12653
+ );
12654
+ if (result.response) {
12925
12655
  try {
12926
12656
  await adapter.sendReply(
12927
12657
  {
12928
12658
  channelId: channelMeta.channelId,
12929
12659
  platformThreadId: channelMeta.platformThreadId ?? channelMeta.channelId
12930
12660
  },
12931
- assistantResponse
12661
+ result.response
12932
12662
  );
12933
12663
  } catch (sendError) {
12934
12664
  console.error(`[reminder] Send to ${channelMeta.platform} failed:`, sendError instanceof Error ? sendError.message : sendError);
@@ -12936,12 +12666,12 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}`;
12936
12666
  }
12937
12667
  const freshConv = await conversationStore.get(originConv.conversationId);
12938
12668
  if (freshConv) {
12939
- freshConv.messages = [
12940
- ...historyMessages,
12941
- { role: "user", content: framedMessage },
12942
- ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12943
- ];
12944
- freshConv.updatedAt = Date.now();
12669
+ appendCronTurn(freshConv, framedMessage, result);
12670
+ applyTurnMetadata(freshConv, result, {
12671
+ clearContinuation: false,
12672
+ clearApprovals: false,
12673
+ setIdle: false
12674
+ });
12945
12675
  await conversationStore.update(freshConv);
12946
12676
  }
12947
12677
  }
@@ -12952,24 +12682,15 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}`;
12952
12682
  `[reminder] ${reminder.task.slice(0, 80)} ${timestamp}`
12953
12683
  );
12954
12684
  const convId = conversation.conversationId;
12955
- let assistantResponse = "";
12956
- for await (const event of harness.runWithTelemetry({
12957
- task: framedMessage,
12958
- conversationId: convId,
12959
- parameters: { __activeConversationId: convId },
12960
- messages: []
12961
- })) {
12962
- if (event.type === "model:chunk") {
12963
- assistantResponse += event.content;
12964
- }
12965
- }
12685
+ const result = await runCronAgent(harness, framedMessage, convId, []);
12966
12686
  const freshConv = await conversationStore.get(convId);
12967
12687
  if (freshConv) {
12968
- freshConv.messages = [
12969
- { role: "user", content: framedMessage },
12970
- ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
12971
- ];
12972
- freshConv.updatedAt = Date.now();
12688
+ freshConv.messages = buildCronMessages(framedMessage, [], result);
12689
+ applyTurnMetadata(freshConv, result, {
12690
+ clearContinuation: false,
12691
+ clearApprovals: false,
12692
+ setIdle: false
12693
+ });
12973
12694
  await conversationStore.update(freshConv);
12974
12695
  }
12975
12696
  }
@@ -13044,39 +12765,6 @@ var startDevServer = async (port, options) => {
13044
12765
  await checkVercelCronDrift(workingDir);
13045
12766
  const { Cron } = await import("croner");
13046
12767
  let activeJobs = [];
13047
- const runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
13048
- const execution = await executeConversationTurn({
13049
- harness: harnessRef,
13050
- runInput: {
13051
- task,
13052
- conversationId,
13053
- parameters: {
13054
- __activeConversationId: conversationId,
13055
- [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
13056
- },
13057
- messages: historyMessages
13058
- },
13059
- onEvent
13060
- });
13061
- flushTurnDraft(execution.draft);
13062
- const hasContent = execution.draft.assistantResponse.length > 0 || execution.draft.toolTimeline.length > 0;
13063
- const assistantMetadata = buildAssistantMetadata(execution.draft);
13064
- return {
13065
- response: execution.draft.assistantResponse,
13066
- steps: execution.runSteps,
13067
- assistantMetadata,
13068
- hasContent,
13069
- contextTokens: execution.runContextTokens,
13070
- contextWindow: execution.runContextWindow,
13071
- harnessMessages: execution.runHarnessMessages,
13072
- toolResultArchive: harnessRef.getToolResultArchive(conversationId)
13073
- };
13074
- };
13075
- const buildCronMessages = (task, historyMessages, result) => [
13076
- ...historyMessages,
13077
- { role: "user", content: task },
13078
- ...result.hasContent ? [{ role: "assistant", content: result.response, metadata: result.assistantMetadata }] : []
13079
- ];
13080
12768
  const scheduleCronJobs = (jobs) => {
13081
12769
  for (const job of activeJobs) {
13082
12770
  job.stop();
@@ -13153,18 +12841,13 @@ ${config.task}`;
13153
12841
  handler._finishConversationStream?.(convId);
13154
12842
  const freshConv = await store.get(convId);
13155
12843
  if (freshConv) {
13156
- freshConv.messages = buildCronMessages(task, historyMessages, result);
13157
- if (result.harnessMessages) {
13158
- freshConv._harnessMessages = result.harnessMessages;
13159
- } else if (historySelection.shouldRebuildCanonical) {
13160
- freshConv._harnessMessages = freshConv.messages;
13161
- }
13162
- if (result.toolResultArchive) {
13163
- freshConv._toolResultArchive = result.toolResultArchive;
13164
- }
13165
- if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
13166
- if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
13167
- freshConv.updatedAt = Date.now();
12844
+ appendCronTurn(freshConv, task, result);
12845
+ applyTurnMetadata(freshConv, result, {
12846
+ clearContinuation: false,
12847
+ clearApprovals: false,
12848
+ setIdle: false,
12849
+ shouldRebuildCanonical: historySelection.shouldRebuildCanonical
12850
+ });
13168
12851
  await store.update(freshConv);
13169
12852
  if (result.response) {
13170
12853
  try {
@@ -13234,15 +12917,11 @@ ${config.task}`;
13234
12917
  const freshConv = await store.get(cronConvId);
13235
12918
  if (freshConv) {
13236
12919
  freshConv.messages = buildCronMessages(config.task, [], result);
13237
- if (result.harnessMessages) {
13238
- freshConv._harnessMessages = result.harnessMessages;
13239
- }
13240
- if (result.toolResultArchive) {
13241
- freshConv._toolResultArchive = result.toolResultArchive;
13242
- }
13243
- if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
13244
- if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
13245
- freshConv.updatedAt = Date.now();
12920
+ applyTurnMetadata(freshConv, result, {
12921
+ clearContinuation: false,
12922
+ clearApprovals: false,
12923
+ setIdle: false
12924
+ });
13246
12925
  await store.update(freshConv);
13247
12926
  }
13248
12927
  const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
@@ -13396,7 +13075,7 @@ var runInteractive = async (workingDir, params) => {
13396
13075
  await harness.initialize();
13397
13076
  const identity = await ensureAgentIdentity2(workingDir);
13398
13077
  try {
13399
- const { runInteractiveInk } = await import("./run-interactive-ink-R4PHKIQR.js");
13078
+ const { runInteractiveInk } = await import("./run-interactive-ink-XQM7OIZT.js");
13400
13079
  await runInteractiveInk({
13401
13080
  harness,
13402
13081
  params,