@poncho-ai/cli 0.30.7 → 0.31.0

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.
@@ -3174,10 +3174,11 @@ var getWebUiClientScript = (markedSource2) => `
3174
3174
  } else if (willStream) {
3175
3175
  setStreaming(true);
3176
3176
  } else if (payload.needsContinuation && !payload.conversation.parentConversationId) {
3177
- console.log("[poncho] Detected orphaned continuation for", conversationId, "\u2014 auto-resuming");
3177
+ console.log("[poncho] Detected orphaned continuation for", conversationId, "\u2014 auto-resuming via /continue");
3178
3178
  (async () => {
3179
3179
  try {
3180
3180
  setStreaming(true);
3181
+ state.activeStreamConversationId = conversationId;
3181
3182
  var localMsgs = state.activeMessages || [];
3182
3183
  var contAssistant = {
3183
3184
  role: "assistant",
@@ -3194,30 +3195,36 @@ var getWebUiClientScript = (markedSource2) => `
3194
3195
  state.activeMessages = localMsgs;
3195
3196
  state._activeStreamMessages = localMsgs;
3196
3197
  renderMessages(localMsgs, true);
3198
+
3197
3199
  var contResp = await fetch(
3198
- "/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
3200
+ "/api/conversations/" + encodeURIComponent(conversationId) + "/continue",
3199
3201
  {
3200
3202
  method: "POST",
3201
3203
  credentials: "include",
3202
3204
  headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
3203
- body: JSON.stringify({ continuation: true }),
3204
3205
  },
3205
3206
  );
3206
3207
  if (!contResp.ok || !contResp.body) {
3207
- contAssistant._error = "Failed to resume \u2014 reload to retry";
3208
+ // Server already claimed the continuation (safety net). Poll for completion.
3209
+ await pollUntilRunIdle(conversationId);
3208
3210
  setStreaming(false);
3209
3211
  renderMessages(localMsgs, false);
3210
3212
  return;
3211
3213
  }
3212
- state.activeStreamConversationId = conversationId;
3214
+
3213
3215
  var contReader = contResp.body.getReader();
3214
3216
  var contDecoder = new TextDecoder();
3215
3217
  var contBuffer = "";
