@poncho-ai/cli 0.30.8 → 0.32.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 +6 -6
- package/CHANGELOG.md +28 -0
- package/dist/chunk-73C227HM.js +13462 -0
- package/dist/chunk-DK5P34JS.js +13643 -0
- package/dist/chunk-FHWZIOBN.js +13485 -0
- package/dist/chunk-UUMBWQCK.js +13458 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/run-interactive-ink-6KMR5BI2.js +2115 -0
- package/dist/run-interactive-ink-IF3TLLQB.js +2115 -0
- package/dist/run-interactive-ink-OKE5AV3N.js +2115 -0
- package/dist/run-interactive-ink-TQQ4KWVI.js +2115 -0
- package/package.json +4 -4
- package/src/auth-codex.ts +123 -0
- package/src/index.ts +656 -101
- package/src/init-onboarding.ts +38 -15
- package/src/web-ui-client.ts +106 -71
- package/test/auth-codex.test.ts +34 -0
- package/test/init-onboarding.contract.test.ts +12 -0
package/src/index.ts
CHANGED
|
@@ -72,6 +72,12 @@ import {
|
|
|
72
72
|
consumeFirstRunIntro,
|
|
73
73
|
initializeOnboardingMarker,
|
|
74
74
|
} from "./init-feature-context.js";
|
|
75
|
+
import {
|
|
76
|
+
exportOpenAICodex,
|
|
77
|
+
loginOpenAICodex,
|
|
78
|
+
logoutOpenAICodex,
|
|
79
|
+
statusOpenAICodex,
|
|
80
|
+
} from "./auth-codex.js";
|
|
75
81
|
|
|
76
82
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
77
83
|
const require = createRequire(import.meta.url);
|
|
@@ -280,7 +286,7 @@ const parseParams = (values: string[]): Record<string, string> => {
|
|
|
280
286
|
const AGENT_TEMPLATE = (
|
|
281
287
|
name: string,
|
|
282
288
|
id: string,
|
|
283
|
-
options: { modelProvider: "anthropic" | "openai"; modelName: string },
|
|
289
|
+
options: { modelProvider: "anthropic" | "openai" | "openai-codex"; modelName: string },
|
|
284
290
|
): string => `---
|
|
285
291
|
name: ${name}
|
|
286
292
|
id: ${id}
|
|
@@ -371,18 +377,25 @@ An AI agent built with [Poncho](https://github.com/cesr/poncho-ai).
|
|
|
371
377
|
|
|
372
378
|
- Node.js 20+
|
|
373
379
|
- npm (or pnpm/yarn)
|
|
374
|
-
- Anthropic
|
|
380
|
+
- Anthropic API key, OpenAI API key, or OpenAI Codex OAuth refresh token
|
|
375
381
|
|
|
376
382
|
## Quick Start
|
|
377
383
|
|
|
378
384
|
\`\`\`bash
|
|
379
385
|
npm install
|
|
380
|
-
# If you didn't enter
|
|
386
|
+
# If you didn't enter credentials during init:
|
|
381
387
|
cp .env.example .env
|
|
382
|
-
# Then edit .env and add
|
|
388
|
+
# Then edit .env and add provider credentials
|
|
383
389
|
poncho dev
|
|
384
390
|
\`\`\`
|
|
385
391
|
|
|
392
|
+
For OpenAI Codex OAuth bootstrap:
|
|
393
|
+
|
|
394
|
+
\`\`\`bash
|
|
395
|
+
poncho auth login --provider openai-codex --device
|
|
396
|
+
poncho auth export --provider openai-codex --format env
|
|
397
|
+
\`\`\`
|
|
398
|
+
|
|
386
399
|
Open \`http://localhost:3000\` for the web UI, or \`http://localhost:3000/api/docs\` for interactive API documentation.
|
|
387
400
|
|
|
388
401
|
The web UI supports file attachments (drag-and-drop, paste, or attach button), conversation management (sidebar), a context window usage ring, and tool approval prompts. It can be installed as a PWA.
|
|
@@ -417,6 +430,11 @@ poncho test
|
|
|
417
430
|
# List available tools
|
|
418
431
|
poncho tools
|
|
419
432
|
|
|
433
|
+
# OpenAI Codex auth (OAuth subscription)
|
|
434
|
+
poncho auth login --provider openai-codex --device
|
|
435
|
+
poncho auth status --provider openai-codex
|
|
436
|
+
poncho auth export --provider openai-codex --format env
|
|
437
|
+
|
|
420
438
|
# Remove deprecated guidance from AGENT.md after upgrading
|
|
421
439
|
poncho update-agent
|
|
422
440
|
\`\`\`
|
|
@@ -719,6 +737,11 @@ Set environment variables on your deployment platform:
|
|
|
719
737
|
|
|
720
738
|
\`\`\`bash
|
|
721
739
|
ANTHROPIC_API_KEY=sk-ant-... # Required
|
|
740
|
+
# OR for OpenAI API key provider:
|
|
741
|
+
# OPENAI_API_KEY=sk-...
|
|
742
|
+
# OR for OpenAI Codex OAuth provider:
|
|
743
|
+
# OPENAI_CODEX_REFRESH_TOKEN=rt_...
|
|
744
|
+
# OPENAI_CODEX_ACCOUNT_ID=... # Optional
|
|
722
745
|
PONCHO_AUTH_TOKEN=your-secret # Optional: protect your endpoint
|
|
723
746
|
PONCHO_MAX_DURATION=55 # Optional: serverless timeout in seconds (enables auto-continuation)
|
|
724
747
|
PONCHO_INTERNAL_SECRET=... # Recommended on serverless: shared secret for internal callback auth
|
|
@@ -1696,7 +1719,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1696
1719
|
parentConversationId: string,
|
|
1697
1720
|
task: string,
|
|
1698
1721
|
ownerId: string,
|
|
1699
|
-
|
|
1722
|
+
_isContinuation = false,
|
|
1700
1723
|
): Promise<void> => {
|
|
1701
1724
|
const childHarness = new AgentHarness({
|
|
1702
1725
|
workingDir,
|
|
@@ -1732,14 +1755,12 @@ export const createRequestHandler = async (options?: {
|
|
|
1732
1755
|
conversation.lastActivityAt = Date.now();
|
|
1733
1756
|
await conversationStore.update(conversation);
|
|
1734
1757
|
|
|
1735
|
-
const harnessMessages =
|
|
1736
|
-
? [...conversation.
|
|
1737
|
-
: conversation.
|
|
1738
|
-
? [...conversation._harnessMessages]
|
|
1739
|
-
: [...conversation.messages];
|
|
1758
|
+
const harnessMessages = conversation._harnessMessages?.length
|
|
1759
|
+
? [...conversation._harnessMessages]
|
|
1760
|
+
: [...conversation.messages];
|
|
1740
1761
|
|
|
1741
1762
|
for await (const event of childHarness.runWithTelemetry({
|
|
1742
|
-
task
|
|
1763
|
+
task,
|
|
1743
1764
|
conversationId: childConversationId,
|
|
1744
1765
|
parameters: {
|
|
1745
1766
|
__activeConversationId: childConversationId,
|
|
@@ -1939,19 +1960,22 @@ export const createRequestHandler = async (options?: {
|
|
|
1939
1960
|
|
|
1940
1961
|
const conv = await conversationStore.get(childConversationId);
|
|
1941
1962
|
if (conv) {
|
|
1963
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
1964
|
+
// Always persist intermediate messages so progress is visible
|
|
1965
|
+
if (hasContent) {
|
|
1966
|
+
conv.messages.push({
|
|
1967
|
+
role: "assistant",
|
|
1968
|
+
content: assistantResponse,
|
|
1969
|
+
metadata: toolTimeline.length > 0 || sections.length > 0
|
|
1970
|
+
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
1971
|
+
: undefined,
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1942
1974
|
if (runResult?.continuation && runResult.continuationMessages) {
|
|
1943
1975
|
conv._continuationMessages = runResult.continuationMessages;
|
|
1944
1976
|
} else {
|
|
1945
1977
|
conv._continuationMessages = undefined;
|
|
1946
|
-
|
|
1947
|
-
conv.messages.push({
|
|
1948
|
-
role: "assistant",
|
|
1949
|
-
content: assistantResponse,
|
|
1950
|
-
metadata: toolTimeline.length > 0 || sections.length > 0
|
|
1951
|
-
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
1952
|
-
: undefined,
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1978
|
+
conv._continuationCount = undefined;
|
|
1955
1979
|
}
|
|
1956
1980
|
if (runResult?.continuationMessages) {
|
|
1957
1981
|
conv._harnessMessages = runResult.continuationMessages;
|
|
@@ -1966,16 +1990,11 @@ export const createRequestHandler = async (options?: {
|
|
|
1966
1990
|
activeConversationRuns.delete(childConversationId);
|
|
1967
1991
|
try { await childHarness.shutdown(); } catch {}
|
|
1968
1992
|
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
} else {
|
|
1975
|
-
runSubagent(childConversationId, parentConversationId, task, ownerId, true).catch(err =>
|
|
1976
|
-
console.error(`[poncho][subagent] Continuation failed:`, err instanceof Error ? err.message : err),
|
|
1977
|
-
);
|
|
1978
|
-
}
|
|
1993
|
+
const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(childConversationId)}`).catch(err =>
|
|
1994
|
+
console.error(`[poncho][subagent] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
1995
|
+
);
|
|
1996
|
+
doWaitUntil(work);
|
|
1997
|
+
if (!waitUntilHook) await work;
|
|
1979
1998
|
return;
|
|
1980
1999
|
}
|
|
1981
2000
|
|
|
@@ -3187,6 +3206,413 @@ export const createRequestHandler = async (options?: {
|
|
|
3187
3206
|
return typeof headerValue === "string" && headerValue === internalSecret;
|
|
3188
3207
|
};
|
|
3189
3208
|
|
|
3209
|
+
// ── Unified continuation ──────────────────────────────────────────────
|
|
3210
|
+
const MAX_CONTINUATION_COUNT = 20;
|
|
3211
|
+
|
|
3212
|
+
async function* runContinuation(
|
|
3213
|
+
conversationId: string,
|
|
3214
|
+
): AsyncGenerator<AgentEvent> {
|
|
3215
|
+
const conversation = await conversationStore.get(conversationId);
|
|
3216
|
+
if (!conversation) return;
|
|
3217
|
+
if (!conversation._continuationMessages?.length) return;
|
|
3218
|
+
if (conversation.runStatus === "running") return;
|
|
3219
|
+
|
|
3220
|
+
const count = (conversation._continuationCount ?? 0) + 1;
|
|
3221
|
+
if (count > MAX_CONTINUATION_COUNT) {
|
|
3222
|
+
console.warn(`[poncho][continuation] Max continuation count (${MAX_CONTINUATION_COUNT}) reached for ${conversationId}`);
|
|
3223
|
+
conversation._continuationMessages = undefined;
|
|
3224
|
+
conversation._continuationCount = undefined;
|
|
3225
|
+
await conversationStore.update(conversation);
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
const continuationMessages = [...conversation._continuationMessages];
|
|
3230
|
+
conversation._continuationMessages = undefined;
|
|
3231
|
+
conversation._continuationCount = count;
|
|
3232
|
+
conversation.runStatus = "running";
|
|
3233
|
+
await conversationStore.update(conversation);
|
|
3234
|
+
|
|
3235
|
+
const abortController = new AbortController();
|
|
3236
|
+
activeConversationRuns.set(conversationId, {
|
|
3237
|
+
ownerId: conversation.ownerId,
|
|
3238
|
+
abortController,
|
|
3239
|
+
runId: null,
|
|
3240
|
+
});
|
|
3241
|
+
|
|
3242
|
+
const prevStream = conversationEventStreams.get(conversationId);
|
|
3243
|
+
if (prevStream) {
|
|
3244
|
+
prevStream.finished = false;
|
|
3245
|
+
prevStream.buffer = [];
|
|
3246
|
+
} else {
|
|
3247
|
+
conversationEventStreams.set(conversationId, {
|
|
3248
|
+
buffer: [],
|
|
3249
|
+
subscribers: new Set(),
|
|
3250
|
+
finished: false,
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
try {
|
|
3255
|
+
if (conversation.parentConversationId) {
|
|
3256
|
+
yield* runSubagentContinuation(conversationId, conversation, continuationMessages);
|
|
3257
|
+
} else {
|
|
3258
|
+
yield* runChatContinuation(conversationId, conversation, continuationMessages);
|
|
3259
|
+
}
|
|
3260
|
+
} finally {
|
|
3261
|
+
activeConversationRuns.delete(conversationId);
|
|
3262
|
+
finishConversationStream(conversationId);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
async function* runChatContinuation(
|
|
3267
|
+
conversationId: string,
|
|
3268
|
+
conversation: Conversation,
|
|
3269
|
+
continuationMessages: Message[],
|
|
3270
|
+
): AsyncGenerator<AgentEvent> {
|
|
3271
|
+
let assistantResponse = "";
|
|
3272
|
+
let latestRunId = conversation.runtimeRunId ?? "";
|
|
3273
|
+
const toolTimeline: string[] = [];
|
|
3274
|
+
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
3275
|
+
let currentTools: string[] = [];
|
|
3276
|
+
let currentText = "";
|
|
3277
|
+
let runContextTokens = conversation.contextTokens ?? 0;
|
|
3278
|
+
let runContextWindow = conversation.contextWindow ?? 0;
|
|
3279
|
+
let nextContinuationMessages: Message[] | undefined;
|
|
3280
|
+
let nextHarnessMessages: Message[] | undefined;
|
|
3281
|
+
|
|
3282
|
+
for await (const event of harness.runWithTelemetry({
|
|
3283
|
+
conversationId,
|
|
3284
|
+
parameters: {
|
|
3285
|
+
__activeConversationId: conversationId,
|
|
3286
|
+
__ownerId: conversation.ownerId,
|
|
3287
|
+
},
|
|
3288
|
+
messages: continuationMessages,
|
|
3289
|
+
abortSignal: activeConversationRuns.get(conversationId)?.abortController.signal,
|
|
3290
|
+
})) {
|
|
3291
|
+
if (event.type === "run:started") {
|
|
3292
|
+
latestRunId = event.runId;
|
|
3293
|
+
runOwners.set(event.runId, conversation.ownerId);
|
|
3294
|
+
runConversations.set(event.runId, conversationId);
|
|
3295
|
+
const active = activeConversationRuns.get(conversationId);
|
|
3296
|
+
if (active) active.runId = event.runId;
|
|
3297
|
+
}
|
|
3298
|
+
if (event.type === "model:chunk") {
|
|
3299
|
+
if (currentTools.length > 0) {
|
|
3300
|
+
sections.push({ type: "tools", content: currentTools });
|
|
3301
|
+
currentTools = [];
|
|
3302
|
+
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
3303
|
+
assistantResponse += " ";
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
assistantResponse += event.content;
|
|
3307
|
+
currentText += event.content;
|
|
3308
|
+
}
|
|
3309
|
+
if (event.type === "tool:started") {
|
|
3310
|
+
if (currentText.length > 0) {
|
|
3311
|
+
sections.push({ type: "text", content: currentText });
|
|
3312
|
+
currentText = "";
|
|
3313
|
+
}
|
|
3314
|
+
const toolText = `- start \`${event.tool}\``;
|
|
3315
|
+
toolTimeline.push(toolText);
|
|
3316
|
+
currentTools.push(toolText);
|
|
3317
|
+
}
|
|
3318
|
+
if (event.type === "tool:completed") {
|
|
3319
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
3320
|
+
toolTimeline.push(toolText);
|
|
3321
|
+
currentTools.push(toolText);
|
|
3322
|
+
}
|
|
3323
|
+
if (event.type === "tool:error") {
|
|
3324
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
3325
|
+
toolTimeline.push(toolText);
|
|
3326
|
+
currentTools.push(toolText);
|
|
3327
|
+
}
|
|
3328
|
+
if (event.type === "run:completed") {
|
|
3329
|
+
runContextTokens = event.result.contextTokens ?? runContextTokens;
|
|
3330
|
+
runContextWindow = event.result.contextWindow ?? runContextWindow;
|
|
3331
|
+
if (event.result.continuation && event.result.continuationMessages) {
|
|
3332
|
+
nextContinuationMessages = event.result.continuationMessages;
|
|
3333
|
+
}
|
|
3334
|
+
if (event.result.continuationMessages) {
|
|
3335
|
+
nextHarnessMessages = event.result.continuationMessages;
|
|
3336
|
+
}
|
|
3337
|
+
if (!assistantResponse && event.result.response) {
|
|
3338
|
+
assistantResponse = event.result.response;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (event.type === "run:error") {
|
|
3342
|
+
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
3343
|
+
}
|
|
3344
|
+
await telemetry.emit(event);
|
|
3345
|
+
broadcastEvent(conversationId, event);
|
|
3346
|
+
yield event;
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
|
|
3350
|
+
if (currentText.length > 0) sections.push({ type: "text", content: currentText });
|
|
3351
|
+
|
|
3352
|
+
const freshConv = await conversationStore.get(conversationId);
|
|
3353
|
+
if (!freshConv) return;
|
|
3354
|
+
|
|
3355
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
3356
|
+
const assistantMetadata =
|
|
3357
|
+
toolTimeline.length > 0 || sections.length > 0
|
|
3358
|
+
? ({
|
|
3359
|
+
toolActivity: [...toolTimeline],
|
|
3360
|
+
sections: sections.length > 0 ? sections : undefined,
|
|
3361
|
+
} as Message["metadata"])
|
|
3362
|
+
: undefined;
|
|
3363
|
+
|
|
3364
|
+
if (nextContinuationMessages) {
|
|
3365
|
+
if (hasContent) {
|
|
3366
|
+
freshConv.messages = [
|
|
3367
|
+
...freshConv.messages,
|
|
3368
|
+
{ role: "assistant", content: assistantResponse, metadata: assistantMetadata },
|
|
3369
|
+
];
|
|
3370
|
+
}
|
|
3371
|
+
freshConv._continuationMessages = nextContinuationMessages;
|
|
3372
|
+
freshConv._continuationCount = conversation._continuationCount;
|
|
3373
|
+
} else {
|
|
3374
|
+
if (hasContent) {
|
|
3375
|
+
freshConv.messages = [
|
|
3376
|
+
...freshConv.messages,
|
|
3377
|
+
{ role: "assistant", content: assistantResponse, metadata: assistantMetadata },
|
|
3378
|
+
];
|
|
3379
|
+
}
|
|
3380
|
+
freshConv._continuationMessages = undefined;
|
|
3381
|
+
freshConv._continuationCount = undefined;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
if (nextHarnessMessages) freshConv._harnessMessages = nextHarnessMessages;
|
|
3385
|
+
freshConv.runtimeRunId = latestRunId || freshConv.runtimeRunId;
|
|
3386
|
+
freshConv.pendingApprovals = [];
|
|
3387
|
+
if (runContextTokens > 0) freshConv.contextTokens = runContextTokens;
|
|
3388
|
+
if (runContextWindow > 0) freshConv.contextWindow = runContextWindow;
|
|
3389
|
+
freshConv.runStatus = "idle";
|
|
3390
|
+
freshConv.updatedAt = Date.now();
|
|
3391
|
+
await conversationStore.update(freshConv);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
async function* runSubagentContinuation(
|
|
3395
|
+
conversationId: string,
|
|
3396
|
+
conversation: Conversation,
|
|
3397
|
+
continuationMessages: Message[],
|
|
3398
|
+
): AsyncGenerator<AgentEvent> {
|
|
3399
|
+
const parentConversationId = conversation.parentConversationId!;
|
|
3400
|
+
const task = conversation.subagentMeta?.task ?? "";
|
|
3401
|
+
const ownerId = conversation.ownerId;
|
|
3402
|
+
|
|
3403
|
+
const childHarness = new AgentHarness({
|
|
3404
|
+
workingDir,
|
|
3405
|
+
environment: resolveHarnessEnvironment(),
|
|
3406
|
+
uploadStore,
|
|
3407
|
+
});
|
|
3408
|
+
await childHarness.initialize();
|
|
3409
|
+
childHarness.unregisterTools(["memory_main_write", "memory_main_edit"]);
|
|
3410
|
+
|
|
3411
|
+
const childAbortController = activeConversationRuns.get(conversationId)?.abortController ?? new AbortController();
|
|
3412
|
+
activeSubagentRuns.set(conversationId, { abortController: childAbortController, harness: childHarness, parentConversationId });
|
|
3413
|
+
|
|
3414
|
+
let assistantResponse = "";
|
|
3415
|
+
let latestRunId = "";
|
|
3416
|
+
let runResult: { status: string; response?: string; steps: number; duration: number; continuation?: boolean; continuationMessages?: Message[] } | undefined;
|
|
3417
|
+
const toolTimeline: string[] = [];
|
|
3418
|
+
const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
|
|
3419
|
+
let currentTools: string[] = [];
|
|
3420
|
+
let currentText = "";
|
|
3421
|
+
|
|
3422
|
+
try {
|
|
3423
|
+
for await (const event of childHarness.runWithTelemetry({
|
|
3424
|
+
conversationId,
|
|
3425
|
+
parameters: {
|
|
3426
|
+
__activeConversationId: conversationId,
|
|
3427
|
+
__ownerId: ownerId,
|
|
3428
|
+
},
|
|
3429
|
+
messages: continuationMessages,
|
|
3430
|
+
abortSignal: childAbortController.signal,
|
|
3431
|
+
})) {
|
|
3432
|
+
if (event.type === "run:started") {
|
|
3433
|
+
latestRunId = event.runId;
|
|
3434
|
+
const active = activeConversationRuns.get(conversationId);
|
|
3435
|
+
if (active) active.runId = event.runId;
|
|
3436
|
+
}
|
|
3437
|
+
if (event.type === "model:chunk") {
|
|
3438
|
+
if (currentTools.length > 0) {
|
|
3439
|
+
sections.push({ type: "tools", content: currentTools });
|
|
3440
|
+
currentTools = [];
|
|
3441
|
+
if (assistantResponse.length > 0 && !/\s$/.test(assistantResponse)) {
|
|
3442
|
+
assistantResponse += " ";
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
assistantResponse += event.content;
|
|
3446
|
+
currentText += event.content;
|
|
3447
|
+
}
|
|
3448
|
+
if (event.type === "tool:started") {
|
|
3449
|
+
if (currentText.length > 0) {
|
|
3450
|
+
sections.push({ type: "text", content: currentText });
|
|
3451
|
+
currentText = "";
|
|
3452
|
+
}
|
|
3453
|
+
const toolText = `- start \`${event.tool}\``;
|
|
3454
|
+
toolTimeline.push(toolText);
|
|
3455
|
+
currentTools.push(toolText);
|
|
3456
|
+
}
|
|
3457
|
+
if (event.type === "tool:completed") {
|
|
3458
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
3459
|
+
toolTimeline.push(toolText);
|
|
3460
|
+
currentTools.push(toolText);
|
|
3461
|
+
}
|
|
3462
|
+
if (event.type === "tool:error") {
|
|
3463
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
3464
|
+
toolTimeline.push(toolText);
|
|
3465
|
+
currentTools.push(toolText);
|
|
3466
|
+
}
|
|
3467
|
+
if (event.type === "run:completed") {
|
|
3468
|
+
runResult = {
|
|
3469
|
+
status: event.result.status,
|
|
3470
|
+
response: event.result.response,
|
|
3471
|
+
steps: event.result.steps,
|
|
3472
|
+
duration: event.result.duration,
|
|
3473
|
+
continuation: event.result.continuation,
|
|
3474
|
+
continuationMessages: event.result.continuationMessages,
|
|
3475
|
+
};
|
|
3476
|
+
if (!assistantResponse && event.result.response) {
|
|
3477
|
+
assistantResponse = event.result.response;
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
if (event.type === "run:error") {
|
|
3481
|
+
assistantResponse = assistantResponse || `[Error: ${event.error.message}]`;
|
|
3482
|
+
}
|
|
3483
|
+
broadcastEvent(conversationId, event);
|
|
3484
|
+
yield event;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
if (currentTools.length > 0) sections.push({ type: "tools", content: currentTools });
|
|
3488
|
+
if (currentText.length > 0) sections.push({ type: "text", content: currentText });
|
|
3489
|
+
|
|
3490
|
+
const conv = await conversationStore.get(conversationId);
|
|
3491
|
+
if (conv) {
|
|
3492
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
3493
|
+
if (runResult?.continuation && runResult.continuationMessages) {
|
|
3494
|
+
if (hasContent) {
|
|
3495
|
+
conv.messages.push({
|
|
3496
|
+
role: "assistant",
|
|
3497
|
+
content: assistantResponse,
|
|
3498
|
+
metadata: toolTimeline.length > 0 || sections.length > 0
|
|
3499
|
+
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
3500
|
+
: undefined,
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3503
|
+
conv._continuationMessages = runResult.continuationMessages;
|
|
3504
|
+
conv._continuationCount = conversation._continuationCount;
|
|
3505
|
+
} else {
|
|
3506
|
+
conv._continuationMessages = undefined;
|
|
3507
|
+
conv._continuationCount = undefined;
|
|
3508
|
+
if (hasContent) {
|
|
3509
|
+
conv.messages.push({
|
|
3510
|
+
role: "assistant",
|
|
3511
|
+
content: assistantResponse,
|
|
3512
|
+
metadata: toolTimeline.length > 0 || sections.length > 0
|
|
3513
|
+
? { toolActivity: toolTimeline, sections: sections.length > 0 ? sections : undefined } as Message["metadata"]
|
|
3514
|
+
: undefined,
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
if (runResult?.continuationMessages) {
|
|
3519
|
+
conv._harnessMessages = runResult.continuationMessages;
|
|
3520
|
+
}
|
|
3521
|
+
conv.lastActivityAt = Date.now();
|
|
3522
|
+
conv.runStatus = "idle";
|
|
3523
|
+
conv.updatedAt = Date.now();
|
|
3524
|
+
|
|
3525
|
+
if (runResult?.continuation) {
|
|
3526
|
+
await conversationStore.update(conv);
|
|
3527
|
+
activeSubagentRuns.delete(conversationId);
|
|
3528
|
+
try { await childHarness.shutdown(); } catch {}
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
conv.subagentMeta = { ...conv.subagentMeta!, status: "completed" };
|
|
3533
|
+
await conversationStore.update(conv);
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
activeSubagentRuns.delete(conversationId);
|
|
3537
|
+
broadcastEvent(parentConversationId, {
|
|
3538
|
+
type: "subagent:completed",
|
|
3539
|
+
subagentId: conversationId,
|
|
3540
|
+
conversationId,
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
let subagentResponse = runResult?.response ?? assistantResponse;
|
|
3544
|
+
if (!subagentResponse) {
|
|
3545
|
+
const freshSubConv = await conversationStore.get(conversationId);
|
|
3546
|
+
if (freshSubConv) {
|
|
3547
|
+
const lastAssistant = [...freshSubConv.messages].reverse().find(m => m.role === "assistant");
|
|
3548
|
+
if (lastAssistant) {
|
|
3549
|
+
subagentResponse = typeof lastAssistant.content === "string" ? lastAssistant.content : "";
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
const parentConv = await conversationStore.get(parentConversationId);
|
|
3555
|
+
if (parentConv) {
|
|
3556
|
+
const result: PendingSubagentResult = {
|
|
3557
|
+
subagentId: conversationId,
|
|
3558
|
+
task,
|
|
3559
|
+
status: "completed",
|
|
3560
|
+
result: { status: "completed", response: subagentResponse, steps: runResult?.steps ?? 0, tokens: { input: 0, output: 0, cached: 0 }, duration: runResult?.duration ?? 0 },
|
|
3561
|
+
timestamp: Date.now(),
|
|
3562
|
+
};
|
|
3563
|
+
await conversationStore.appendSubagentResult(parentConversationId, result);
|
|
3564
|
+
|
|
3565
|
+
if (isServerless) {
|
|
3566
|
+
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(parentConversationId)}/subagent-callback`).catch(err =>
|
|
3567
|
+
console.error(`[poncho][subagent] Callback self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
3568
|
+
);
|
|
3569
|
+
} else {
|
|
3570
|
+
processSubagentCallback(parentConversationId).catch(err =>
|
|
3571
|
+
console.error(`[poncho][subagent] Callback failed:`, err instanceof Error ? err.message : err),
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
try { await childHarness.shutdown(); } catch {}
|
|
3577
|
+
} catch (err) {
|
|
3578
|
+
activeSubagentRuns.delete(conversationId);
|
|
3579
|
+
try { await childHarness.shutdown(); } catch {}
|
|
3580
|
+
|
|
3581
|
+
const conv = await conversationStore.get(conversationId);
|
|
3582
|
+
if (conv) {
|
|
3583
|
+
conv.subagentMeta = { ...conv.subagentMeta!, status: "error", error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) } };
|
|
3584
|
+
conv.runStatus = "idle";
|
|
3585
|
+
conv._continuationMessages = undefined;
|
|
3586
|
+
conv._continuationCount = undefined;
|
|
3587
|
+
conv.updatedAt = Date.now();
|
|
3588
|
+
await conversationStore.update(conv);
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
broadcastEvent(conversation.parentConversationId!, {
|
|
3592
|
+
type: "subagent:completed",
|
|
3593
|
+
subagentId: conversationId,
|
|
3594
|
+
conversationId,
|
|
3595
|
+
});
|
|
3596
|
+
|
|
3597
|
+
const parentConv = await conversationStore.get(conversation.parentConversationId!);
|
|
3598
|
+
if (parentConv) {
|
|
3599
|
+
const result: PendingSubagentResult = {
|
|
3600
|
+
subagentId: conversationId,
|
|
3601
|
+
task,
|
|
3602
|
+
status: "error",
|
|
3603
|
+
error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
|
|
3604
|
+
timestamp: Date.now(),
|
|
3605
|
+
};
|
|
3606
|
+
await conversationStore.appendSubagentResult(conversation.parentConversationId!, result);
|
|
3607
|
+
if (isServerless) {
|
|
3608
|
+
selfFetchWithRetry(`/api/internal/conversations/${encodeURIComponent(conversation.parentConversationId!)}/subagent-callback`).catch(() => {});
|
|
3609
|
+
} else {
|
|
3610
|
+
processSubagentCallback(conversation.parentConversationId!).catch(() => {});
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3190
3616
|
const messagingAdapters = new Map<string, MessagingAdapter>();
|
|
3191
3617
|
const messagingBridges: AgentBridge[] = [];
|
|
3192
3618
|
if (config?.messaging && config.messaging.length > 0) {
|
|
@@ -3563,7 +3989,7 @@ export const createRequestHandler = async (options?: {
|
|
|
3563
3989
|
const subagentRunMatch = pathname.match(/^\/api\/internal\/subagent\/([^/]+)\/run$/);
|
|
3564
3990
|
if (subagentRunMatch) {
|
|
3565
3991
|
const subagentId = decodeURIComponent(subagentRunMatch[1]!);
|
|
3566
|
-
const body = (await readRequestBody(request)) as {
|
|
3992
|
+
const body = (await readRequestBody(request)) as { resume?: boolean } | undefined;
|
|
3567
3993
|
writeJson(response, 202, { ok: true });
|
|
3568
3994
|
const work = (async () => {
|
|
3569
3995
|
try {
|
|
@@ -3576,9 +4002,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3576
4002
|
return;
|
|
3577
4003
|
}
|
|
3578
4004
|
|
|
3579
|
-
const
|
|
3580
|
-
|
|
3581
|
-
await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, isContinuation);
|
|
4005
|
+
const task = (conv.messages.find(m => m.role === "user")?.content as string) ?? conv.subagentMeta?.task ?? "";
|
|
4006
|
+
await runSubagent(subagentId, conv.parentConversationId, task, conv.ownerId, false);
|
|
3582
4007
|
} catch (err) {
|
|
3583
4008
|
console.error(`[poncho][internal] subagent run error for ${subagentId}:`, err instanceof Error ? err.message : err);
|
|
3584
4009
|
}
|
|
@@ -3598,6 +4023,29 @@ export const createRequestHandler = async (options?: {
|
|
|
3598
4023
|
return;
|
|
3599
4024
|
}
|
|
3600
4025
|
|
|
4026
|
+
const continueMatch = pathname.match(/^\/api\/internal\/continue\/([^/]+)$/);
|
|
4027
|
+
if (continueMatch) {
|
|
4028
|
+
const conversationId = decodeURIComponent(continueMatch[1]!);
|
|
4029
|
+
writeJson(response, 202, { ok: true });
|
|
4030
|
+
const work = (async () => {
|
|
4031
|
+
try {
|
|
4032
|
+
for await (const _event of runContinuation(conversationId)) {
|
|
4033
|
+
// Events are already broadcast inside runContinuation
|
|
4034
|
+
}
|
|
4035
|
+
// Chain: if another continuation is needed, fire next self-fetch
|
|
4036
|
+
const conv = await conversationStore.get(conversationId);
|
|
4037
|
+
if (conv?._continuationMessages?.length) {
|
|
4038
|
+
await selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`);
|
|
4039
|
+
}
|
|
4040
|
+
} catch (err) {
|
|
4041
|
+
console.error(`[poncho][internal-continue] Error for ${conversationId}:`, err instanceof Error ? err.message : err);
|
|
4042
|
+
}
|
|
4043
|
+
})();
|
|
4044
|
+
doWaitUntil(work);
|
|
4045
|
+
if (!waitUntilHook) await work;
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
3601
4049
|
writeJson(response, 404, { error: "Not found" });
|
|
3602
4050
|
return;
|
|
3603
4051
|
}
|
|
@@ -4489,6 +4937,88 @@ export const createRequestHandler = async (options?: {
|
|
|
4489
4937
|
return;
|
|
4490
4938
|
}
|
|
4491
4939
|
|
|
4940
|
+
// ── Public continuation endpoint (SSE) ──
|
|
4941
|
+
const conversationContinueMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/continue$/);
|
|
4942
|
+
if (conversationContinueMatch && request.method === "POST") {
|
|
4943
|
+
const conversationId = decodeURIComponent(conversationContinueMatch[1] ?? "");
|
|
4944
|
+
const conversation = await conversationStore.get(conversationId);
|
|
4945
|
+
if (!conversation || conversation.ownerId !== ownerId) {
|
|
4946
|
+
writeJson(response, 404, {
|
|
4947
|
+
code: "CONVERSATION_NOT_FOUND",
|
|
4948
|
+
message: "Conversation not found",
|
|
4949
|
+
});
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
if (conversation.parentConversationId) {
|
|
4953
|
+
writeJson(response, 403, {
|
|
4954
|
+
code: "SUBAGENT_READ_ONLY",
|
|
4955
|
+
message: "Subagent conversations are read-only.",
|
|
4956
|
+
});
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
response.writeHead(200, {
|
|
4961
|
+
"Content-Type": "text/event-stream",
|
|
4962
|
+
"Cache-Control": "no-cache",
|
|
4963
|
+
Connection: "keep-alive",
|
|
4964
|
+
"X-Accel-Buffering": "no",
|
|
4965
|
+
});
|
|
4966
|
+
|
|
4967
|
+
const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
|
|
4968
|
+
if (evt.type.startsWith("subagent:")) {
|
|
4969
|
+
try { response.write(formatSseEvent(evt)); } catch {}
|
|
4970
|
+
}
|
|
4971
|
+
});
|
|
4972
|
+
|
|
4973
|
+
let eventCount = 0;
|
|
4974
|
+
try {
|
|
4975
|
+
for await (const event of runContinuation(conversationId)) {
|
|
4976
|
+
eventCount++;
|
|
4977
|
+
let sseEvent: AgentEvent = event;
|
|
4978
|
+
if (sseEvent.type === "run:completed") {
|
|
4979
|
+
const hasRunningSubagents = Array.from(activeSubagentRuns.values()).some(
|
|
4980
|
+
r => r.parentConversationId === conversationId,
|
|
4981
|
+
);
|
|
4982
|
+
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
4983
|
+
sseEvent = hasRunningSubagents ? { ...stripped, pendingSubagents: true } : stripped;
|
|
4984
|
+
}
|
|
4985
|
+
try {
|
|
4986
|
+
response.write(formatSseEvent(sseEvent));
|
|
4987
|
+
} catch {
|
|
4988
|
+
// Client disconnected — continue processing so the run completes
|
|
4989
|
+
}
|
|
4990
|
+
emitBrowserStatusIfActive(conversationId, event, response);
|
|
4991
|
+
}
|
|
4992
|
+
} catch (err) {
|
|
4993
|
+
const errorEvent: AgentEvent = {
|
|
4994
|
+
type: "run:error",
|
|
4995
|
+
runId: "",
|
|
4996
|
+
error: { code: "CONTINUATION_ERROR", message: err instanceof Error ? err.message : String(err) },
|
|
4997
|
+
};
|
|
4998
|
+
try { response.write(formatSseEvent(errorEvent)); } catch {}
|
|
4999
|
+
} finally {
|
|
5000
|
+
unsubSubagentEvents();
|
|
5001
|
+
}
|
|
5002
|
+
|
|
5003
|
+
if (eventCount === 0) {
|
|
5004
|
+
try { response.write("event: stream:end\ndata: {}\n\n"); } catch {}
|
|
5005
|
+
} else {
|
|
5006
|
+
// If the run produced events and another continuation is needed,
|
|
5007
|
+
// fire a delayed safety net in case the client disconnects before
|
|
5008
|
+
// POSTing the next /continue.
|
|
5009
|
+
const freshConv = await conversationStore.get(conversationId);
|
|
5010
|
+
if (freshConv?._continuationMessages?.length) {
|
|
5011
|
+
doWaitUntil(
|
|
5012
|
+
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5013
|
+
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
5014
|
+
),
|
|
5015
|
+
);
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
response.end();
|
|
5019
|
+
return;
|
|
5020
|
+
}
|
|
5021
|
+
|
|
4492
5022
|
const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
|
|
4493
5023
|
if (conversationMessageMatch && request.method === "POST") {
|
|
4494
5024
|
const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
|
|
@@ -4510,7 +5040,6 @@ export const createRequestHandler = async (options?: {
|
|
|
4510
5040
|
let messageText = "";
|
|
4511
5041
|
let bodyParameters: Record<string, unknown> | undefined;
|
|
4512
5042
|
let files: FileInput[] = [];
|
|
4513
|
-
let isContinuation = false;
|
|
4514
5043
|
|
|
4515
5044
|
const contentType = request.headers["content-type"] ?? "";
|
|
4516
5045
|
if (contentType.includes("multipart/form-data")) {
|
|
@@ -4521,15 +5050,10 @@ export const createRequestHandler = async (options?: {
|
|
|
4521
5050
|
} else {
|
|
4522
5051
|
const body = (await readRequestBody(request)) as {
|
|
4523
5052
|
message?: string;
|
|
4524
|
-
continuation?: boolean;
|
|
4525
5053
|
parameters?: Record<string, unknown>;
|
|
4526
5054
|
files?: Array<{ data?: string; mediaType?: string; filename?: string }>;
|
|
4527
5055
|
};
|
|
4528
|
-
|
|
4529
|
-
isContinuation = true;
|
|
4530
|
-
} else {
|
|
4531
|
-
messageText = body.message?.trim() ?? "";
|
|
4532
|
-
}
|
|
5056
|
+
messageText = body.message?.trim() ?? "";
|
|
4533
5057
|
bodyParameters = body.parameters;
|
|
4534
5058
|
if (Array.isArray(body.files)) {
|
|
4535
5059
|
files = body.files
|
|
@@ -4538,7 +5062,7 @@ export const createRequestHandler = async (options?: {
|
|
|
4538
5062
|
);
|
|
4539
5063
|
}
|
|
4540
5064
|
}
|
|
4541
|
-
if (!
|
|
5065
|
+
if (!messageText) {
|
|
4542
5066
|
writeJson(response, 400, {
|
|
4543
5067
|
code: "VALIDATION_ERROR",
|
|
4544
5068
|
message: "message is required",
|
|
@@ -4564,7 +5088,6 @@ export const createRequestHandler = async (options?: {
|
|
|
4564
5088
|
runId: null,
|
|
4565
5089
|
});
|
|
4566
5090
|
if (
|
|
4567
|
-
!isContinuation &&
|
|
4568
5091
|
conversation.messages.length === 0 &&
|
|
4569
5092
|
(conversation.title === "New conversation" || conversation.title.trim().length === 0)
|
|
4570
5093
|
) {
|
|
@@ -4576,11 +5099,9 @@ export const createRequestHandler = async (options?: {
|
|
|
4576
5099
|
Connection: "keep-alive",
|
|
4577
5100
|
"X-Accel-Buffering": "no",
|
|
4578
5101
|
});
|
|
4579
|
-
const harnessMessages =
|
|
4580
|
-
? [...conversation.
|
|
4581
|
-
: conversation.
|
|
4582
|
-
? [...conversation._harnessMessages]
|
|
4583
|
-
: [...conversation.messages];
|
|
5102
|
+
const harnessMessages = conversation._harnessMessages?.length
|
|
5103
|
+
? [...conversation._harnessMessages]
|
|
5104
|
+
: [...conversation.messages];
|
|
4584
5105
|
const historyMessages = [...conversation.messages];
|
|
4585
5106
|
const preRunMessages = [...conversation.messages];
|
|
4586
5107
|
let latestRunId = conversation.runtimeRunId ?? "";
|
|
@@ -4596,8 +5117,8 @@ export const createRequestHandler = async (options?: {
|
|
|
4596
5117
|
let runContextWindow = conversation.contextWindow ?? 0;
|
|
4597
5118
|
let runContinuationMessages: Message[] | undefined;
|
|
4598
5119
|
let runHarnessMessages: Message[] | undefined;
|
|
4599
|
-
let userContent: Message["content"] | undefined =
|
|
4600
|
-
if (
|
|
5120
|
+
let userContent: Message["content"] | undefined = messageText;
|
|
5121
|
+
if (files.length > 0) {
|
|
4601
5122
|
try {
|
|
4602
5123
|
const uploadedParts = await Promise.all(
|
|
4603
5124
|
files.map(async (f) => {
|
|
@@ -4640,9 +5161,10 @@ export const createRequestHandler = async (options?: {
|
|
|
4640
5161
|
});
|
|
4641
5162
|
|
|
4642
5163
|
try {
|
|
4643
|
-
|
|
5164
|
+
{
|
|
4644
5165
|
conversation.messages = [...historyMessages, { role: "user", content: userContent! }];
|
|
4645
5166
|
conversation.subagentCallbackCount = 0;
|
|
5167
|
+
conversation._continuationCount = undefined;
|
|
4646
5168
|
conversation.updatedAt = Date.now();
|
|
4647
5169
|
conversationStore.update(conversation).catch((err) => {
|
|
4648
5170
|
console.error("[poncho] Failed to persist user turn:", err);
|
|
@@ -4720,7 +5242,7 @@ export const createRequestHandler = async (options?: {
|
|
|
4720
5242
|
};
|
|
4721
5243
|
|
|
4722
5244
|
for await (const event of harness.runWithTelemetry({
|
|
4723
|
-
task:
|
|
5245
|
+
task: messageText,
|
|
4724
5246
|
conversationId,
|
|
4725
5247
|
parameters: {
|
|
4726
5248
|
...(bodyParameters ?? {}),
|
|
@@ -4729,7 +5251,7 @@ export const createRequestHandler = async (options?: {
|
|
|
4729
5251
|
__ownerId: ownerId,
|
|
4730
5252
|
},
|
|
4731
5253
|
messages: harnessMessages,
|
|
4732
|
-
files:
|
|
5254
|
+
files: files.length > 0 ? files : undefined,
|
|
4733
5255
|
abortSignal: abortController.signal,
|
|
4734
5256
|
})) {
|
|
4735
5257
|
if (event.type === "run:started") {
|
|
@@ -4845,6 +5367,21 @@ export const createRequestHandler = async (options?: {
|
|
|
4845
5367
|
}
|
|
4846
5368
|
if (event.result.continuation && event.result.continuationMessages) {
|
|
4847
5369
|
runContinuationMessages = event.result.continuationMessages;
|
|
5370
|
+
|
|
5371
|
+
// Persist intermediate messages so clients connecting later
|
|
5372
|
+
// see progress, plus _continuationMessages for the next step.
|
|
5373
|
+
const intSections = [...sections];
|
|
5374
|
+
if (currentTools.length > 0) intSections.push({ type: "tools", content: [...currentTools] });
|
|
5375
|
+
if (currentText.length > 0) intSections.push({ type: "text", content: currentText });
|
|
5376
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0 || intSections.length > 0;
|
|
5377
|
+
const intMetadata = toolTimeline.length > 0 || intSections.length > 0
|
|
5378
|
+
? ({ toolActivity: [...toolTimeline], sections: intSections.length > 0 ? intSections : undefined } as Message["metadata"])
|
|
5379
|
+
: undefined;
|
|
5380
|
+
conversation.messages = [
|
|
5381
|
+
...historyMessages,
|
|
5382
|
+
...(userContent != null ? [{ role: "user" as const, content: userContent }] : []),
|
|
5383
|
+
...(hasContent ? [{ role: "assistant" as const, content: assistantResponse, metadata: intMetadata }] : []),
|
|
5384
|
+
];
|
|
4848
5385
|
conversation._continuationMessages = runContinuationMessages;
|
|
4849
5386
|
conversation._harnessMessages = runContinuationMessages;
|
|
4850
5387
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
@@ -4853,6 +5390,14 @@ export const createRequestHandler = async (options?: {
|
|
|
4853
5390
|
if (runContextWindow > 0) conversation.contextWindow = runContextWindow;
|
|
4854
5391
|
conversation.updatedAt = Date.now();
|
|
4855
5392
|
await conversationStore.update(conversation);
|
|
5393
|
+
|
|
5394
|
+
// Delayed safety net: if the client doesn't POST to /continue
|
|
5395
|
+
// within 3 seconds (e.g. browser closed), the server picks it up.
|
|
5396
|
+
doWaitUntil(
|
|
5397
|
+
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
5398
|
+
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
5399
|
+
),
|
|
5400
|
+
);
|
|
4856
5401
|
}
|
|
4857
5402
|
}
|
|
4858
5403
|
await telemetry.emit(event);
|
|
@@ -5032,23 +5577,11 @@ export const createRequestHandler = async (options?: {
|
|
|
5032
5577
|
}
|
|
5033
5578
|
|
|
5034
5579
|
const urlObj = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
5035
|
-
const continueConversationId = urlObj.searchParams.get("continue");
|
|
5036
|
-
const continuationCount = Number(urlObj.searchParams.get("continuation") ?? "0");
|
|
5037
|
-
const maxContinuations = 5;
|
|
5038
|
-
|
|
5039
|
-
if (continuationCount >= maxContinuations) {
|
|
5040
|
-
writeJson(response, 200, {
|
|
5041
|
-
conversationId: continueConversationId,
|
|
5042
|
-
status: "max_continuations_reached",
|
|
5043
|
-
continuations: continuationCount,
|
|
5044
|
-
});
|
|
5045
|
-
return;
|
|
5046
|
-
}
|
|
5047
5580
|
|
|
5048
5581
|
const cronOwnerId = ownerId;
|
|
5049
5582
|
const start = Date.now();
|
|
5050
5583
|
|
|
5051
|
-
if (cronJob.channel
|
|
5584
|
+
if (cronJob.channel) {
|
|
5052
5585
|
const adapter = messagingAdapters.get(cronJob.channel);
|
|
5053
5586
|
if (!adapter) {
|
|
5054
5587
|
writeJson(response, 200, {
|
|
@@ -5161,34 +5694,12 @@ export const createRequestHandler = async (options?: {
|
|
|
5161
5694
|
}
|
|
5162
5695
|
|
|
5163
5696
|
try {
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
writeJson(response, 404, {
|
|
5171
|
-
code: "CONVERSATION_NOT_FOUND",
|
|
5172
|
-
message: "Continuation conversation not found",
|
|
5173
|
-
});
|
|
5174
|
-
return;
|
|
5175
|
-
}
|
|
5176
|
-
historyMessages = conversation._continuationMessages?.length
|
|
5177
|
-
? [...conversation._continuationMessages]
|
|
5178
|
-
: conversation._harnessMessages?.length
|
|
5179
|
-
? [...conversation._harnessMessages]
|
|
5180
|
-
: [...conversation.messages];
|
|
5181
|
-
if (conversation._continuationMessages?.length) {
|
|
5182
|
-
conversation._continuationMessages = undefined;
|
|
5183
|
-
await conversationStore.update(conversation);
|
|
5184
|
-
}
|
|
5185
|
-
} else {
|
|
5186
|
-
const timestamp = new Date().toISOString();
|
|
5187
|
-
conversation = await conversationStore.create(
|
|
5188
|
-
cronOwnerId,
|
|
5189
|
-
`[cron] ${jobName} ${timestamp}`,
|
|
5190
|
-
);
|
|
5191
|
-
}
|
|
5697
|
+
const timestamp = new Date().toISOString();
|
|
5698
|
+
const conversation = await conversationStore.create(
|
|
5699
|
+
cronOwnerId,
|
|
5700
|
+
`[cron] ${jobName} ${timestamp}`,
|
|
5701
|
+
);
|
|
5702
|
+
const historyMessages: Message[] = [];
|
|
5192
5703
|
|
|
5193
5704
|
const convId = conversation.conversationId;
|
|
5194
5705
|
activeConversationRuns.set(convId, {
|
|
@@ -5298,20 +5809,20 @@ export const createRequestHandler = async (options?: {
|
|
|
5298
5809
|
: undefined;
|
|
5299
5810
|
const messages: Message[] = [
|
|
5300
5811
|
...historyMessages,
|
|
5301
|
-
|
|
5302
|
-
? []
|
|
5303
|
-
: [{ role: "user" as const, content: cronJob.task }]),
|
|
5812
|
+
{ role: "user" as const, content: cronJob.task },
|
|
5304
5813
|
...(hasContent
|
|
5305
5814
|
? [{ role: "assistant" as const, content: assistantResponse, metadata: assistantMetadata }]
|
|
5306
5815
|
: []),
|
|
5307
5816
|
];
|
|
5308
5817
|
const freshConv = await conversationStore.get(convId);
|
|
5309
5818
|
if (freshConv) {
|
|
5819
|
+
// Always persist intermediate messages so clients see progress
|
|
5820
|
+
freshConv.messages = messages;
|
|
5310
5821
|
if (runContinuationMessages) {
|
|
5311
5822
|
freshConv._continuationMessages = runContinuationMessages;
|
|
5312
5823
|
} else {
|
|
5313
5824
|
freshConv._continuationMessages = undefined;
|
|
5314
|
-
freshConv.
|
|
5825
|
+
freshConv._continuationCount = undefined;
|
|
5315
5826
|
}
|
|
5316
5827
|
if (runResult.harnessMessages) {
|
|
5317
5828
|
freshConv._harnessMessages = runResult.harnessMessages;
|
|
@@ -5324,15 +5835,13 @@ export const createRequestHandler = async (options?: {
|
|
|
5324
5835
|
}
|
|
5325
5836
|
|
|
5326
5837
|
if (runResult.continuation) {
|
|
5327
|
-
const
|
|
5328
|
-
const work = selfFetchWithRetry(continuationPath).catch(err =>
|
|
5838
|
+
const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(err =>
|
|
5329
5839
|
console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
5330
5840
|
);
|
|
5331
5841
|
doWaitUntil(work);
|
|
5332
5842
|
writeJson(response, 200, {
|
|
5333
5843
|
conversationId: convId,
|
|
5334
5844
|
status: "continued",
|
|
5335
|
-
continuations: continuationCount + 1,
|
|
5336
5845
|
duration: Date.now() - start,
|
|
5337
5846
|
});
|
|
5338
5847
|
return;
|
|
@@ -6689,6 +7198,52 @@ export const buildCli = (): Command => {
|
|
|
6689
7198
|
await listTools(process.cwd());
|
|
6690
7199
|
});
|
|
6691
7200
|
|
|
7201
|
+
const authCommand = program.command("auth").description("Manage model provider authentication");
|
|
7202
|
+
authCommand
|
|
7203
|
+
.command("login")
|
|
7204
|
+
.requiredOption("--provider <provider>", "provider id (currently: openai-codex)")
|
|
7205
|
+
.option("--device", "use device auth flow", true)
|
|
7206
|
+
.action(async (options: { provider: string; device: boolean }) => {
|
|
7207
|
+
if (options.provider !== "openai-codex") {
|
|
7208
|
+
throw new Error(`Unsupported provider "${options.provider}". Try --provider openai-codex.`);
|
|
7209
|
+
}
|
|
7210
|
+
await loginOpenAICodex({ device: options.device });
|
|
7211
|
+
});
|
|
7212
|
+
|
|
7213
|
+
authCommand
|
|
7214
|
+
.command("status")
|
|
7215
|
+
.requiredOption("--provider <provider>", "provider id (currently: openai-codex)")
|
|
7216
|
+
.action(async (options: { provider: string }) => {
|
|
7217
|
+
if (options.provider !== "openai-codex") {
|
|
7218
|
+
throw new Error(`Unsupported provider "${options.provider}". Try --provider openai-codex.`);
|
|
7219
|
+
}
|
|
7220
|
+
await statusOpenAICodex();
|
|
7221
|
+
});
|
|
7222
|
+
|
|
7223
|
+
authCommand
|
|
7224
|
+
.command("logout")
|
|
7225
|
+
.requiredOption("--provider <provider>", "provider id (currently: openai-codex)")
|
|
7226
|
+
.action(async (options: { provider: string }) => {
|
|
7227
|
+
if (options.provider !== "openai-codex") {
|
|
7228
|
+
throw new Error(`Unsupported provider "${options.provider}". Try --provider openai-codex.`);
|
|
7229
|
+
}
|
|
7230
|
+
await logoutOpenAICodex();
|
|
7231
|
+
});
|
|
7232
|
+
|
|
7233
|
+
authCommand
|
|
7234
|
+
.command("export")
|
|
7235
|
+
.requiredOption("--provider <provider>", "provider id (currently: openai-codex)")
|
|
7236
|
+
.option("--format <format>", "env|json", "env")
|
|
7237
|
+
.action(async (options: { provider: string; format: string }) => {
|
|
7238
|
+
if (options.provider !== "openai-codex") {
|
|
7239
|
+
throw new Error(`Unsupported provider "${options.provider}". Try --provider openai-codex.`);
|
|
7240
|
+
}
|
|
7241
|
+
if (options.format !== "env" && options.format !== "json") {
|
|
7242
|
+
throw new Error(`Unsupported export format "${options.format}". Use env or json.`);
|
|
7243
|
+
}
|
|
7244
|
+
await exportOpenAICodex(options.format);
|
|
7245
|
+
});
|
|
7246
|
+
|
|
6692
7247
|
const skillsCommand = program.command("skills").description("Manage installed skills");
|
|
6693
7248
|
skillsCommand
|
|
6694
7249
|
.command("add")
|