@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.
package/src/index.ts CHANGED
@@ -1696,7 +1696,7 @@ export const createRequestHandler = async (options?: {
1696
1696
  parentConversationId: string,
1697
1697
  task: string,
1698
1698
  ownerId: string,
1699
- isContinuation = false,
1699
+ _isContinuation = false,
1700
1700
  ): Promise<void> => {
1701
1701
  const childHarness = new AgentHarness({
1702
1702
  workingDir,
@@ -1732,12 +1732,12 @@ export const createRequestHandler = async (options?: {
1732
1732
  conversation.lastActivityAt = Date.now();
1733
1733
  await conversationStore.update(conversation);
1734
1734
 
1735
- const harnessMessages = isContinuation && conversation._continuationMessages?.length
1736
- ? [...conversation._continuationMessages]
1735
+ const harnessMessages = conversation._harnessMessages?.length
1736
+ ? [...conversation._harnessMessages]
1737
1737
  : [...conversation.messages];
1738
1738
 
1739
1739
  for await (const event of childHarness.runWithTelemetry({
1740
- task: isContinuation ? undefined : task,
1740
+ task,
1741
1741
  conversationId: childConversationId,
1742
1742
  parameters: {
1743
1743
  __activeConversationId: childConversationId,
@@ -1937,19 +1937,25 @@ export const createRequestHandler = async (options?: {
1937
1937
 
1938
1938
  const conv = await conversationStore.get(childConversationId);
1939
1939
  if (conv) {
1940
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
1941
+ // Always persist intermediate messages so progress is visible
1942
+ if (hasContent) {
1943
+ conv.messages.push({
1944
+ role: "assistant",
1945
+ content: assistantResponse,
1946
+ metadata: toolTimeline.length > 0 || sections.length > 0
1947
+ ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
1948
+ : undefined,
1949
+ });
1950
+ }
1940
1951
  if (runResult?.continuation && runResult.continuationMessages) {
1941
1952
  conv._continuationMessages = runResult.continuationMessages;
1942
1953
  } else {
1943
1954
  conv._continuationMessages = undefined;
1944
- if (assistantResponse.length > 0 || toolTimeline.length > 0) {
1945
- conv.messages.push({
1946
- role: "assistant",
1947
- content: assistantResponse,
1948
- metadata: toolTimeline.length > 0 || sections.length > 0
1949
- ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
1950
- : undefined,
1951
- });
1952
- }
1955
+ conv._continuationCount = undefined;
1956
+ }
1957
+ if (runResult?.continuationMessages) {
1958
+ conv._harnessMessages = runResult.continuationMessages;
1953
1959
  }
1954
1960
  conv.lastActivityAt = Date.now();
1955
1961
  conv.updatedAt = Date.now();
@@ -1961,16 +1967,11 @@ export const createRequestHandler = async (options?: {
1961
1967
  activeConversationRuns.delete(childConversationId);
1962
1968
  try { await childHarness.shutdown(); } catch {}
1963
1969
 
1964
- if (isServerless) {
1965
- const work = selfFetchWithRetry(`/api/internal/subagent/${encodeURIComponent(childConversationId)}/run`, { continuation: true }).catch(err =>
1966
- console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
1967
- );
1968
- doWaitUntil(work);
1969
- } else {
1970
- runSubagent(childConversationId, parentConversationId, task, ownerId, true).catch(err =>
1971
- console.error(`[poncho][subagent] Continuation failed:`, err instanceof Error ? err.message : err),
1972
- );
1973
- }
1970
+ const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(childConversationId)}`).catch(err =>
1971
+ console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
1972
+ );
1973
+ doWaitUntil(work);
1974
+ if (!waitUntilHook) await work;
1974
1975
  return;
1975
1976
  }
1976
1977
 
@@ -2158,11 +2159,14 @@ export const createRequestHandler = async (options?: {
2158
2159
 
2159
2160
  const historyMessages = isContinuationResume && conversation._continuationMessages?.length
2160
2161
  ? [...conversation._continuationMessages]
2161
- : [...conversation.messages];
2162
+ : conversation._harnessMessages?.length
2163
+ ? [...conversation._harnessMessages]
2164
+ : [...conversation.messages];
2162
2165
  let assistantResponse = "";
2163
2166
  let latestRunId = "";
2164
2167
  let runContinuation = false;
2165
2168
  let runContinuationMessages: Message[] | undefined;
2169
+ let runHarnessMessages: Message[] | undefined;
2166
2170
  let runContextTokens = conversation.contextTokens ?? 0;
2167
2171
  let runContextWindow = conversation.contextWindow ?? 0;
2168
2172
  const toolTimeline: string[] = [];
@@ -2222,6 +2226,9 @@ export const createRequestHandler = async (options?: {
2222
2226
  }
2223
2227
  runContextTokens = event.result.contextTokens ?? runContextTokens;
2224
2228
  runContextWindow = event.result.contextWindow ?? runContextWindow;
2229
+ if (event.result.continuationMessages) {
2230
+ runHarnessMessages = event.result.continuationMessages;
2231
+ }
2225
2232
  if (event.result.continuation) {
2226
2233
  runContinuation = true;
2227
2234
  if (event.result.continuationMessages) {
@@ -2250,6 +2257,9 @@ export const createRequestHandler = async (options?: {
2250
2257
  : undefined,
2251
2258
  });
2252
2259
  }
2260
+ if (runHarnessMessages) {
2261
+ freshConv._harnessMessages = runHarnessMessages;
2262
+ }
2253
2263
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
2254
2264
  freshConv.runningCallbackSince = undefined;
2255
2265
  freshConv.runStatus = "idle";
@@ -2485,6 +2495,7 @@ export const createRequestHandler = async (options?: {
2485
2495
  let checkpointedRun = false;
2486
2496
  let runContextTokens = conversation.contextTokens ?? 0;
2487
2497
  let runContextWindow = conversation.contextWindow ?? 0;
2498
+ let resumeHarnessMessages: Message[] | undefined;
2488
2499
 
2489
2500
  const baseMessages = checkpoint.baseMessageCount != null
2490
2501
  ? conversation.messages.slice(0, checkpoint.baseMessageCount)
@@ -2611,6 +2622,9 @@ export const createRequestHandler = async (options?: {
2611
2622
  }
2612
2623
  runContextTokens = event.result.contextTokens ?? runContextTokens;
2613
2624
  runContextWindow = event.result.contextWindow ?? runContextWindow;
2625
+ if (event.result.continuationMessages) {
2626
+ resumeHarnessMessages = event.result.continuationMessages;
2627
+ }
2614
2628
  }
2615
2629
  if (event.type === "run:error") {
2616
2630
  assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
@@ -2675,6 +2689,9 @@ export const createRequestHandler = async (options?: {
2675
2689
  ];
2676
2690
  }
2677
2691
  }
2692
+ if (resumeHarnessMessages) {
2693
+ conv._harnessMessages = resumeHarnessMessages;
2694
+ }
2678
2695
  conv.runtimeRunId = latestRunId || conv.runtimeRunId;
2679
2696
  conv.pendingApprovals = [];
2680
2697
  conv.runStatus = "idle";
@@ -2736,7 +2753,7 @@ export const createRequestHandler = async (options?: {
2736
2753
  };
2737
2754
  await conversationStore.update(existing);
2738
2755
  }
2739
- return { messages: existing.messages };
2756
+ return { messages: existing._harnessMessages?.length ? existing._harnessMessages : existing.messages };
2740
2757
  }
2741
2758
  const now = Date.now();
2742
2759
  const channelMeta = meta.channelId
@@ -2990,12 +3007,15 @@ export const createRequestHandler = async (options?: {
2990
3007
 
2991
3008
  if (!checkpointedRun) {
2992
3009
  await updateConversation((c) => {
2993
- if (runContinuationMessages) {
3010
+ if (runContinuation && runContinuationMessages) {
2994
3011
  c._continuationMessages = runContinuationMessages;
2995
3012
  } else {
2996
3013
  c._continuationMessages = undefined;
2997
3014
  c.messages = buildMessages();
2998
3015
  }
3016
+ if (runContinuationMessages) {
3017
+ c._harnessMessages = runContinuationMessages;
3018
+ }
2999
3019
  c.runtimeRunId = latestRunId || c.runtimeRunId;
3000
3020
  c.pendingApprovals = [];
3001
3021
  c.runStatus = "idle";
@@ -3163,6 +3183,413 @@ export const createRequestHandler = async (options?: {
3163
3183
  return typeof headerValue === "string" && headerValue === internalSecret;
3164
3184
  };
3165
3185
 
3186
+ // ── Unified continuation ──────────────────────────────────────────────
3187
+ const MAX_CONTINUATION_COUNT = 20;
3188
+
3189
+ async function* runContinuation(
3190
+ conversationId: string,
3191
+ ): AsyncGenerator<AgentEvent> {
3192
+ const conversation = await conversationStore.get(conversationId);
3193
+ if (!conversation) return;
3194
+ if (!conversation._continuationMessages?.length) return;
3195
+ if (conversation.runStatus === "running") return;
3196
+
3197
+ const count = (conversation._continuationCount ?? 0) + 1;
3198
+ if (count > MAX_CONTINUATION_COUNT) {
3199
+ console.warn(`[poncho][continuation] Max continuation count (${MAX_CONTINUATION_COUNT}) reached for ${conversationId}`);
3200
+ conversation._continuationMessages = undefined;
3201
+ conversation._continuationCount = undefined;
3202
+ await conversationStore.update(conversation);
3203
+ return;
3204
+ }
3205
+
3206
+ const continuationMessages = [...conversation._continuationMessages];
3207
+ conversation._continuationMessages = undefined;
3208
+ conversation._continuationCount = count;
3209
+ conversation.runStatus = "running";
3210
+ await conversationStore.update(conversation);
3211
+
3212
+ const abortController = new AbortController();
3213
+ activeConversationRuns.set(conversationId, {
3214
+ ownerId: conversation.ownerId,
3215
+ abortController,
3216
+ runId: null,
3217
+ });
3218
+
3219
+ const prevStream = conversationEventStreams.get(conversationId);
3220
+ if (prevStream) {
3221
+ prevStream.finished = false;
3222
+ prevStream.buffer = [];
3223
+ } else {
3224
+ conversationEventStreams.set(conversationId, {
3225
+ buffer: [],
3226
+ subscribers: new Set(),
3227
+ finished: false,
3228
+ });
3229
+ }
3230
+
3231
+ try {
3232
+ if (conversation.parentConversationId) {
3233
+ yield* runSubagentContinuation(conversationId, conversation, continuationMessages);
3234
+ } else {
3235
+ yield* runChatContinuation(conversationId, conversation, continuationMessages);
3236
+ }
3237
+ } finally {
3238
+ activeConversationRuns.delete(conversationId);
3239
+ finishConversationStream(conversationId);
3240
+ }
3241
+ }
3242
+
3243
+ async function* runChatContinuation(
3244
+ conversationId: string,
3245
+ conversation: Conversation,
3246
+ continuationMessages: Message[],
3247
+ ): AsyncGenerator<AgentEvent> {
3248
+ let assistantResponse = "";
3249
+ let latestRunId = conversation.runtimeRunId ?? "";
3250
+ const toolTimeline: string[] = [];
3251
+ const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
3252
+ let currentTools: string[] = [];
3253
+ let currentText = "";
3254
+ let runContextTokens = conversation.contextTokens ?? 0;
3255
+ let runContextWindow = conversation.contextWindow ?? 0;
3256
+ let nextContinuationMessages: Message[] | undefined;
3257
+ let nextHarnessMessages: Message[] | undefined;
3258
+
3259
+ for await (const event of harness.runWithTelemetry({
3260
+ conversationId,
3261
+ parameters: {
3262
+ __activeConversationId: conversationId,
3263
+ __ownerId: conversation.ownerId,
3264
+ },
3265
+ messages: continuationMessages,
3266
+ abortSignal: activeConversationRuns.get(conversationId)?.abortController.signal,
3267
+ })) {
3268
+ if (event.type === "run:started") {
3269
+ latestRunId = event.runId;
3270
+ runOwners.set(event.runId, conversation.ownerId);
3271
+ runConversations.set(event.runId, conversationId);
3272
+ const active = activeConversationRuns.get(conversationId);
3273
+ if (active) active.runId = event.runId;
3274
+ }
3275
+ if (event.type === "model:chunk") {
3276
+ if (currentTools.length > 0) {
3277
+ sections.push({ type: "tools", content: currentTools });
3278
+ currentTools = [];
3279
+ if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
3280
+ assistantResponse += " ";
3281
+ }
3282
+ }
3283
+ assistantResponse += event.content;
3284
+ currentText += event.content;
3285
+ }
3286
+ if (event.type === "tool:started") {
3287
+ if (currentText.length > 0) {
3288
+ sections.push({ type: "text", content: currentText });
3289
+ currentText = "";
3290
+ }
3291
+ const toolText = `- start \`${event.tool}\``;
3292
+ toolTimeline.push(toolText);
3293
+ currentTools.push(toolText);
3294
+ }
3295
+ if (event.type === "tool:completed") {
3296
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
3297
+ toolTimeline.push(toolText);
3298
+ currentTools.push(toolText);
3299
+ }
3300
+ if (event.type === "tool:error") {
3301
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
3302
+ toolTimeline.push(toolText);
3303
+ currentTools.push(toolText);
3304
+ }
3305
+ if (event.type === "run:completed") {
3306
+ runContextTokens = event.result.contextTokens ?? runContextTokens;
3307
+ runContextWindow = event.result.contextWindow ?? runContextWindow;
3308
+ if (event.result.continuation && event.result.continuationMessages) {
3309
+ nextContinuationMessages = event.result.continuationMessages;
3310
+ }
3311
+ if (event.result.continuationMessages) {
3312
+ nextHarnessMessages = event.result.continuationMessages;
3313
+ }
3314
+ if (!assistantResponse && event.result.response) {
3315
+ assistantResponse = event.result.response;
3316
+ }
3317
+ }
3318
+ if (event.type === "run:error") {
3319
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
3320
+ }
3321
+ await telemetry.emit(event);
3322
+ broadcastEvent(conversationId, event);
3323
+ yield event;
3324
+ }
3325
+
3326
+ if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
3327
+ if (currentText.length > 0) sections.push({ type: "text", content: currentText });
3328
+
3329
+ const freshConv = await conversationStore.get(conversationId);
3330
+ if (!freshConv) return;
3331
+
3332
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
3333
+ const assistantMetadata =
3334
+ toolTimeline.length > 0 || sections.length > 0
3335
+ ? ({
3336
+ toolActivity: [...toolTimeline],
3337
+ sections: sections.length > 0 ? sections : undefined,
3338
+ } as Message["metadata"])
3339
+ : undefined;
3340
+
3341
+ if (nextContinuationMessages) {
3342
+ if (hasContent) {
3343
+ freshConv.messages = [
3344
+ ...freshConv.messages,
3345
+ { role: "assistant", content: assistantResponse, metadata: assistantMetadata },
3346
+ ];
3347
+ }
3348
+ freshConv._continuationMessages = nextContinuationMessages;
3349
+ freshConv._continuationCount = conversation._continuationCount;
3350
+ } else {
3351
+ if (hasContent) {
3352
+ freshConv.messages = [
3353
+ ...freshConv.messages,
3354
+ { role: "assistant", content: assistantResponse, metadata: assistantMetadata },
3355
+ ];
3356
+ }
3357
+ freshConv._continuationMessages = undefined;
3358
+ freshConv._continuationCount = undefined;
3359
+ }
3360
+
3361
+ if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
3362
+ freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
3363
+ freshConv.pendingApprovals = [];
3364
+ if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
3365
+ if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
3366
+ freshConv.runStatus = "idle";
3367
+ freshConv.updatedAt = Date.now();
3368
+ await conversationStore.update(freshConv);
3369
+ }
3370
+
3371
+ async function* runSubagentContinuation(
3372
+ conversationId: string,
3373
+ conversation: Conversation,
3374
+ continuationMessages: Message[],
3375
+ ): AsyncGenerator<AgentEvent> {
3376
+ const parentConversationId = conversation.parentConversationId!;
3377
+ const task = conversation.subagentMeta?.task ?? "";
3378
+ const ownerId = conversation.ownerId;
3379
+
3380
+ const childHarness = new AgentHarness({
3381
+ workingDir,
3382
+ environment: resolveHarnessEnvironment(),
3383
+ uploadStore,
3384
+ });
3385
+ await childHarness.initialize();
3386
+ childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
3387
+
3388
+ const childAbortController = activeConversationRuns.get(conversationId)?.abortController ?? new AbortController();
3389
+ activeSubagentRuns.set(conversationId, { abortController: childAbortController, harness: childHarness, parentConversationId });
3390
+
3391
+ let assistantResponse = "";
3392
+ let latestRunId = "";
3393
+ let runResult: { status: string; response?: string; steps: number; duration: number; continuation?: boolean; continuationMessages?: Message[] } | undefined;
3394
+ const toolTimeline: string[] = [];
3395
+ const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
3396
+ let currentTools: string[] = [];
3397
+ let currentText = "";
3398
+
3399
+ try {
3400
+ for await (const event of childHarness.runWithTelemetry({
3401
+ conversationId,
3402
+ parameters: {
3403
+ __activeConversationId: conversationId,
3404
+ __ownerId: ownerId,
3405
+ },
3406
+ messages: continuationMessages,
3407
+ abortSignal: childAbortController.signal,
3408
+ })) {
3409
+ if (event.type === "run:started") {
3410
+ latestRunId = event.runId;
3411
+ const active = activeConversationRuns.get(conversationId);
3412
+ if (active) active.runId = event.runId;
3413
+ }
3414
+ if (event.type === "model:chunk") {
3415
+ if (currentTools.length > 0) {
3416
+ sections.push({ type: "tools", content: currentTools });
3417
+ currentTools = [];
3418
+ if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
3419
+ assistantResponse += " ";
3420
+ }
3421
+ }
3422
+ assistantResponse += event.content;
3423
+ currentText += event.content;
3424
+ }
3425
+ if (event.type === "tool:started") {
3426
+ if (currentText.length > 0) {
3427
+ sections.push({ type: "text", content: currentText });
3428
+ currentText = "";
3429
+ }
3430
+ const toolText = `- start \`${event.tool}\``;
3431
+ toolTimeline.push(toolText);
3432
+ currentTools.push(toolText);
3433
+ }
3434
+ if (event.type === "tool:completed") {
3435
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
3436
+ toolTimeline.push(toolText);
3437
+ currentTools.push(toolText);
3438
+ }
3439
+ if (event.type === "tool:error") {
3440
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
3441
+ toolTimeline.push(toolText);
3442
+ currentTools.push(toolText);
3443
+ }
3444
+ if (event.type === "run:completed") {
3445
+ runResult = {
3446
+ status: event.result.status,
3447
+ response: event.result.response,
3448
+ steps: event.result.steps,
3449
+ duration: event.result.duration,
3450
+ continuation: event.result.continuation,
3451
+ continuationMessages: event.result.continuationMessages,
3452
+ };
3453
+ if (!assistantResponse && event.result.response) {
3454
+ assistantResponse = event.result.response;
3455
+ }
3456
+ }
3457
+ if (event.type === "run:error") {
3458
+ assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
3459
+ }
3460
+ broadcastEvent(conversationId, event);
3461
+ yield event;
3462
+ }
3463
+
3464
+ if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
3465
+ if (currentText.length > 0) sections.push({ type: "text", content: currentText });
3466
+
3467
+ const conv = await conversationStore.get(conversationId);
3468
+ if (conv) {
3469
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
3470
+ if (runResult?.continuation && runResult.continuationMessages) {
3471
+ if (hasContent) {
3472
+ conv.messages.push({
3473
+ role: "assistant",
3474
+ content: assistantResponse,
3475
+ metadata: toolTimeline.length > 0 || sections.length > 0
3476
+ ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
3477
+ : undefined,
3478
+ });
3479
+ }
3480
+ conv._continuationMessages = runResult.continuationMessages;
3481
+ conv._continuationCount = conversation._continuationCount;
3482
+ } else {
3483
+ conv._continuationMessages = undefined;
3484
+ conv._continuationCount = undefined;
3485
+ if (hasContent) {
3486
+ conv.messages.push({
3487
+ role: "assistant",
3488
+ content: assistantResponse,
3489
+ metadata: toolTimeline.length > 0 || sections.length > 0
3490
+ ? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
3491
+ : undefined,
3492
+ });
3493
+ }
3494
+ }
3495
+ if (runResult?.continuationMessages) {
3496
+ conv._harnessMessages = runResult.continuationMessages;
3497
+ }
3498
+ conv.lastActivityAt = Date.now();
3499
+ conv.runStatus = "idle";
3500
+ conv.updatedAt = Date.now();
3501
+
3502
+ if (runResult?.continuation) {
3503
+ await conversationStore.update(conv);
3504
+ activeSubagentRuns.delete(conversationId);
3505
+ try { await childHarness.shutdown(); } catch {}
3506
+ return;
3507
+ }
3508
+
3509
+ conv.subagentMeta = { ...conv.subagentMeta!, status: "completed" };
3510
+ await conversationStore.update(conv);
3511
+ }
3512
+
3513
+ activeSubagentRuns.delete(conversationId);
3514
+ broadcastEvent(parentConversationId, {
3515
+ type: "subagent:completed",
3516
+ subagentId: conversationId,
3517
+ conversationId,
3518
+ });
3519
+
3520
+ let subagentResponse = runResult?.response ?? assistantResponse;
3521
+ if (!subagentResponse) {
3522
+ const freshSubConv = await conversationStore.get(conversationId);
3523
+ if (freshSubConv) {
3524
+ const lastAssistant = [...freshSubConv.messages].reverse().find(m => m.role === "assistant");
3525
+ if (lastAssistant) {
3526
+ subagentResponse = typeof lastAssistant.content === "string" ? lastAssistant.content : "";
3527
+ }
3528
+ }
3529
+ }
3530
+
3531
+ const parentConv = await conversationStore.get(parentConversationId);
3532
+ if (parentConv) {
3533
+ const result: PendingSubagentResult = {
3534
+ subagentId: conversationId,
3535
+ task,
3536
+ status: "completed",
3537
+ result: { status: "completed", response: subagentResponse, steps: runResult?.steps ?? 0, tokens: { input: 0, output: 0, cached: 0 }, duration: runResult?.duration ?? 0 },
3538
+ timestamp: Date.now(),
3539
+ };
3540
+ await conversationStore.appendSubagentResult(parentConversationId, result);
3541
+
3542
+ if (isServerless) {
3543
+ selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(parentConversationId)}/subagent-callback`).catch(err =>
3544
+ console.error(`[poncho][subagent] Callback self-fetch failed:`, err instanceof Error ? err.message : err),
3545
+ );
3546
+ } else {
3547
+ processSubagentCallback(parentConversationId).catch(err =>
3548
+ console.error(`[poncho][subagent] Callback failed:`, err instanceof Error ? err.message : err),
3549
+ );
3550
+ }
3551
+ }
3552
+
3553
+ try { await childHarness.shutdown(); } catch {}
3554
+ } catch (err) {
3555
+ activeSubagentRuns.delete(conversationId);
3556
+ try { await childHarness.shutdown(); } catch {}
3557
+
3558
+ const conv = await conversationStore.get(conversationId);
3559
+ if (conv) {
3560
+ conv.subagentMeta = { ...conv.subagentMeta!, status: "error", error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) } };
3561
+ conv.runStatus = "idle";
3562
+ conv._continuationMessages = undefined;
3563
+ conv._continuationCount = undefined;
3564
+ conv.updatedAt = Date.now();
3565
+ await conversationStore.update(conv);
3566
+ }
3567
+
3568
+ broadcastEvent(conversation.parentConversationId!, {
3569
+ type: "subagent:completed",
3570
+ subagentId: conversationId,
3571
+ conversationId,
3572
+ });
3573
+
3574
+ const parentConv = await conversationStore.get(conversation.parentConversationId!);
3575
+ if (parentConv) {
3576
+ const result: PendingSubagentResult = {
3577
+ subagentId: conversationId,
3578
+ task,
3579
+ status: "error",
3580
+ error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
3581
+ timestamp: Date.now(),
3582
+ };
3583
+ await conversationStore.appendSubagentResult(conversation.parentConversationId!, result);
3584
+ if (isServerless) {
3585
+ selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversation.parentConversationId!)}/subagent-callback`).catch(() => {});
3586
+ } else {
3587
+ processSubagentCallback(conversation.parentConversationId!).catch(() => {});
3588
+ }
3589
+ }
3590
+ }
3591
+ }
3592
+
3166
3593
  const messagingAdapters = new Map<string, MessagingAdapter>();
3167
3594
  const messagingBridges: AgentBridge[] = [];
3168
3595
  if (config?.messaging && config.messaging.length > 0) {
@@ -3539,7 +3966,7 @@ export const createRequestHandler = async (options?: {
3539
3966
  const subagentRunMatch = pathname.match(/^\/api\/internal\/subagent\/([^/]+)\/run$/);
3540
3967
  if (subagentRunMatch) {
3541
3968
  const subagentId = decodeURIComponent(subagentRunMatch[1]!);
3542
- const body = (await readRequestBody(request)) as { continuation?: boolean; resume?: boolean } | undefined;
3969
+ const body = (await readRequestBody(request)) as { resume?: boolean } | undefined;
3543
3970
  writeJson(response, 202, { ok: true });
3544
3971
  const work = (async () => {
3545
3972
  try {
@@ -3552,9 +3979,8 @@ export const createRequestHandler = async (options?: {
3552
3979
  return;
3553
3980
  }
3554
3981
 
3555
- const isContinuation = body?.continuation === true;
3556
- const task = isContinuation ? conv.subagentMeta?.task ?? "" : (conv.messages.find(m => m.role === "user")?.content as string) ?? conv.subagentMeta?.task ?? "";
3557
- await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, isContinuation);
3982
+ const task = (conv.messages.find(m => m.role === "user")?.content as string) ?? conv.subagentMeta?.task ?? "";
3983
+ await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, false);
3558
3984
  } catch (err) {
3559
3985
  console.error(`[poncho][internal] subagent run error for ${subagentId}:`, err instanceof Error ? err.message : err);
3560
3986
  }
@@ -3574,6 +4000,29 @@ export const createRequestHandler = async (options?: {
3574
4000
  return;
3575
4001
  }
3576
4002
 
4003
+ const continueMatch = pathname.match(/^\/api\/internal\/continue\/([^/]+)$/);
4004
+ if (continueMatch) {
4005
+ const conversationId = decodeURIComponent(continueMatch[1]!);
4006
+ writeJson(response, 202, { ok: true });
4007
+ const work = (async () => {
4008
+ try {
4009
+ for await (const _event of runContinuation(conversationId)) {
4010
+ // Events are already broadcast inside runContinuation
4011
+ }
4012
+ // Chain: if another continuation is needed, fire next self-fetch
4013
+ const conv = await conversationStore.get(conversationId);
4014
+ if (conv?._continuationMessages?.length) {
4015
+ await selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`);
4016
+ }
4017
+ } catch (err) {
4018
+ console.error(`[poncho][internal-continue] Error for ${conversationId}:`, err instanceof Error ? err.message : err);
4019
+ }
4020
+ })();
4021
+ doWaitUntil(work);
4022
+ if (!waitUntilHook) await work;
4023
+ return;
4024
+ }
4025
+
3577
4026
  writeJson(response, 404, { error: "Not found" });
3578
4027
  return;
3579
4028
  }
@@ -4299,6 +4748,7 @@ export const createRequestHandler = async (options?: {
4299
4748
  ...conversation,
4300
4749
  pendingApprovals: storedPending,
4301
4750
  _continuationMessages: undefined,
4751
+ _harnessMessages: undefined,
4302
4752
  },
4303
4753
  subagentPendingApprovals: subagentPending,
4304
4754
  hasActiveRun: hasActiveRun || hasPendingCallbackResults,
@@ -4464,6 +4914,88 @@ export const createRequestHandler = async (options?: {
4464
4914
  return;
4465
4915
  }
4466
4916
 
4917
+ // ── Public continuation endpoint (SSE) ──
4918
+ const conversationContinueMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/continue$/);
4919
+ if (conversationContinueMatch && request.method === "POST") {
4920
+ const conversationId = decodeURIComponent(conversationContinueMatch[1] ?? "");
4921
+ const conversation = await conversationStore.get(conversationId);
4922
+ if (!conversation || conversation.ownerId !== ownerId) {
4923
+ writeJson(response, 404, {
4924
+ code: "CONVERSATION_NOT_FOUND",
4925
+ message: "Conversation not found",
4926
+ });
4927
+ return;
4928
+ }
4929
+ if (conversation.parentConversationId) {
4930
+ writeJson(response, 403, {
4931
+ code: "SUBAGENT_READ_ONLY",
4932
+ message: "Subagent conversations are read-only.",
4933
+ });
4934
+ return;
4935
+ }
4936
+
4937
+ response.writeHead(200, {
4938
+ "Content-Type": "text/event-stream",
4939
+ "Cache-Control": "no-cache",
4940
+ Connection: "keep-alive",
4941
+ "X-Accel-Buffering": "no",
4942
+ });
4943
+
4944
+ const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
4945
+ if (evt.type.startsWith("subagent:")) {
4946
+ try { response.write(formatSseEvent(evt)); } catch {}
4947
+ }
4948
+ });
4949
+
4950
+ let eventCount = 0;
4951
+ try {
4952
+ for await (const event of runContinuation(conversationId)) {
4953
+ eventCount++;
4954
+ let sseEvent: AgentEvent = event;
4955
+ if (sseEvent.type === "run:completed") {
4956
+ const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
4957
+ r => r.parentConversationId === conversationId,
4958
+ );
4959
+ const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
4960
+ sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
4961
+ }
4962
+ try {
4963
+ response.write(formatSseEvent(sseEvent));
4964
+ } catch {
4965
+ // Client disconnected — continue processing so the run completes
4966
+ }
4967
+ emitBrowserStatusIfActive(conversationId, event, response);
4968
+ }
4969
+ } catch (err) {
4970
+ const errorEvent: AgentEvent = {
4971
+ type: "run:error",
4972
+ runId: "",
4973
+ error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
4974
+ };
4975
+ try { response.write(formatSseEvent(errorEvent)); } catch {}
4976
+ } finally {
4977
+ unsubSubagentEvents();
4978
+ }
4979
+
4980
+ if (eventCount === 0) {
4981
+ try { response.write("event: stream:end\ndata: {}\n\n"); } catch {}
4982
+ } else {
4983
+ // If the run produced events and another continuation is needed,
4984
+ // fire a delayed safety net in case the client disconnects before
4985
+ // POSTing the next /continue.
4986
+ const freshConv = await conversationStore.get(conversationId);
4987
+ if (freshConv?._continuationMessages?.length) {
4988
+ doWaitUntil(
4989
+ new Promise(r => setTimeout(r, 3000)).then(() =>
4990
+ selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
4991
+ ),
4992
+ );
4993
+ }
4994
+ }
4995
+ response.end();
4996
+ return;
4997
+ }
4998
+
4467
4999
  const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
4468
5000
  if (conversationMessageMatch && request.method === "POST") {
4469
5001
  const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
@@ -4485,7 +5017,6 @@ export const createRequestHandler = async (options?: {
4485
5017
  let messageText = "";
4486
5018
  let bodyParameters: Record<string, unknown> | undefined;
4487
5019
  let files: FileInput[] = [];
4488
- let isContinuation = false;
4489
5020
 
4490
5021
  const contentType = request.headers["content-type"] ?? "";
4491
5022
  if (contentType.includes("multipart/form-data")) {
@@ -4496,15 +5027,10 @@ export const createRequestHandler = async (options?: {
4496
5027
  } else {
4497
5028
  const body = (await readRequestBody(request)) as {
4498
5029
  message?: string;
4499
- continuation?: boolean;
4500
5030
  parameters?: Record<string, unknown>;
4501
5031
  files?: Array<{ data?: string; mediaType?: string; filename?: string }>;
4502
5032
  };
4503
- if (body.continuation === true) {
4504
- isContinuation = true;
4505
- } else {
4506
- messageText = body.message?.trim() ?? "";
4507
- }
5033
+ messageText = body.message?.trim() ?? "";
4508
5034
  bodyParameters = body.parameters;
4509
5035
  if (Array.isArray(body.files)) {
4510
5036
  files = body.files
@@ -4513,7 +5039,7 @@ export const createRequestHandler = async (options?: {
4513
5039
  );
4514
5040
  }
4515
5041
  }
4516
- if (!isContinuation && !messageText) {
5042
+ if (!messageText) {
4517
5043
  writeJson(response, 400, {
4518
5044
  code: "VALIDATION_ERROR",
4519
5045
  message: "message is required",
@@ -4539,7 +5065,6 @@ export const createRequestHandler = async (options?: {
4539
5065
  runId: null,
4540
5066
  });
4541
5067
  if (
4542
- !isContinuation &&
4543
5068
  conversation.messages.length === 0 &&
4544
5069
  (conversation.title === "New conversation" || conversation.title.trim().length === 0)
4545
5070
  ) {
@@ -4551,8 +5076,8 @@ export const createRequestHandler = async (options?: {
4551
5076
  Connection: "keep-alive",
4552
5077
  "X-Accel-Buffering": "no",
4553
5078
  });
4554
- const harnessMessages = isContinuation && conversation._continuationMessages?.length
4555
- ? [...conversation._continuationMessages]
5079
+ const harnessMessages = conversation._harnessMessages?.length
5080
+ ? [...conversation._harnessMessages]
4556
5081
  : [...conversation.messages];
4557
5082
  const historyMessages = [...conversation.messages];
4558
5083
  const preRunMessages = [...conversation.messages];
@@ -4568,8 +5093,9 @@ export const createRequestHandler = async (options?: {
4568
5093
  let runContextTokens = conversation.contextTokens ?? 0;
4569
5094
  let runContextWindow = conversation.contextWindow ?? 0;
4570
5095
  let runContinuationMessages: Message[] | undefined;
4571
- let userContent: Message["content"] | undefined = isContinuation ? undefined : messageText;
4572
- if (!isContinuation && files.length > 0) {
5096
+ let runHarnessMessages: Message[] | undefined;
5097
+ let userContent: Message["content"] | undefined = messageText;
5098
+ if (files.length > 0) {
4573
5099
  try {
4574
5100
  const uploadedParts = await Promise.all(
4575
5101
  files.map(async (f) => {
@@ -4612,9 +5138,10 @@ export const createRequestHandler = async (options?: {
4612
5138
  });
4613
5139
 
4614
5140
  try {
4615
- if (!isContinuation) {
5141
+ {
4616
5142
  conversation.messages = [...historyMessages, { role: "user", content: userContent! }];
4617
5143
  conversation.subagentCallbackCount = 0;
5144
+ conversation._continuationCount = undefined;
4618
5145
  conversation.updatedAt = Date.now();
4619
5146
  conversationStore.update(conversation).catch((err) => {
4620
5147
  console.error("[poncho] Failed to persist user turn:", err);
@@ -4692,7 +5219,7 @@ export const createRequestHandler = async (options?: {
4692
5219
  };
4693
5220
 
4694
5221
  for await (const event of harness.runWithTelemetry({
4695
- task: isContinuation ? undefined : messageText,
5222
+ task: messageText,
4696
5223
  conversationId,
4697
5224
  parameters: {
4698
5225
  ...(bodyParameters ?? {}),
@@ -4701,7 +5228,7 @@ export const createRequestHandler = async (options?: {
4701
5228
  __ownerId: ownerId,
4702
5229
  },
4703
5230
  messages: harnessMessages,
4704
- files: !isContinuation && files.length > 0 ? files : undefined,
5231
+ files: files.length > 0 ? files : undefined,
4705
5232
  abortSignal: abortController.signal,
4706
5233
  })) {
4707
5234
  if (event.type === "run:started") {
@@ -4812,15 +5339,42 @@ export const createRequestHandler = async (options?: {
4812
5339
  }
4813
5340
  runContextTokens = event.result.contextTokens ?? runContextTokens;
4814
5341
  runContextWindow = event.result.contextWindow ?? runContextWindow;
5342
+ if (event.result.continuationMessages) {
5343
+ runHarnessMessages = event.result.continuationMessages;
5344
+ }
4815
5345
  if (event.result.continuation && event.result.continuationMessages) {
4816
5346
  runContinuationMessages = event.result.continuationMessages;
5347
+
5348
+ // Persist intermediate messages so clients connecting later
5349
+ // see progress, plus _continuationMessages for the next step.
5350
+ const intSections = [...sections];
5351
+ if (currentTools.length > 0) intSections.push({ type: "tools", content: [...currentTools] });
5352
+ if (currentText.length > 0) intSections.push({ type: "text", content: currentText });
5353
+ const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0 || intSections.length > 0;
5354
+ const intMetadata = toolTimeline.length > 0 || intSections.length > 0
5355
+ ? ({ toolActivity: [...toolTimeline], sections: intSections.length > 0 ? intSections : undefined } as Message["metadata"])
5356
+ : undefined;
5357
+ conversation.messages = [
5358
+ ...historyMessages,
5359
+ ...(userContent != null ? [{ role: "user" as const, content: userContent }] : []),
5360
+ ...(hasContent ? [{ role: "assistant" as const, content: assistantResponse, metadata: intMetadata }] : []),
5361
+ ];
4817
5362
  conversation._continuationMessages = runContinuationMessages;
5363
+ conversation._harnessMessages = runContinuationMessages;
4818
5364
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
4819
5365
  conversation.pendingApprovals = [];
4820
5366
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
4821
5367
  if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
4822
5368
  conversation.updatedAt = Date.now();
4823
5369
  await conversationStore.update(conversation);
5370
+
5371
+ // Delayed safety net: if the client doesn't POST to /continue
5372
+ // within 3 seconds (e.g. browser closed), the server picks it up.
5373
+ doWaitUntil(
5374
+ new Promise(r => setTimeout(r, 3000)).then(() =>
5375
+ selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
5376
+ ),
5377
+ );
4824
5378
  }
4825
5379
  }
4826
5380
  await telemetry.emit(event);
@@ -4878,6 +5432,9 @@ export const createRequestHandler = async (options?: {
4878
5432
  ]
4879
5433
  : [...historyMessages, ...userTurn];
4880
5434
  conversation._continuationMessages = undefined;
5435
+ if (runHarnessMessages) {
5436
+ conversation._harnessMessages = runHarnessMessages;
5437
+ }
4881
5438
  conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
4882
5439
  conversation.pendingApprovals = [];
4883
5440
  if (runContextTokens > 0) conversation.contextTokens = runContextTokens;
@@ -4997,23 +5554,11 @@ export const createRequestHandler = async (options?: {
4997
5554
  }
4998
5555
 
4999
5556
  const urlObj = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
5000
- const continueConversationId = urlObj.searchParams.get("continue");
5001
- const continuationCount = Number(urlObj.searchParams.get("continuation") ?? "0");
5002
- const maxContinuations = 5;
5003
-
5004
- if (continuationCount >= maxContinuations) {
5005
- writeJson(response, 200, {
5006
- conversationId: continueConversationId,
5007
- status: "max_continuations_reached",
5008
- continuations: continuationCount,
5009
- });
5010
- return;
5011
- }
5012
5557
 
5013
5558
  const cronOwnerId = ownerId;
5014
5559
  const start = Date.now();
5015
5560
 
5016
- if (cronJob.channel && !continueConversationId) {
5561
+ if (cronJob.channel) {
5017
5562
  const adapter = messagingAdapters.get(cronJob.channel);
5018
5563
  if (!adapter) {
5019
5564
  writeJson(response, 200, {
@@ -5051,10 +5596,13 @@ export const createRequestHandler = async (options?: {
5051
5596
  if (!conv) continue;
5052
5597
 
5053
5598
  const task = `[Scheduled: ${jobName}]\n${cronJob.task}`;
5054
- const historyMessages = [...conv.messages];
5599
+ const historyMessages = conv._harnessMessages?.length
5600
+ ? [...conv._harnessMessages]
5601
+ : [...conv.messages];
5055
5602
  try {
5056
5603
  let assistantResponse = "";
5057
5604
  let steps = 0;
5605
+ let cronHarnessMessages: Message[] | undefined;
5058
5606
  for await (const event of harness.runWithTelemetry({
5059
5607
  task,
5060
5608
  conversationId: conv.conversationId,
@@ -5069,6 +5617,9 @@ export const createRequestHandler = async (options?: {
5069
5617
  if (!assistantResponse && event.result.response) {
5070
5618
  assistantResponse = event.result.response;
5071
5619
  }
5620
+ if (event.result.continuationMessages) {
5621
+ cronHarnessMessages = event.result.continuationMessages;
5622
+ }
5072
5623
  }
5073
5624
  await telemetry.emit(event);
5074
5625
  }
@@ -5078,6 +5629,9 @@ export const createRequestHandler = async (options?: {
5078
5629
  { role: "user" as const, content: task },
5079
5630
  ...(assistantResponse ? [{ role: "assistant" as const, content: assistantResponse }] : []),
5080
5631
  ];
5632
+ if (cronHarnessMessages) {
5633
+ conv._harnessMessages = cronHarnessMessages;
5634
+ }
5081
5635
  conv.updatedAt = Date.now();
5082
5636
  await conversationStore.update(conv);
5083
5637
 
@@ -5117,32 +5671,12 @@ export const createRequestHandler = async (options?: {
5117
5671
  }
5118
5672
 
5119
5673
  try {
5120
- let conversation;
5121
- let historyMessages: Message[] = [];
5122
-
5123
- if (continueConversationId) {
5124
- conversation = await conversationStore.get(continueConversationId);
5125
- if (!conversation) {
5126
- writeJson(response, 404, {
5127
- code: "CONVERSATION_NOT_FOUND",
5128
- message: "Continuation conversation not found",
5129
- });
5130
- return;
5131
- }
5132
- historyMessages = conversation._continuationMessages?.length
5133
- ? [...conversation._continuationMessages]
5134
- : [...conversation.messages];
5135
- if (conversation._continuationMessages?.length) {
5136
- conversation._continuationMessages = undefined;
5137
- await conversationStore.update(conversation);
5138
- }
5139
- } else {
5140
- const timestamp = new Date().toISOString();
5141
- conversation = await conversationStore.create(
5142
- cronOwnerId,
5143
- `[cron] ${jobName} ${timestamp}`,
5144
- );
5145
- }
5674
+ const timestamp = new Date().toISOString();
5675
+ const conversation = await conversationStore.create(
5676
+ cronOwnerId,
5677
+ `[cron] ${jobName} ${timestamp}`,
5678
+ );
5679
+ const historyMessages: Message[] = [];
5146
5680
 
5147
5681
  const convId = conversation.conversationId;
5148
5682
  activeConversationRuns.set(convId, {
@@ -5160,7 +5694,7 @@ export const createRequestHandler = async (options?: {
5160
5694
  const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
5161
5695
  let currentTools: string[] = [];
5162
5696
  let currentText = "";
5163
- let runResult: { status: string; steps: number; continuation?: boolean; contextTokens?: number; contextWindow?: number } = {
5697
+ let runResult: { status: string; steps: number; continuation?: boolean; contextTokens?: number; contextWindow?: number; harnessMessages?: Message[] } = {
5164
5698
  status: "completed",
5165
5699
  steps: 0,
5166
5700
  };
@@ -5217,6 +5751,7 @@ export const createRequestHandler = async (options?: {
5217
5751
  continuation: event.result.continuation,
5218
5752
  contextTokens: event.result.contextTokens,
5219
5753
  contextWindow: event.result.contextWindow,
5754
+ harnessMessages: event.result.continuationMessages,
5220
5755
  };
5221
5756
  if (event.result.continuation && event.result.continuationMessages) {
5222
5757
  runContinuationMessages = event.result.continuationMessages;
@@ -5251,20 +5786,23 @@ export const createRequestHandler = async (options?: {
5251
5786
  : undefined;
5252
5787
  const messages: Message[] = [
5253
5788
  ...historyMessages,
5254
- ...(continueConversationId
5255
- ? []
5256
- : [{ role: "user" as const, content: cronJob.task }]),
5789
+ { role: "user" as const, content: cronJob.task },
5257
5790
  ...(hasContent
5258
5791
  ? [{ role: "assistant" as const, content: assistantResponse, metadata: assistantMetadata }]
5259
5792
  : []),
5260
5793
  ];
5261
5794
  const freshConv = await conversationStore.get(convId);
5262
5795
  if (freshConv) {
5796
+ // Always persist intermediate messages so clients see progress
5797
+ freshConv.messages = messages;
5263
5798
  if (runContinuationMessages) {
5264
5799
  freshConv._continuationMessages = runContinuationMessages;
5265
5800
  } else {
5266
5801
  freshConv._continuationMessages = undefined;
5267
- freshConv.messages = messages;
5802
+ freshConv._continuationCount = undefined;
5803
+ }
5804
+ if (runResult.harnessMessages) {
5805
+ freshConv._harnessMessages = runResult.harnessMessages;
5268
5806
  }
5269
5807
  freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
5270
5808
  if (runResult.contextTokens) freshConv.contextTokens = runResult.contextTokens;
@@ -5274,15 +5812,13 @@ export const createRequestHandler = async (options?: {
5274
5812
  }
5275
5813
 
5276
5814
  if (runResult.continuation) {
5277
- const continuationPath = `/api/cron/${encodeURIComponent(jobName)}?continue=${encodeURIComponent(convId)}&continuation=${continuationCount + 1}`;
5278
- const work = selfFetchWithRetry(continuationPath).catch(err =>
5815
+ const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(err =>
5279
5816
  console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
5280
5817
  );
5281
5818
  doWaitUntil(work);
5282
5819
  writeJson(response, 200, {
5283
5820
  conversationId: convId,
5284
5821
  status: "continued",
5285
- continuations: continuationCount + 1,
5286
5822
  duration: Date.now() - start,
5287
5823
  });
5288
5824
  return;
@@ -5401,6 +5937,7 @@ export const startDevServer = async (
5401
5937
  hasContent: boolean;
5402
5938
  contextTokens: number;
5403
5939
  contextWindow: number;
5940
+ harnessMessages?: Message[];
5404
5941
  };
5405
5942
 
5406
5943
  const runCronAgent = async (
@@ -5414,6 +5951,7 @@ export const startDevServer = async (
5414
5951
  let steps = 0;
5415
5952
  let contextTokens = 0;
5416
5953
  let contextWindow = 0;
5954
+ let harnessMessages: Message[] | undefined;
5417
5955
  const toolTimeline: string[] = [];
5418
5956
  const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
5419
5957
  let currentTools: string[] = [];
@@ -5459,6 +5997,9 @@ export const startDevServer = async (
5459
5997
  steps = event.result.steps;
5460
5998
  contextTokens = event.result.contextTokens ?? 0;
5461
5999
  contextWindow = event.result.contextWindow ?? 0;
6000
+ if (event.result.continuationMessages) {
6001
+ harnessMessages = event.result.continuationMessages;
6002
+ }
5462
6003
  if (!assistantResponse && event.result.response) {
5463
6004
  assistantResponse = event.result.response;
5464
6005
  }
@@ -5478,7 +6019,7 @@ export const startDevServer = async (
5478
6019
  sections: sections.length > 0 ? sections : undefined,
5479
6020
  } as Message["metadata"])
5480
6021
  : undefined;
5481
- return { response: assistantResponse, steps, assistantMetadata, hasContent, contextTokens, contextWindow };
6022
+ return { response: assistantResponse, steps, assistantMetadata, hasContent, contextTokens, contextWindow, harnessMessages };
5482
6023
  };
5483
6024
 
5484
6025
  const buildCronMessages = (
@@ -5548,7 +6089,9 @@ export const startDevServer = async (
5548
6089
  if (!conversation) continue;
5549
6090
 
5550
6091
  const task = `[Scheduled: ${jobName}]\n${config.task}`;
5551
- const historyMessages = [...conversation.messages];
6092
+ const historyMessages = conversation._harnessMessages?.length
6093
+ ? [...conversation._harnessMessages]
6094
+ : [...conversation.messages];
5552
6095
  const convId = conversation.conversationId;
5553
6096
 
5554
6097
  activeRuns?.set(convId, {
@@ -5566,6 +6109,9 @@ export const startDevServer = async (
5566
6109
  const freshConv = await store.get(convId);
5567
6110
  if (freshConv) {
5568
6111
  freshConv.messages = buildCronMessages(task, historyMessages, result);
6112
+ if (result.harnessMessages) {
6113
+ freshConv._harnessMessages = result.harnessMessages;
6114
+ }
5569
6115
  if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
5570
6116
  if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
5571
6117
  freshConv.updatedAt = Date.now();
@@ -5632,6 +6178,9 @@ export const startDevServer = async (
5632
6178
  const freshConv = await store.get(cronConvId);
5633
6179
  if (freshConv) {
5634
6180
  freshConv.messages = buildCronMessages(config.task, [], result);
6181
+ if (result.harnessMessages) {
6182
+ freshConv._harnessMessages = result.harnessMessages;
6183
+ }
5635
6184
  if (result.contextTokens > 0) freshConv.contextTokens = result.contextTokens;
5636
6185
  if (result.contextWindow > 0) freshConv.contextWindow = result.contextWindow;
5637
6186
  freshConv.updatedAt = Date.now();