3218
+ var gotStreamEnd = false;
3216
3219
  while (true) {
3217
3220
  var chunk = await contReader.read();
3218
3221
  if (chunk.done) break;
3219
3222
  contBuffer += contDecoder.decode(chunk.value, { stream: true });
3220
3223
  contBuffer = parseSseChunk(contBuffer, function(evtName, evtPayload) {
3224
+ if (evtName === "stream:end") {
3225
+ gotStreamEnd = true;
3226
+ return;
3227
+ }
3221
3228
  if (evtName === "model:chunk" && evtPayload.content) {
3222
3229
  contAssistant.content = (contAssistant.content || "") + evtPayload.content;
3223
3230
  contAssistant._currentText += evtPayload.content;
@@ -3248,14 +3255,14 @@ var getWebUiClientScript = (markedSource2) => `
3248
3255
  if (evtName === "run:error") {
3249
3256
  contAssistant._error = evtPayload.error?.message || "Something went wrong";
3250
3257
  }
3251
- if (evtName === "run:completed" && evtPayload.result?.continuation === true) {
3252
- // Another continuation needed \u2014 reload to pick it up
3253
- loadConversation(conversationId).catch(function() {});
3254
- }
3255
3258
  }
3256
3259
  renderMessages(localMsgs, true);
3257
3260
  });
3258
3261
  }
3262
+ if (gotStreamEnd) {
3263
+ // Safety net already claimed it. Poll for completion.
3264
+ await pollUntilRunIdle(conversationId);
3265
+ }
3259
3266
  setStreaming(false);
3260
3267
  renderMessages(localMsgs, false);
3261
3268
  await loadConversations();
@@ -4350,59 +4357,30 @@ var getWebUiClientScript = (markedSource2) => `
4350
4357
  };
4351
4358
  let _totalSteps = 0;
4352
4359
  let _maxSteps = 0;
4353
- let _isContinuation = false;
4354
4360
  let _receivedTerminalEvent = false;
4355
- while (true) {
4356
4361
  let _shouldContinue = false;
4357
- let fetchOpts;
4358
- if (_isContinuation) {
4359
- fetchOpts = {
4360
- method: "POST",
4361
- credentials: "include",
4362
- headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
4363
- body: JSON.stringify({ continuation: true }),
4364
- signal: streamAbortController.signal,
4365
- };
4366
- } else if (filesToSend.length > 0) {
4367
- const formData = new FormData();
4368
- formData.append("message", messageText);
4369
- for (const f of filesToSend) {
4370
- formData.append("files", f, f.name);
4371
- }
4372
- fetchOpts = {
4373
- method: "POST",
4374
- credentials: "include",
4375
- headers: { "x-csrf-token": state.csrfToken },
4376
- body: formData,
4377
- signal: streamAbortController.signal,
4378
- };
4379
- } else {
4380
- fetchOpts = {
4381
- method: "POST",
4382
- credentials: "include",
4383
- headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
4384
- body: JSON.stringify({ message: messageText }),
4385
- signal: streamAbortController.signal,
4386
- };
4387
- }
4388
- const response = await fetch(
4389
- "/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
4390
- fetchOpts,
4391
- );
4392
- if (!response.ok || !response.body) {
4393
- throw new Error("Failed to stream response");
4394
- }
4395
- const reader = response.body.getReader();
4396
- const decoder = new TextDecoder();
4397
- let buffer = "";
4398
- while (true) {
4399
- const { value, done } = await reader.read();
4400
- if (done) {
4401
- break;
4362
+
4363
+ // Helper to read an SSE stream from a fetch response
4364
+ const readSseStream = async (response) => {
4365
+ _shouldContinue = false;
4366
+ const reader = response.body.getReader();
4367
+ const decoder = new TextDecoder();
4368
+ let buffer = "";
4369
+ while (true) {
4370
+ const { value, done } = await reader.read();
4371
+ if (done) break;
4372
+ buffer += decoder.decode(value, { stream: true });
4373
+ buffer = parseSseChunk(buffer, (eventName, payload) => {
4374
+ try {
4375
+ handleSseEvent(eventName, payload);
4376
+ } catch (error) {
4377
+ console.error("SSE event handling error:", eventName, error);
4378
+ }
4379
+ });
4402
4380
  }
4403
- buffer += decoder.decode(value, { stream: true });
4404
- buffer = parseSseChunk(buffer, (eventName, payload) => {
4405
- try {
4381
+ };
4382
+
4383
+ const handleSseEvent = (eventName, payload) => {
4406
4384
  if (eventName === "model:chunk") {
4407
4385
  const chunk = String(payload.content || "");
4408
4386
  if (chunk.length > 0) clearResolvedApprovals(assistantMessage);
@@ -4686,6 +4664,12 @@ var getWebUiClientScript = (markedSource2) => `
4686
4664
  if (typeof payload.result?.maxSteps === "number") _maxSteps = payload.result.maxSteps;
4687
4665
  if (payload.result?.continuation === true && (_maxSteps <= 0 || _totalSteps < _maxSteps)) {
4688
4666
  _shouldContinue = true;
4667
+ if (assistantMessage._currentTools.length > 0) {
4668
+ assistantMessage._sections.push({ type: "tools", content: assistantMessage._currentTools });
4669
+ assistantMessage._currentTools = [];
4670
+ }
4671
+ assistantMessage._activeActivities = [];
4672
+ renderIfActiveConversation(true);
4689
4673
  } else {
4690
4674
  finalizeAssistantMessage();
4691
4675
  if (!assistantMessage.content || assistantMessage.content.length === 0) {
@@ -4709,26 +4693,77 @@ var getWebUiClientScript = (markedSource2) => `
4709
4693
  assistantMessage._error = errMsg;
4710
4694
  renderIfActiveConversation(false);
4711
4695
  }
4712
- } catch (error) {
4713
- console.error("SSE event handling error:", eventName, error);
4714
- }
4715
- });
4696
+ if (eventName === "stream:end") {
4697
+ // no-op: server signals empty continuation
4698
+ }
4699
+ };
4700
+
4701
+ // Initial message POST
4702
+ let fetchOpts;
4703
+ if (filesToSend.length > 0) {
4704
+ const formData = new FormData();
4705
+ formData.append("message", messageText);
4706
+ for (const f of filesToSend) {
4707
+ formData.append("files", f, f.name);
4708
+ }
4709
+ fetchOpts = {
4710
+ method: "POST",
4711
+ credentials: "include",
4712
+ headers: { "x-csrf-token": state.csrfToken },
4713
+ body: formData,
4714
+ signal: streamAbortController.signal,
4715
+ };
4716
+ } else {
4717
+ fetchOpts = {
4718
+ method: "POST",
4719
+ credentials: "include",
4720
+ headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
4721
+ body: JSON.stringify({ message: messageText }),
4722
+ signal: streamAbortController.signal,
4723
+ };
4716
4724
  }
4717
- if (!_shouldContinue && !_receivedTerminalEvent) {
4725
+ const response = await fetch(
4726
+ "/api/conversations/" + encodeURIComponent(conversationId) + "/messages",
4727
+ fetchOpts,
4728
+ );
4729
+ if (!response.ok || !response.body) {
4730
+ throw new Error("Failed to stream response");
4731
+ }
4732
+ await readSseStream(response);
4733
+
4734
+ // Continuation loop: POST to /continue while the server signals more work
4735
+ while (_shouldContinue) {
4736
+ _shouldContinue = false;
4737
+ _receivedTerminalEvent = false;
4738
+ const contResponse = await fetch(
4739
+ "/api/conversations/" + encodeURIComponent(conversationId) + "/continue",
4740
+ {
4741
+ method: "POST",
4742
+ credentials: "include",
4743
+ headers: { "Content-Type": "application/json", "x-csrf-token": state.csrfToken },
4744
+ signal: streamAbortController.signal,
4745
+ },
4746
+ );
4747
+ if (!contResponse.ok || !contResponse.body) {
4748
+ // Server may have already handled continuation (safety net claimed it).
4749
+ // Fall back to polling for idle state.
4750
+ await pollUntilRunIdle(conversationId);
4751
+ break;
4752
+ }
4753
+ await readSseStream(contResponse);
4754
+ }
4755
+
4756
+ // If stream ended without terminal event and no continuation, check server
4757
+ if (!_receivedTerminalEvent && !_shouldContinue) {
4718
4758
  try {
4719
4759
  const recoveryPayload = await api("/api/conversations/" + encodeURIComponent(conversationId));
4720
- if (recoveryPayload.needsContinuation) {
4721
- _shouldContinue = true;
4722
- console.log("[poncho] Stream ended without terminal event, server has continuation \u2014 resuming");
4760
+ if (recoveryPayload.hasActiveRun || recoveryPayload.needsContinuation) {
4761
+ await pollUntilRunIdle(conversationId);
4723
4762
  }
4724
4763
  } catch (_recoverErr) {
4725
4764
  console.warn("[poncho] Recovery check failed after abrupt stream end");
4726
4765
  }
4727
4766
  }
4728
- if (!_shouldContinue) break;
4729
- _receivedTerminalEvent = false;
4730
- _isContinuation = true;
4731
- }
4732
4767
  // Update active state only if user is still on this conversation.
4733
4768
  if (state.activeConversationId === streamConversationId) {
4734
4769
  state.activeMessages = localMessages;
@@ -8604,7 +8639,7 @@ data: ${JSON.stringify(statusPayload)}
8604
8639
  await resumeRunFromCheckpoint(subagentId, conv, checkpointRef, toolResults);
8605
8640
  await handleSubagentCompletion(subagentId);
8606
8641
  };
8607
- const runSubagent = async (childConversationId, parentConversationId, task, ownerId, isContinuation = false) => {
8642
+ const runSubagent = async (childConversationId, parentConversationId, task, ownerId, _isContinuation = false) => {
8608
8643
  const childHarness = new AgentHarness({
8609
8644
  workingDir,
8610
8645
  environment: resolveHarnessEnvironment(),
@@ -8632,9 +8667,9 @@ data: ${JSON.stringify(statusPayload)}
8632
8667
  if (conversation.subagentMeta?.status === "stopped") return;
8633
8668
  conversation.lastActivityAt = Date.now();
8634
8669
  await conversationStore.update(conversation);
8635
- const harnessMessages = isContinuation && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : [...conversation.messages];
8670
+ const harnessMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
8636
8671
  for await (const event of childHarness.runWithTelemetry({
8637
- task: isContinuation ? void 0 : task,
8672
+ task,
8638
8673
  conversationId: childConversationId,
8639
8674
  parameters: {
8640
8675
  __activeConversationId: childConversationId,
@@ -8824,17 +8859,22 @@ data: ${JSON.stringify(statusPayload)}
8824
8859
  if (currentText.length > 0) sections.push({ type: "text", content: currentText });
8825
8860
  const conv = await conversationStore.get(childConversationId);
8826
8861
  if (conv) {
8862
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
8863
+ if (hasContent) {
8864
+ conv.messages.push({
8865
+ role: "assistant",
8866
+ content: assistantResponse,
8867
+ metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
8868
+ });
8869
+ }
8827
8870
  if (runResult?.continuation && runResult.continuationMessages) {
8828
8871
  conv._continuationMessages = runResult.continuationMessages;
8829
8872
  } else {
8830
8873
  conv._continuationMessages = void 0;
8831
- if (assistantResponse.length > 0 || toolTimeline.length > 0) {
8832
- conv.messages.push({
8833
- role: "assistant",
8834
- content: assistantResponse,
8835
- metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
8836
- });
8837
- }
8874
+ conv._continuationCount = void 0;
8875
+ }
8876
+ if (runResult?.continuationMessages) {
8877
+ conv._harnessMessages = runResult.continuationMessages;
8838
8878
  }
8839
8879
  conv.lastActivityAt = Date.now();
8840
8880
  conv.updatedAt = Date.now();
@@ -8847,16 +8887,11 @@ data: ${JSON.stringify(statusPayload)}
8847
8887
  await childHarness.shutdown();
8848
8888
  } catch {
8849
8889
  }
8850
- if (isServerless) {
8851
- const work = selfFetchWithRetry(`/api/internal/subagent/${encodeURIComponent(childConversationId)}/run`, { continuation: true }).catch(
8852
- (err) => console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
8853
- );
8854
- doWaitUntil(work);
8855
- } else {
8856
- runSubagent(childConversationId, parentConversationId, task, ownerId, true).catch(
8857
- (err) => console.error(`[poncho][subagent] Continuation failed:`, err instanceof Error ? err.message : err)
8858
- );
8859
- }
8890
+ const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(childConversationId)}`).catch(
8891
+ (err) => console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
8892
+ );
8893
+ doWaitUntil(work);
8894
+ if (!waitUntilHook) await work;
8860
8895
  return;
8861
8896
  }
8862
8897
  conv.subagentMeta = { ...conv.subagentMeta, status: "completed" };
@@ -9009,11 +9044,12 @@ ${resultBody}`,
9009
9044
  finished: false
9010
9045
  });
9011
9046
  }
9012
- const historyMessages = isContinuationResume && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : [...conversation.messages];
9047
+ const historyMessages = isContinuationResume && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
9013
9048
  let assistantResponse = "";
9014
9049
  let latestRunId = "";
9015
- let runContinuation = false;
9050
+ let runContinuation2 = false;
9016
9051
  let runContinuationMessages;
9052
+ let runHarnessMessages;
9017
9053
  let runContextTokens = conversation.contextTokens ?? 0;
9018
9054
  let runContextWindow = conversation.contextWindow ?? 0;
9019
9055
  const toolTimeline = [];
@@ -9072,8 +9108,11 @@ ${resultBody}`,
9072
9108
  }
9073
9109
  runContextTokens = event.result.contextTokens ?? runContextTokens;
9074
9110
  runContextWindow = event.result.contextWindow ?? runContextWindow;
9111
+ if (event.result.continuationMessages) {
9112
+ runHarnessMessages = event.result.continuationMessages;
9113
+ }
9075
9114
  if (event.result.continuation) {
9076
- runContinuation = true;
9115
+ runContinuation2 = true;
9077
9116
  if (event.result.continuationMessages) {
9078
9117
  runContinuationMessages = event.result.continuationMessages;
9079
9118
  }
@@ -9096,6 +9135,9 @@ ${resultBody}`,
9096
9135
  metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
9097
9136
  });
9098
9137
  }
9138
+ if (runHarnessMessages) {
9139
+ freshConv._harnessMessages = runHarnessMessages;
9140
+ }
9099
9141
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
9100
9142
  freshConv.runningCallbackSince = void 0;
9101
9143
  freshConv.runStatus = "idle";
@@ -9121,7 +9163,7 @@ ${resultBody}`,
9121
9163
  }
9122
9164
  }
9123
9165
  }
9124
- if (runContinuation) {
9166
+ if (runContinuation2) {
9125
9167
  if (isServerless) {
9126
9168
  const work = selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversationId)}/subagent-callback`).catch(
9127
9169
  (err) => console.error(`[poncho][subagent-callback] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
@@ -9293,6 +9335,7 @@ ${resultBody}`,
9293
9335
  let checkpointedRun = false;
9294
9336
  let runContextTokens = conversation.contextTokens ?? 0;
9295
9337
  let runContextWindow = conversation.contextWindow ?? 0;
9338
+ let resumeHarnessMessages;
9296
9339
  const baseMessages = checkpoint.baseMessageCount != null ? conversation.messages.slice(0, checkpoint.baseMessageCount) : [];
9297
9340
  const fullCheckpointMessages = [...baseMessages, ...checkpoint.checkpointMessages];
9298
9341
  let resumeToolResultMsg;
@@ -9406,6 +9449,9 @@ ${resultBody}`,
9406
9449
  }
9407
9450
  runContextTokens = event.result.contextTokens ?? runContextTokens;
9408
9451
  runContextWindow = event.result.contextWindow ?? runContextWindow;
9452
+ if (event.result.continuationMessages) {
9453
+ resumeHarnessMessages = event.result.continuationMessages;
9454
+ }
9409
9455
  }
9410
9456
  if (event.type === "run:error") {
9411
9457
  assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
@@ -9465,6 +9511,9 @@ ${resultBody}`,
9465
9511
  ];
9466
9512
  }
9467
9513
  }
9514
+ if (resumeHarnessMessages) {
9515
+ conv._harnessMessages = resumeHarnessMessages;
9516
+ }
9468
9517
  conv.runtimeRunId = latestRunId || conv.runtimeRunId;
9469
9518
  conv.pendingApprovals = [];
9470
9519
  conv.runStatus = "idle";
@@ -9517,7 +9566,7 @@ ${resultBody}`,
9517
9566
  };
9518
9567
  await conversationStore.update(existing);
9519
9568
  }
9520
- return { messages: existing.messages };
9569
+ return { messages: existing._harnessMessages?.length ? existing._harnessMessages : existing.messages };
9521
9570
  }
9522
9571
  const now = Date.now();
9523
9572
  const channelMeta = meta.channelId ? {
@@ -9566,7 +9615,7 @@ ${resultBody}`,
9566
9615
  let checkpointedRun = false;
9567
9616
  let runContextTokens = 0;
9568
9617
  let runContextWindow = 0;
9569
- let runContinuation = false;
9618
+ let runContinuation2 = false;
9570
9619
  let runContinuationMessages;
9571
9620
  let runSteps = 0;
9572
9621
  let runMaxSteps;
@@ -9717,7 +9766,7 @@ ${resultBody}`,
9717
9766
  if (assistantResponse.length === 0 && event.result.response) {
9718
9767
  assistantResponse = event.result.response;
9719
9768
  }
9720
- runContinuation = event.result.continuation === true;
9769
+ runContinuation2 = event.result.continuation === true;
9721
9770
  if (event.result.continuationMessages) {
9722
9771
  runContinuationMessages = event.result.continuationMessages;
9723
9772
  }
@@ -9745,12 +9794,15 @@ ${resultBody}`,
9745
9794
  }
9746
9795
  if (!checkpointedRun) {
9747
9796
  await updateConversation((c) => {
9748
- if (runContinuationMessages) {
9797
+ if (runContinuation2 && runContinuationMessages) {
9749
9798
  c._continuationMessages = runContinuationMessages;
9750
9799
  } else {
9751
9800
  c._continuationMessages = void 0;
9752
9801
  c.messages = buildMessages();
9753
9802
  }
9803
+ if (runContinuationMessages) {
9804
+ c._harnessMessages = runContinuationMessages;
9805
+ }
9754
9806
  c.runtimeRunId = latestRunId || c.runtimeRunId;
9755
9807
  c.pendingApprovals = [];
9756
9808
  c.runStatus = "idle";
@@ -9767,11 +9819,11 @@ ${resultBody}`,
9767
9819
  runOwners.delete(latestRunId);
9768
9820
  runConversations.delete(latestRunId);
9769
9821
  }
9770
- console.log("[messaging-runner] run complete, response length:", assistantResponse.length, runContinuation ? "(continuation)" : "");
9822
+ console.log("[messaging-runner] run complete, response length:", assistantResponse.length, runContinuation2 ? "(continuation)" : "");
9771
9823
  const response = assistantResponse;
9772
9824
  return {
9773
9825
  response,
9774
- continuation: runContinuation,
9826
+ continuation: runContinuation2,
9775
9827
  steps: runSteps,
9776
9828
  maxSteps: runMaxSteps
9777
9829
  };
@@ -9897,6 +9949,375 @@ ${resultBody}`,
9897
9949
  const headerValue = getInternalRequestHeader(headers);
9898
9950
  return typeof headerValue === "string" && headerValue === internalSecret;
9899
9951
  };
9952
+ const MAX_CONTINUATION_COUNT = 20;
9953
+ async function* runContinuation(conversationId) {
9954
+ const conversation = await conversationStore.get(conversationId);
9955
+ if (!conversation) return;
9956
+ if (!conversation._continuationMessages?.length) return;
9957
+ if (conversation.runStatus === "running") return;
9958
+ const count = (conversation._continuationCount ?? 0) + 1;
9959
+ if (count > MAX_CONTINUATION_COUNT) {
9960
+ console.warn(`[poncho][continuation] Max continuation count (${MAX_CONTINUATION_COUNT}) reached for ${conversationId}`);
9961
+ conversation._continuationMessages = void 0;
9962
+ conversation._continuationCount = void 0;
9963
+ await conversationStore.update(conversation);
9964
+ return;
9965
+ }
9966
+ const continuationMessages = [...conversation._continuationMessages];
9967
+ conversation._continuationMessages = void 0;
9968
+ conversation._continuationCount = count;
9969
+ conversation.runStatus = "running";
9970
+ await conversationStore.update(conversation);
9971
+ const abortController = new AbortController();
9972
+ activeConversationRuns.set(conversationId, {
9973
+ ownerId: conversation.ownerId,
9974
+ abortController,
9975
+ runId: null
9976
+ });
9977
+ const prevStream = conversationEventStreams.get(conversationId);
9978
+ if (prevStream) {
9979
+ prevStream.finished = false;
9980
+ prevStream.buffer = [];
9981
+ } else {
9982
+ conversationEventStreams.set(conversationId, {
9983
+ buffer: [],
9984
+ subscribers: /* @__PURE__ */ new Set(),
9985
+ finished: false
9986
+ });
9987
+ }
9988
+ try {
9989
+ if (conversation.parentConversationId) {
9990
+ yield* runSubagentContinuation(conversationId, conversation, continuationMessages);
9991
+ } else {
9992
+ yield* runChatContinuation(conversationId, conversation, continuationMessages);
9993
+ }
9994
+ } finally {
9995
+ activeConversationRuns.delete(conversationId);
9996
+ finishConversationStream(conversationId);
9997
+ }
9998
+ }
9999
+ async function* runChatContinuation(conversationId, conversation, continuationMessages) {
10000
+ let assistantResponse = "";
10001
+ let latestRunId = conversation.runtimeRunId ?? "";
10002
+ const toolTimeline = [];
10003
+ const sections = [];
10004
+ let currentTools = [];
10005
+ let currentText = "";
10006
+ let runContextTokens = conversation.contextTokens ?? 0;
10007
+ let runContextWindow = conversation.contextWindow ?? 0;
10008
+ let nextContinuationMessages;
10009
+ let nextHarnessMessages;
10010
+ for await (const event of harness.runWithTelemetry({
10011
+ conversationId,
10012
+ parameters: {
10013
+ __activeConversationId: conversationId,
10014
+ __ownerId: conversation.ownerId
10015
+ },
10016
+ messages: continuationMessages,
10017
+ abortSignal: activeConversationRuns.get(conversationId)?.abortController.signal
10018
+ })) {
10019
+ if (event.type === "run:started") {
10020
+ latestRunId = event.runId;
10021
+ runOwners.set(event.runId, conversation.ownerId);
10022
+ runConversations.set(event.runId, conversationId);
10023
+ const active = activeConversationRuns.get(conversationId);
10024
+ if (active) active.runId = event.runId;
10025
+ }
10026
+ if (event.type === "model:chunk") {
10027
+ if (currentTools.length > 0) {
10028
+ sections.push({ type: "tools", content: currentTools });
10029
+ currentTools = [];
10030
+ if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
10031
+ assistantResponse += " ";
10032
+ }
10033
+ }
10034
+ assistantResponse += event.content;
10035
+ currentText += event.content;
10036
+ }
10037
+ if (event.type === "tool:started") {
10038
+ if (currentText.length > 0) {
10039
+ sections.push({ type: "text", content: currentText });
10040
+ currentText = "";
10041
+ }
10042
+ const toolText = `- start \`${event.tool}\``;
10043
+ toolTimeline.push(toolText);
10044
+ currentTools.push(toolText);
10045
+ }
10046
+ if (event.type === "tool:completed") {
10047
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
10048
+ toolTimeline.push(toolText);
10049
+ currentTools.push(toolText);
10050
+ }
10051
+ if (event.type === "tool:error") {
10052
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
10053
+ toolTimeline.push(toolText);
10054
+ currentTools.push(toolText);
10055
+ }
10056
+ if (event.type === "run:completed") {
10057
+ runContextTokens = event.result.contextTokens ?? runContextTokens;
10058
+ runContextWindow = event.result.contextWindow ?? runContextWindow;
10059
+ if (event.result.continuation && event.result.continuationMessages) {
10060
+ nextContinuationMessages = event.result.continuationMessages;
10061
+ }
10062
+ if (event.result.continuationMessages) {
10063
+ nextHarnessMessages = event.result.continuationMessages;
10064
+ }
10065
+ if (!assistantResponse && event.result.response) {
10066
+ assistantResponse = event.result.response;
10067
+ }
10068
+ }
10069
+ if (event.type === "run:error") {
10070
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
10071
+ }
10072
+ await telemetry.emit(event);
10073
+ broadcastEvent(conversationId, event);
10074
+ yield event;
10075
+ }
10076
+ if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
10077
+ if (currentText.length > 0) sections.push({ type: "text", content: currentText });
10078
+ const freshConv = await conversationStore.get(conversationId);
10079
+ if (!freshConv) return;
10080
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
10081
+ const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
10082
+ toolActivity: [...toolTimeline],
10083
+ sections: sections.length > 0 ? sections : void 0
10084
+ } : void 0;
10085
+ if (nextContinuationMessages) {
10086
+ if (hasContent) {
10087
+ freshConv.messages = [
10088
+ ...freshConv.messages,
10089
+ { role: "assistant", content: assistantResponse, metadata: assistantMetadata }
10090
+ ];
10091
+ }
10092
+ freshConv._continuationMessages = nextContinuationMessages;
10093
+ freshConv._continuationCount = conversation._continuationCount;
10094
+ } else {
10095
+ if (hasContent) {
10096
+ freshConv.messages = [
10097
+ ...freshConv.messages,
10098
+ { role: "assistant", content: assistantResponse, metadata: assistantMetadata }
10099
+ ];
10100
+ }
10101
+ freshConv._continuationMessages = void 0;
10102
+ freshConv._continuationCount = void 0;
10103
+ }
10104
+ if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
10105
+ freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
10106
+ freshConv.pendingApprovals = [];
10107
+ if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
10108
+ if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
10109
+ freshConv.runStatus = "idle";
10110
+ freshConv.updatedAt = Date.now();
10111
+ await conversationStore.update(freshConv);
10112
+ }
10113
+ async function* runSubagentContinuation(conversationId, conversation, continuationMessages) {
10114
+ const parentConversationId = conversation.parentConversationId;
10115
+ const task = conversation.subagentMeta?.task ?? "";
10116
+ const ownerId = conversation.ownerId;
10117
+ const childHarness = new AgentHarness({
10118
+ workingDir,
10119
+ environment: resolveHarnessEnvironment(),
10120
+ uploadStore
10121
+ });
10122
+ await childHarness.initialize();
10123
+ childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
10124
+ const childAbortController = activeConversationRuns.get(conversationId)?.abortController ?? new AbortController();
10125
+ activeSubagentRuns.set(conversationId, { abortController: childAbortController, harness: childHarness, parentConversationId });
10126
+ let assistantResponse = "";
10127
+ let latestRunId = "";
10128
+ let runResult;
10129
+ const toolTimeline = [];
10130
+ const sections = [];
10131
+ let currentTools = [];
10132
+ let currentText = "";
10133
+ try {
10134
+ for await (const event of childHarness.runWithTelemetry({
10135
+ conversationId,
10136
+ parameters: {
10137
+ __activeConversationId: conversationId,
10138
+ __ownerId: ownerId
10139
+ },
10140
+ messages: continuationMessages,
10141
+ abortSignal: childAbortController.signal
10142
+ })) {
10143
+ if (event.type === "run:started") {
10144
+ latestRunId = event.runId;
10145
+ const active = activeConversationRuns.get(conversationId);
10146
+ if (active) active.runId = event.runId;
10147
+ }
10148
+ if (event.type === "model:chunk") {
10149
+ if (currentTools.length > 0) {
10150
+ sections.push({ type: "tools", content: currentTools });
10151
+ currentTools = [];
10152
+ if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
10153
+ assistantResponse += " ";
10154
+ }
10155
+ }
10156
+ assistantResponse += event.content;
10157
+ currentText += event.content;
10158
+ }
10159
+ if (event.type === "tool:started") {
10160
+ if (currentText.length > 0) {
10161
+ sections.push({ type: "text", content: currentText });
10162
+ currentText = "";
10163
+ }
10164
+ const toolText = `- start \`${event.tool}\``;
10165
+ toolTimeline.push(toolText);
10166
+ currentTools.push(toolText);
10167
+ }
10168
+ if (event.type === "tool:completed") {
10169
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
10170
+ toolTimeline.push(toolText);
10171
+ currentTools.push(toolText);
10172
+ }
10173
+ if (event.type === "tool:error") {
10174
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
10175
+ toolTimeline.push(toolText);
10176
+ currentTools.push(toolText);
10177
+ }
10178
+ if (event.type === "run:completed") {
10179
+ runResult = {
10180
+ status: event.result.status,
10181
+ response: event.result.response,
10182
+ steps: event.result.steps,
10183
+ duration: event.result.duration,
10184
+ continuation: event.result.continuation,
10185
+ continuationMessages: event.result.continuationMessages
10186
+ };
10187
+ if (!assistantResponse && event.result.response) {
10188
+ assistantResponse = event.result.response;
10189
+ }
10190
+ }
10191
+ if (event.type === "run:error") {
10192
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
10193
+ }
10194
+ broadcastEvent(conversationId, event);
10195
+ yield event;
10196
+ }
10197
+ if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
10198
+ if (currentText.length > 0) sections.push({ type: "text", content: currentText });
10199
+ const conv = await conversationStore.get(conversationId);
10200
+ if (conv) {
10201
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
10202
+ if (runResult?.continuation && runResult.continuationMessages) {
10203
+ if (hasContent) {
10204
+ conv.messages.push({
10205
+ role: "assistant",
10206
+ content: assistantResponse,
10207
+ metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
10208
+ });
10209
+ }
10210
+ conv._continuationMessages = runResult.continuationMessages;
10211
+ conv._continuationCount = conversation._continuationCount;
10212
+ } else {
10213
+ conv._continuationMessages = void 0;
10214
+ conv._continuationCount = void 0;
10215
+ if (hasContent) {
10216
+ conv.messages.push({
10217
+ role: "assistant",
10218
+ content: assistantResponse,
10219
+ metadata: toolTimeline.length > 0 || sections.length > 0 ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : void 0 } : void 0
10220
+ });
10221
+ }
10222
+ }
10223
+ if (runResult?.continuationMessages) {
10224
+ conv._harnessMessages = runResult.continuationMessages;
10225
+ }
10226
+ conv.lastActivityAt = Date.now();
10227
+ conv.runStatus = "idle";
10228
+ conv.updatedAt = Date.now();
10229
+ if (runResult?.continuation) {
10230
+ await conversationStore.update(conv);
10231
+ activeSubagentRuns.delete(conversationId);
10232
+ try {
10233
+ await childHarness.shutdown();
10234
+ } catch {
10235
+ }
10236
+ return;
10237
+ }
10238
+ conv.subagentMeta = { ...conv.subagentMeta, status: "completed" };
10239
+ await conversationStore.update(conv);
10240
+ }
10241
+ activeSubagentRuns.delete(conversationId);
10242
+ broadcastEvent(parentConversationId, {
10243
+ type: "subagent:completed",
10244
+ subagentId: conversationId,
10245
+ conversationId
10246
+ });
10247
+ let subagentResponse = runResult?.response ?? assistantResponse;
10248
+ if (!subagentResponse) {
10249
+ const freshSubConv = await conversationStore.get(conversationId);
10250
+ if (freshSubConv) {
10251
+ const lastAssistant = [...freshSubConv.messages].reverse().find((m) => m.role === "assistant");
10252
+ if (lastAssistant) {
10253
+ subagentResponse = typeof lastAssistant.content === "string" ? lastAssistant.content : "";
10254
+ }
10255
+ }
10256
+ }
10257
+ const parentConv = await conversationStore.get(parentConversationId);
10258
+ if (parentConv) {
10259
+ const result = {
10260
+ subagentId: conversationId,
10261
+ task,
10262
+ status: "completed",
10263
+ result: { status: "completed", response: subagentResponse, steps: runResult?.steps ?? 0, tokens: { input: 0, output: 0, cached: 0 }, duration: runResult?.duration ?? 0 },
10264
+ timestamp: Date.now()
10265
+ };
10266
+ await conversationStore.appendSubagentResult(parentConversationId, result);
10267
+ if (isServerless) {
10268
+ selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(parentConversationId)}/subagent-callback`).catch(
10269
+ (err) => console.error(`[poncho][subagent] Callback self-fetch failed:`, err instanceof Error ? err.message : err)
10270
+ );
10271
+ } else {
10272
+ processSubagentCallback(parentConversationId).catch(
10273
+ (err) => console.error(`[poncho][subagent] Callback failed:`, err instanceof Error ? err.message : err)
10274
+ );
10275
+ }
10276
+ }
10277
+ try {
10278
+ await childHarness.shutdown();
10279
+ } catch {
10280
+ }
10281
+ } catch (err) {
10282
+ activeSubagentRuns.delete(conversationId);
10283
+ try {
10284
+ await childHarness.shutdown();
10285
+ } catch {
10286
+ }
10287
+ const conv = await conversationStore.get(conversationId);
10288
+ if (conv) {
10289
+ conv.subagentMeta = { ...conv.subagentMeta, status: "error", error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) } };
10290
+ conv.runStatus = "idle";
10291
+ conv._continuationMessages = void 0;
10292
+ conv._continuationCount = void 0;
10293
+ conv.updatedAt = Date.now();
10294
+ await conversationStore.update(conv);
10295
+ }
10296
+ broadcastEvent(conversation.parentConversationId, {
10297
+ type: "subagent:completed",
10298
+ subagentId: conversationId,
10299
+ conversationId
10300
+ });
10301
+ const parentConv = await conversationStore.get(conversation.parentConversationId);
10302
+ if (parentConv) {
10303
+ const result = {
10304
+ subagentId: conversationId,
10305
+ task,
10306
+ status: "error",
10307
+ error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
10308
+ timestamp: Date.now()
10309
+ };
10310
+ await conversationStore.appendSubagentResult(conversation.parentConversationId, result);
10311
+ if (isServerless) {
10312
+ selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversation.parentConversationId)}/subagent-callback`).catch(() => {
10313
+ });
10314
+ } else {
10315
+ processSubagentCallback(conversation.parentConversationId).catch(() => {
10316
+ });
10317
+ }
10318
+ }
10319
+ }
10320
+ }
9900
10321
  const messagingAdapters = /* @__PURE__ */ new Map();
9901
10322
  const messagingBridges = [];
9902
10323
  if (config?.messaging && config.messaging.length > 0) {
@@ -10230,9 +10651,8 @@ ${resultBody}`,
10230
10651
  await resumeSubagentFromCheckpoint(subagentId);
10231
10652
  return;
10232
10653
  }
10233
- const isContinuation = body?.continuation === true;
10234
- const task = isContinuation ? conv.subagentMeta?.task ?? "" : conv.messages.find((m) => m.role === "user")?.content ?? conv.subagentMeta?.task ?? "";
10235
- await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, isContinuation);
10654
+ const task = conv.messages.find((m) => m.role === "user")?.content ?? conv.subagentMeta?.task ?? "";
10655
+ await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, false);
10236
10656
  } catch (err) {
10237
10657
  console.error(`[poncho][internal] subagent run error for ${subagentId}:`, err instanceof Error ? err.message : err);
10238
10658
  }
@@ -10250,6 +10670,26 @@ ${resultBody}`,
10250
10670
  if (!waitUntilHook) await work;
10251
10671
  return;
10252
10672
  }
10673
+ const continueMatch = pathname.match(/^\/api\/internal\/continue\/([^/]+)$/);
10674
+ if (continueMatch) {
10675
+ const conversationId = decodeURIComponent(continueMatch[1]);
10676
+ writeJson(response, 202, { ok: true });
10677
+ const work = (async () => {
10678
+ try {
10679
+ for await (const _event of runContinuation(conversationId)) {
10680
+ }
10681
+ const conv = await conversationStore.get(conversationId);
10682
+ if (conv?._continuationMessages?.length) {
10683
+ await selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`);
10684
+ }
10685
+ } catch (err) {
10686
+ console.error(`[poncho][internal-continue] Error for ${conversationId}:`, err instanceof Error ? err.message : err);
10687
+ }
10688
+ })();
10689
+ doWaitUntil(work);
10690
+ if (!waitUntilHook) await work;
10691
+ return;
10692
+ }
10253
10693
  writeJson(response, 404, { error: "Not found" });
10254
10694
  return;
10255
10695
  }
@@ -10856,7 +11296,8 @@ data: ${JSON.stringify(frame)}
10856
11296
  conversation: {
10857
11297
  ...conversation,
10858
11298
  pendingApprovals: storedPending,
10859
- _continuationMessages: void 0
11299
+ _continuationMessages: void 0,
11300
+ _harnessMessages: void 0
10860
11301
  },
10861
11302
  subagentPendingApprovals: subagentPending,
10862
11303
  hasActiveRun: hasActiveRun || hasPendingCallbackResults,
@@ -11025,6 +11466,87 @@ data: ${JSON.stringify(frame)}
11025
11466
  });
11026
11467
  return;
11027
11468
  }
11469
+ const conversationContinueMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/continue$/);
11470
+ if (conversationContinueMatch && request.method === "POST") {
11471
+ const conversationId = decodeURIComponent(conversationContinueMatch[1] ?? "");
11472
+ const conversation = await conversationStore.get(conversationId);
11473
+ if (!conversation || conversation.ownerId !== ownerId) {
11474
+ writeJson(response, 404, {
11475
+ code: "CONVERSATION_NOT_FOUND",
11476
+ message: "Conversation not found"
11477
+ });
11478
+ return;
11479
+ }
11480
+ if (conversation.parentConversationId) {
11481
+ writeJson(response, 403, {
11482
+ code: "SUBAGENT_READ_ONLY",
11483
+ message: "Subagent conversations are read-only."
11484
+ });
11485
+ return;
11486
+ }
11487
+ response.writeHead(200, {
11488
+ "Content-Type": "text/event-stream",
11489
+ "Cache-Control": "no-cache",
11490
+ Connection: "keep-alive",
11491
+ "X-Accel-Buffering": "no"
11492
+ });
11493
+ const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
11494
+ if (evt.type.startsWith("subagent:")) {
11495
+ try {
11496
+ response.write(formatSseEvent(evt));
11497
+ } catch {
11498
+ }
11499
+ }
11500
+ });
11501
+ let eventCount = 0;
11502
+ try {
11503
+ for await (const event of runContinuation(conversationId)) {
11504
+ eventCount++;
11505
+ let sseEvent = event;
11506
+ if (sseEvent.type === "run:completed") {
11507
+ const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
11508
+ (r) => r.parentConversationId === conversationId
11509
+ );
11510
+ const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
11511
+ sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
11512
+ }
11513
+ try {
11514
+ response.write(formatSseEvent(sseEvent));
11515
+ } catch {
11516
+ }
11517
+ emitBrowserStatusIfActive(conversationId, event, response);
11518
+ }
11519
+ } catch (err) {
11520
+ const errorEvent = {
11521
+ type: "run:error",
11522
+ runId: "",
11523
+ error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) }
11524
+ };
11525
+ try {
11526
+ response.write(formatSseEvent(errorEvent));
11527
+ } catch {
11528
+ }
11529
+ } finally {
11530
+ unsubSubagentEvents();
11531
+ }
11532
+ if (eventCount === 0) {
11533
+ try {
11534
+ response.write("event: stream:end\ndata: {}\n\n");
11535
+ } catch {
11536
+ }
11537
+ } else {
11538
+ const freshConv = await conversationStore.get(conversationId);
11539
+ if (freshConv?._continuationMessages?.length) {
11540
+ doWaitUntil(
11541
+ new Promise((r) => setTimeout(r, 3e3)).then(
11542
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
11543
+ )
11544
+ );
11545
+ }
11546
+ }
11547
+ response.end();
11548
+ return;
11549
+ }
11028
11550
  const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
11029
11551
  if (conversationMessageMatch && request.method === "POST") {
11030
11552
  const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
@@ -11046,7 +11568,6 @@ data: ${JSON.stringify(frame)}
11046
11568
  let messageText = "";
11047
11569
  let bodyParameters;
11048
11570
  let files = [];
11049
- let isContinuation = false;
11050
11571
  const contentType = request.headers["content-type"] ?? "";
11051
11572
  if (contentType.includes("multipart/form-data")) {
11052
11573
  const parsed = await parseMultipartRequest(request);
@@ -11055,11 +11576,7 @@ data: ${JSON.stringify(frame)}
11055
11576
  files = parsed.files;
11056
11577
  } else {
11057
11578
  const body = await readRequestBody(request);
11058
- if (body.continuation === true) {
11059
- isContinuation = true;
11060
- } else {
11061
- messageText = body.message?.trim() ?? "";
11062
- }
11579
+ messageText = body.message?.trim() ?? "";
11063
11580
  bodyParameters = body.parameters;
11064
11581
  if (Array.isArray(body.files)) {
11065
11582
  files = body.files.filter(
@@ -11067,7 +11584,7 @@ data: ${JSON.stringify(frame)}
11067
11584
  );
11068
11585
  }
11069
11586
  }
11070
- if (!isContinuation && !messageText) {
11587
+ if (!messageText) {
11071
11588
  writeJson(response, 400, {
11072
11589
  code: "VALIDATION_ERROR",
11073
11590
  message: "message is required"
@@ -11092,7 +11609,7 @@ data: ${JSON.stringify(frame)}
11092
11609
  abortController,
11093
11610
  runId: null
11094
11611
  });
11095
- if (!isContinuation && conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0)) {
11612
+ if (conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0)) {
11096
11613
  conversation.title = inferConversationTitle(messageText);
11097
11614
  }
11098
11615
  response.writeHead(200, {
@@ -11101,7 +11618,7 @@ data: ${JSON.stringify(frame)}
11101
11618
  Connection: "keep-alive",
11102
11619
  "X-Accel-Buffering": "no"
11103
11620
  });
11104
- const harnessMessages = isContinuation && conversation._continuationMessages?.length ? [...conversation._continuationMessages] : [...conversation.messages];
11621
+ const harnessMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
11105
11622
  const historyMessages = [...conversation.messages];
11106
11623
  const preRunMessages = [...conversation.messages];
11107
11624
  let latestRunId = conversation.runtimeRunId ?? "";
@@ -11116,8 +11633,9 @@ data: ${JSON.stringify(frame)}
11116
11633
  let runContextTokens = conversation.contextTokens ?? 0;
11117
11634
  let runContextWindow = conversation.contextWindow ?? 0;
11118
11635
  let runContinuationMessages;
11119
- let userContent = isContinuation ? void 0 : messageText;
11120
- if (!isContinuation && files.length > 0) {
11636
+ let runHarnessMessages;
11637
+ let userContent = messageText;
11638
+ if (files.length > 0) {
11121
11639
  try {
11122
11640
  const uploadedParts = await Promise.all(
11123
11641
  files.map(async (f) => {
@@ -11160,9 +11678,10 @@ data: ${JSON.stringify(frame)}
11160
11678
  }
11161
11679
  });
11162
11680
  try {
11163
- if (!isContinuation) {
11681
+ {
11164
11682
  conversation.messages = [...historyMessages, { role: "user", content: userContent }];
11165
11683
  conversation.subagentCallbackCount = 0;
11684
+ conversation._continuationCount = void 0;
11166
11685
  conversation.updatedAt = Date.now();
11167
11686
  conversationStore.update(conversation).catch((err) => {
11168
11687
  console.error("[poncho] Failed to persist user turn:", err);
@@ -11221,7 +11740,7 @@ data: ${JSON.stringify(frame)}
11221
11740
  return cachedRecallCorpus;
11222
11741
  };
11223
11742
  for await (const event of harness.runWithTelemetry({
11224
- task: isContinuation ? void 0 : messageText,
11743
+ task: messageText,
11225
11744
  conversationId,
11226
11745
  parameters: {
11227
11746
  ...bodyParameters ?? {},
@@ -11230,7 +11749,7 @@ data: ${JSON.stringify(frame)}
11230
11749
  __ownerId: ownerId
11231
11750
  },
11232
11751
  messages: harnessMessages,
11233
- files: !isContinuation && files.length > 0 ? files : void 0,
11752
+ files: files.length > 0 ? files : void 0,
11234
11753
  abortSignal: abortController.signal
11235
11754
  })) {
11236
11755
  if (event.type === "run:started") {
@@ -11335,15 +11854,34 @@ data: ${JSON.stringify(frame)}
11335
11854
  }
11336
11855
  runContextTokens = event.result.contextTokens ?? runContextTokens;
11337
11856
  runContextWindow = event.result.contextWindow ?? runContextWindow;
11857
+ if (event.result.continuationMessages) {
11858
+ runHarnessMessages = event.result.continuationMessages;
11859
+ }
11338
11860
  if (event.result.continuation && event.result.continuationMessages) {
11339
11861
  runContinuationMessages = event.result.continuationMessages;
11862
+ const intSections = [...sections];
11863
+ if (currentTools.length > 0) intSections.push({ type: "tools", content: [...currentTools] });
11864
+ if (currentText.length > 0) intSections.push({ type: "text", content: currentText });
11865
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0 || intSections.length > 0;
11866
+ const intMetadata = toolTimeline.length > 0 || intSections.length > 0 ? { toolActivity: [...toolTimeline], sections: intSections.length > 0 ? intSections : void 0 } : void 0;
11867
+ conversation.messages = [
11868
+ ...historyMessages,
11869
+ ...userContent != null ? [{ role: "user", content: userContent }] : [],
11870
+ ...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: intMetadata }] : []
11871
+ ];
11340
11872
  conversation._continuationMessages = runContinuationMessages;
11873
+ conversation._harnessMessages = runContinuationMessages;
11341
11874
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
11342
11875
  conversation.pendingApprovals = [];
11343
11876
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
11344
11877
  if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
11345
11878
  conversation.updatedAt = Date.now();
11346
11879
  await conversationStore.update(conversation);
11880
+ doWaitUntil(
11881
+ new Promise((r) => setTimeout(r, 3e3)).then(
11882
+ () => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
11883
+ )
11884
+ );
11347
11885
  }
11348
11886
  }
11349
11887
  await telemetry.emit(event);
@@ -11388,6 +11926,9 @@ data: ${JSON.stringify(frame)}
11388
11926
  }
11389
11927
  ] : [...historyMessages, ...userTurn];
11390
11928
  conversation._continuationMessages = void 0;
11929
+ if (runHarnessMessages) {
11930
+ conversation._harnessMessages = runHarnessMessages;
11931
+ }
11391
11932
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
11392
11933
  conversation.pendingApprovals = [];
11393
11934
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
@@ -11496,20 +12037,9 @@ data: ${JSON.stringify(frame)}
11496
12037
  return;
11497
12038
  }
11498
12039
  const urlObj = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
11499
- const continueConversationId = urlObj.searchParams.get("continue");
11500
- const continuationCount = Number(urlObj.searchParams.get("continuation") ?? "0");
11501
- const maxContinuations = 5;
11502
- if (continuationCount >= maxContinuations) {
11503
- writeJson(response, 200, {
11504
- conversationId: continueConversationId,
11505
- status: "max_continuations_reached",
11506
- continuations: continuationCount
11507
- });
11508
- return;
11509
- }
11510
12040
  const cronOwnerId = ownerId;
11511
12041
  const start = Date.now();
11512
- if (cronJob.channel && !continueConversationId) {
12042
+ if (cronJob.channel) {
11513
12043
  const adapter = messagingAdapters.get(cronJob.channel);
11514
12044
  if (!adapter) {
11515
12045
  writeJson(response, 200, {
@@ -11544,10 +12074,11 @@ data: ${JSON.stringify(frame)}
11544
12074
  if (!conv) continue;
11545
12075
  const task = `[Scheduled: ${jobName}]
11546
12076
  ${cronJob.task}`;
11547
- const historyMessages = [...conv.messages];
12077
+ const historyMessages = conv._harnessMessages?.length ? [...conv._harnessMessages] : [...conv.messages];
11548
12078
  try {
11549
12079
  let assistantResponse = "";
11550
12080
  let steps = 0;
12081
+ let cronHarnessMessages;
11551
12082
  for await (const event of harness.runWithTelemetry({
11552
12083
  task,
11553
12084
  conversationId: conv.conversationId,
@@ -11562,6 +12093,9 @@ ${cronJob.task}`;
11562
12093
  if (!assistantResponse && event.result.response) {
11563
12094
  assistantResponse = event.result.response;
11564
12095
  }
12096
+ if (event.result.continuationMessages) {
12097
+ cronHarnessMessages = event.result.continuationMessages;
12098
+ }
11565
12099
  }
11566
12100
  await telemetry.emit(event);
11567
12101
  }
@@ -11570,6 +12104,9 @@ ${cronJob.task}`;
11570
12104
  { role: "user", content: task },
11571
12105
  ...assistantResponse ? [{ role: "assistant", content: assistantResponse }] : []
11572
12106
  ];
12107
+ if (cronHarnessMessages) {
12108
+ conv._harnessMessages = cronHarnessMessages;
12109
+ }
11573
12110
  conv.updatedAt = Date.now();
11574
12111
  await conversationStore.update(conv);
11575
12112
  if (assistantResponse) {
@@ -11606,29 +12143,12 @@ ${cronJob.task}`;
11606
12143
  return;
11607
12144
  }
11608
12145
  try {
11609
- let conversation;
11610
- let historyMessages = [];
11611
- if (continueConversationId) {
11612
- conversation = await conversationStore.get(continueConversationId);
11613
- if (!conversation) {
11614
- writeJson(response, 404, {
11615
- code: "CONVERSATION_NOT_FOUND",
11616
- message: "Continuation conversation not found"
11617
- });
11618
- return;
11619
- }
11620
- historyMessages = conversation._continuationMessages?.length ? [...conversation._continuationMessages] : [...conversation.messages];
11621
- if (conversation._continuationMessages?.length) {
11622
- conversation._continuationMessages = void 0;
11623
- await conversationStore.update(conversation);
11624
- }
11625
- } else {
11626
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11627
- conversation = await conversationStore.create(
11628
- cronOwnerId,
11629
- `[cron] ${jobName} ${timestamp}`
11630
- );
11631
- }
12146
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
12147
+ const conversation = await conversationStore.create(
12148
+ cronOwnerId,
12149
+ `[cron] ${jobName} ${timestamp}`
12150
+ );
12151
+ const historyMessages = [];
11632
12152
  const convId = conversation.conversationId;
11633
12153
  activeConversationRuns.set(convId, {
11634
12154
  ownerId: conversation.ownerId,
@@ -11696,7 +12216,8 @@ ${cronJob.task}`;
11696
12216
  steps: event.result.steps,
11697
12217
  continuation: event.result.continuation,
11698
12218
  contextTokens: event.result.contextTokens,
11699
- contextWindow: event.result.contextWindow
12219
+ contextWindow: event.result.contextWindow,
12220
+ harnessMessages: event.result.continuationMessages
11700
12221
  };
11701
12222
  if (event.result.continuation && event.result.continuationMessages) {
11702
12223
  runContinuationMessages = event.result.continuationMessages;
@@ -11723,16 +12244,20 @@ ${cronJob.task}`;
11723
12244
  } : void 0;
11724
12245
  const messages = [
11725
12246
  ...historyMessages,
11726
- ...continueConversationId ? [] : [{ role: "user", content: cronJob.task }],
12247
+ { role: "user", content: cronJob.task },
11727
12248
  ...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: assistantMetadata }] : []
11728
12249
  ];
11729
12250
  const freshConv = await conversationStore.get(convId);
11730
12251
  if (freshConv) {
12252
+ freshConv.messages = messages;
11731
12253
  if (runContinuationMessages) {
11732
12254
  freshConv._continuationMessages = runContinuationMessages;
11733
12255
  } else {
11734
12256
  freshConv._continuationMessages = void 0;
11735
- freshConv.messages = messages;
12257
+ freshConv._continuationCount = void 0;
12258
+ }
12259
+ if (runResult.harnessMessages) {
12260
+ freshConv._harnessMessages = runResult.harnessMessages;
11736
12261
  }
11737
12262
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
11738
12263
  if (runResult.contextTokens) freshConv.contextTokens = runResult.contextTokens;
@@ -11741,15 +12266,13 @@ ${cronJob.task}`;
11741
12266
  await conversationStore.update(freshConv);
11742
12267
  }
11743
12268
  if (runResult.continuation) {
11744
- const continuationPath = `/api/cron/${encodeURIComponent(jobName)}?continue=${encodeURIComponent(convId)}&continuation=${continuationCount + 1}`;
11745
- const work = selfFetchWithRetry(continuationPath).catch(
12269
+ const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(
11746
12270
  (err) => console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
11747
12271
  );
11748
12272
  doWaitUntil(work);
11749
12273
  writeJson(response, 200, {
11750
12274
  conversationId: convId,
11751
12275
  status: "continued",
11752
- continuations: continuationCount + 1,
11753
12276
  duration: Date.now() - start
11754
12277
  });
11755
12278
  return;
@@ -11851,6 +12374,7 @@ var startDevServer = async (port, options) => {
11851
12374
  let steps = 0;
11852
12375
  let contextTokens = 0;
11853
12376
  let contextWindow = 0;
12377
+ let harnessMessages;
11854
12378
  const toolTimeline = [];
11855
12379
  const sections = [];
11856
12380
  let currentTools = [];
@@ -11896,6 +12420,9 @@ var startDevServer = async (port, options) => {
11896
12420
  steps = event.result.steps;
11897
12421
  contextTokens = event.result.contextTokens ?? 0;
11898
12422
  contextWindow = event.result.contextWindow ?? 0;
12423
+ if (event.result.continuationMessages) {
12424
+ harnessMessages = event.result.continuationMessages;
12425
+ }
11899
12426
  if (!assistantResponse && event.result.response) {
11900
12427
  assistantResponse = event.result.response;
11901
12428
  }
@@ -11912,7 +12439,7 @@ var startDevServer = async (port, options) => {
11912
12439
  toolActivity: [...toolTimeline],
11913
12440
  sections: sections.length > 0 ? sections : void 0
11914
12441
  } : void 0;
11915
- return { response: assistantResponse, steps, assistantMetadata, hasContent, contextTokens, contextWindow };
12442
+ return { response: assistantResponse, steps, assistantMetadata, hasContent, contextTokens, contextWindow, harnessMessages };
11916
12443
  };
11917
12444
  const buildCronMessages = (task, historyMessages, result) => [
11918
12445
  ...historyMessages,
@@ -11971,7 +12498,7 @@ var startDevServer = async (port, options) => {
11971
12498
  if (!conversation) continue;
11972
12499
  const task = `[Scheduled: ${jobName}]
11973
12500
  ${config.task}`;
11974
- const historyMessages = [...conversation.messages];
12501
+ const historyMessages = conversation._harnessMessages?.length ? [...conversation._harnessMessages] : [...conversation.messages];
11975
12502
  const convId = conversation.conversationId;
11976
12503
  activeRuns?.set(convId, {
11977
12504
  ownerId: "local-owner",
@@ -11991,6 +12518,9 @@ ${config.task}`;
11991
12518
  const freshConv = await store.get(convId);
11992
12519
  if (freshConv) {
11993
12520
  freshConv.messages = buildCronMessages(task, historyMessages, result);
12521
+ if (result.harnessMessages) {
12522
+ freshConv._harnessMessages = result.harnessMessages;
12523
+ }
11994
12524
  if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
11995
12525
  if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
11996
12526
  freshConv.updatedAt = Date.now();
@@ -12062,6 +12592,9 @@ ${config.task}`;
12062
12592
  const freshConv = await store.get(cronConvId);
12063
12593
  if (freshConv) {
12064
12594
  freshConv.messages = buildCronMessages(config.task, [], result);
12595
+ if (result.harnessMessages) {
12596
+ freshConv._harnessMessages = result.harnessMessages;
12597
+ }
12065
12598
  if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
12066
12599
  if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
12067
12600
  freshConv.updatedAt = Date.now();
@@ -12197,7 +12730,7 @@ var runInteractive = async (workingDir, params) => {
12197
12730
  await harness.initialize();
12198
12731
  const identity = await ensureAgentIdentity2(workingDir);
12199
12732
  try {
12200
- const { runInteractiveInk } = await import("./run-interactive-ink-NV6LIQWU.js");
12733
+ const { runInteractiveInk } = await import("./run-interactive-ink-OKE5AV3N.js");
12201
12734
  await runInteractiveInk({
12202
12735
  harness,
12203
12736
  params,