@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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +22 -0
- package/dist/{chunk-NPD5GM5C.js → chunk-73C227HM.js} +697 -164
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-NV6LIQWU.js → run-interactive-ink-OKE5AV3N.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +645 -96
- package/src/web-ui-client.ts +106 -71
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
|
-
|
|
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 =
|
|
1736
|
-
? [...conversation.
|
|
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
|
|
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
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
-
:
|
|
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 {
|
|
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
|
|
3556
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
4555
|
-
? [...conversation.
|
|
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
|
|
4572
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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();
|