@poncho-ai/cli 0.40.2 → 0.40.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +52 -0
- package/dist/{chunk-KVGMTYDD.js → chunk-7YEM6KJS.js} +26 -246
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-LJTKUUV4.js → run-interactive-ink-JWYEHJZH.js} +1 -1
- package/package.json +2 -2
- package/src/index.ts +41 -278
- package/src/web-ui-styles.ts +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/cli@0.40.
|
|
2
|
+
> @poncho-ai/cli@0.40.3 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
|
|
3
3
|
> tsup src/index.ts src/cli.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[32mESM[39m [1mdist/cli.js [22m[32m528.00 B[39m
|
|
11
11
|
[32mESM[39m [1mdist/index.js [22m[32m3.10 KB[39m
|
|
12
|
-
[32mESM[39m [1mdist/run-interactive-ink-
|
|
13
|
-
[32mESM[39m [1mdist/chunk-
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/run-interactive-ink-JWYEHJZH.js [22m[32m23.38 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/chunk-7YEM6KJS.js [22m[32m665.12 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 73ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 4142ms
|
|
17
17
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m20.00 B[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m13.25 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# @poncho-ai/cli
|
|
2
2
|
|
|
3
|
+
## 0.40.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`111d24e`](https://github.com/cesr/poncho-ai/commit/111d24efaab054ef7543c396085f8f4d41e7976a) Thanks [@cesr](https://github.com/cesr)! - cli: include VFS skills in the chat input slash command menu
|
|
8
|
+
|
|
9
|
+
The `/api/slash-commands` endpoint was returning only repo-loaded skills,
|
|
10
|
+
so tenant-authored skills stored in the VFS (`/skills/<name>/SKILL.md`)
|
|
11
|
+
never appeared in the `/` autocomplete bar even though the agent could
|
|
12
|
+
already see and run them at conversation time.
|
|
13
|
+
|
|
14
|
+
The endpoint now resolves skills per-tenant via a new
|
|
15
|
+
`harness.listSkillsForTenant(tenantId)` and applies the same repo-wins
|
|
16
|
+
collision semantics used elsewhere in the harness.
|
|
17
|
+
|
|
18
|
+
- [`39793b0`](https://github.com/cesr/poncho-ai/commit/39793b0ab11ed26f140af6fc9c0cd3e1b1c83fec) Thanks [@cesr](https://github.com/cesr)! - harness: extract `runConversationTurn` helper; refactor CLI to use it
|
|
19
|
+
|
|
20
|
+
Lifts the inline turn lifecycle from the CLI's
|
|
21
|
+
`POST /api/conversations/:id/messages` handler (~280 lines of orchestration)
|
|
22
|
+
into a new public helper at `@poncho-ai/harness`.
|
|
23
|
+
|
|
24
|
+
The helper handles the full conversation lifecycle for a primary chat
|
|
25
|
+
turn: load the conversation with archive, resolve canonical history,
|
|
26
|
+
upload files via the harness's upload store, build stable user/assistant
|
|
27
|
+
ids, persist the user message immediately, drive `executeConversationTurn`,
|
|
28
|
+
periodically persist the in-flight assistant draft on `step:completed`
|
|
29
|
+
and `tool:approval:required`, persist on `tool:approval:checkpoint` and
|
|
30
|
+
`run:completed` continuation, rebuild history on `compaction:completed`,
|
|
31
|
+
apply turn metadata on success, and persist partial state on
|
|
32
|
+
cancel/error.
|
|
33
|
+
|
|
34
|
+
Caller responsibilities (auth, active-run dedup, streaming, continuation
|
|
35
|
+
HTTP self-fetch, title inference) stay outside the helper — passed in
|
|
36
|
+
via opts or handled around the call. `opts.onEvent` is invoked for every
|
|
37
|
+
`AgentEvent` for downstream forwarding (SSE, WebSocket, telemetry, etc.).
|
|
38
|
+
|
|
39
|
+
The CLI's handler now delegates to `runConversationTurn` (drops from
|
|
40
|
+
~430 to ~150 lines). Consumers like PonchOS can call the same helper
|
|
41
|
+
to ship the _exact_ same conversation lifecycle without duplicating
|
|
42
|
+
the orchestration.
|
|
43
|
+
|
|
44
|
+
Public API additions:
|
|
45
|
+
- `runConversationTurn(opts): Promise<RunConversationTurnResult>`
|
|
46
|
+
- `RunConversationTurnOpts`
|
|
47
|
+
- `RunConversationTurnResult`
|
|
48
|
+
|
|
49
|
+
No behavior changes. The helper is a verbatim extraction of the CLI's
|
|
50
|
+
prior inline implementation.
|
|
51
|
+
|
|
52
|
+
- Updated dependencies [[`111d24e`](https://github.com/cesr/poncho-ai/commit/111d24efaab054ef7543c396085f8f4d41e7976a), [`39793b0`](https://github.com/cesr/poncho-ai/commit/39793b0ab11ed26f140af6fc9c0cd3e1b1c83fec)]:
|
|
53
|
+
- @poncho-ai/harness@0.42.0
|
|
54
|
+
|
|
3
55
|
## 0.40.2
|
|
4
56
|
|
|
5
57
|
### Patch Changes
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
createConversationStore as createConversationStore2,
|
|
15
15
|
createConversationStoreFromEngine as createConversationStoreFromEngine2,
|
|
16
16
|
createUploadStore as createUploadStore2,
|
|
17
|
-
deriveUploadKey,
|
|
18
17
|
ensureAgentIdentity as ensureAgentIdentity3,
|
|
19
18
|
loadPonchoConfig as loadPonchoConfig4,
|
|
20
19
|
parseAgentMarkdown as parseAgentMarkdown2,
|
|
@@ -32,6 +31,7 @@ import {
|
|
|
32
31
|
normalizeApprovalCheckpoint,
|
|
33
32
|
buildApprovalCheckpoints,
|
|
34
33
|
applyTurnMetadata,
|
|
34
|
+
runConversationTurn,
|
|
35
35
|
withToolResultArchiveParam,
|
|
36
36
|
AgentOrchestrator
|
|
37
37
|
} from "@poncho-ai/harness";
|
|
@@ -571,7 +571,7 @@ var WEB_UI_STYLES = `
|
|
|
571
571
|
.sidebar-segmented {
|
|
572
572
|
display: inline-flex;
|
|
573
573
|
align-self: stretch;
|
|
574
|
-
margin: 12px 6px
|
|
574
|
+
margin: 12px 6px 8px;
|
|
575
575
|
padding: 3px;
|
|
576
576
|
background: var(--surface-3);
|
|
577
577
|
border-radius: 999px;
|
|
@@ -14261,7 +14261,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
14261
14261
|
await harness.initialize();
|
|
14262
14262
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
14263
14263
|
try {
|
|
14264
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
14264
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-JWYEHJZH.js");
|
|
14265
14265
|
await runInteractiveInk({
|
|
14266
14266
|
harness,
|
|
14267
14267
|
params,
|
|
@@ -14307,7 +14307,6 @@ var approvalLog = createLogger("approval");
|
|
|
14307
14307
|
var browserLog = createLogger("browser");
|
|
14308
14308
|
var selfFetchLog = createLogger("self-fetch");
|
|
14309
14309
|
var csrfLog = createLogger("csrf");
|
|
14310
|
-
var uploadLog = createLogger("upload");
|
|
14311
14310
|
var collectToolCallIds = (msgs) => {
|
|
14312
14311
|
const ids = /* @__PURE__ */ new Set();
|
|
14313
14312
|
for (const m of msgs) {
|
|
@@ -16826,7 +16825,8 @@ data: ${JSON.stringify(frame)}
|
|
|
16826
16825
|
return;
|
|
16827
16826
|
}
|
|
16828
16827
|
if (pathname === "/api/slash-commands" && request.method === "GET") {
|
|
16829
|
-
const
|
|
16828
|
+
const tenantSkills = await harness.listSkillsForTenant(ctx.tenantId);
|
|
16829
|
+
const skills = tenantSkills.map((s) => ({
|
|
16830
16830
|
command: "/" + s.name,
|
|
16831
16831
|
description: s.description,
|
|
16832
16832
|
type: "skill"
|
|
@@ -16964,7 +16964,7 @@ data: ${JSON.stringify(frame)}
|
|
|
16964
16964
|
const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
|
|
16965
16965
|
if (conversationMessageMatch && request.method === "POST") {
|
|
16966
16966
|
const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
|
|
16967
|
-
const conversation = await conversationStore.
|
|
16967
|
+
const conversation = await conversationStore.get(conversationId);
|
|
16968
16968
|
if (!conversation || !canAccessConversation(conversation)) {
|
|
16969
16969
|
writeJson(response, 404, {
|
|
16970
16970
|
code: "CONVERSATION_NOT_FOUND",
|
|
@@ -17025,6 +17025,8 @@ data: ${JSON.stringify(frame)}
|
|
|
17025
17025
|
});
|
|
17026
17026
|
if (conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0)) {
|
|
17027
17027
|
conversation.title = inferConversationTitle(messageText);
|
|
17028
|
+
conversation.updatedAt = Date.now();
|
|
17029
|
+
await conversationStore.update(conversation);
|
|
17028
17030
|
}
|
|
17029
17031
|
response.writeHead(200, {
|
|
17030
17032
|
"Content-Type": "text/event-stream",
|
|
@@ -17032,53 +17034,6 @@ data: ${JSON.stringify(frame)}
|
|
|
17032
17034
|
Connection: "keep-alive",
|
|
17033
17035
|
"X-Accel-Buffering": "no"
|
|
17034
17036
|
});
|
|
17035
|
-
const canonicalHistory = resolveRunRequest(conversation, {
|
|
17036
|
-
conversationId,
|
|
17037
|
-
messages: conversation.messages
|
|
17038
|
-
});
|
|
17039
|
-
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
17040
|
-
const harnessMessages = [...canonicalHistory.messages];
|
|
17041
|
-
const historyMessages = [...conversation.messages];
|
|
17042
|
-
const preRunMessages = [...conversation.messages];
|
|
17043
|
-
log.debug(
|
|
17044
|
-
`conversation=${conversationId.slice(0, 8)} history=${canonicalHistory.source}`
|
|
17045
|
-
);
|
|
17046
|
-
let latestRunId = conversation.runtimeRunId ?? "";
|
|
17047
|
-
let userContent = messageText;
|
|
17048
|
-
if (files.length > 0) {
|
|
17049
|
-
try {
|
|
17050
|
-
const uploadedParts = await Promise.all(
|
|
17051
|
-
files.map(async (f) => {
|
|
17052
|
-
const buf = Buffer.from(f.data, "base64");
|
|
17053
|
-
const key = deriveUploadKey(buf, f.mediaType);
|
|
17054
|
-
const ref = await uploadStore.put(key, buf, f.mediaType);
|
|
17055
|
-
return {
|
|
17056
|
-
type: "file",
|
|
17057
|
-
data: ref,
|
|
17058
|
-
mediaType: f.mediaType,
|
|
17059
|
-
filename: f.filename
|
|
17060
|
-
};
|
|
17061
|
-
})
|
|
17062
|
-
);
|
|
17063
|
-
userContent = [
|
|
17064
|
-
{ type: "text", text: messageText },
|
|
17065
|
-
...uploadedParts
|
|
17066
|
-
];
|
|
17067
|
-
} catch (uploadErr) {
|
|
17068
|
-
const errMsg = uploadErr instanceof Error ? uploadErr.message : String(uploadErr);
|
|
17069
|
-
uploadLog.error(`file upload failed: ${errMsg}`);
|
|
17070
|
-
const errorEvent = {
|
|
17071
|
-
type: "run:error",
|
|
17072
|
-
runId: "",
|
|
17073
|
-
error: { code: "UPLOAD_ERROR", message: `File upload failed: ${errMsg}` }
|
|
17074
|
-
};
|
|
17075
|
-
broadcastEvent(conversationId, errorEvent);
|
|
17076
|
-
finishConversationStream(conversationId);
|
|
17077
|
-
activeConversationRuns.delete(conversationId);
|
|
17078
|
-
response.end();
|
|
17079
|
-
return;
|
|
17080
|
-
}
|
|
17081
|
-
}
|
|
17082
17037
|
const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
|
|
17083
17038
|
if (evt.type.startsWith("subagent:")) {
|
|
17084
17039
|
try {
|
|
@@ -17087,79 +17042,18 @@ data: ${JSON.stringify(frame)}
|
|
|
17087
17042
|
}
|
|
17088
17043
|
}
|
|
17089
17044
|
});
|
|
17090
|
-
|
|
17091
|
-
let checkpointedRun = false;
|
|
17092
|
-
let runCancelled = false;
|
|
17093
|
-
let runContinuationMessages;
|
|
17094
|
-
let cancelHarnessMessages;
|
|
17095
|
-
const turnTimestamp = Date.now();
|
|
17096
|
-
const userMessage = userContent != null ? {
|
|
17097
|
-
role: "user",
|
|
17098
|
-
content: userContent,
|
|
17099
|
-
metadata: { id: randomUUID3(), timestamp: turnTimestamp }
|
|
17100
|
-
} : void 0;
|
|
17101
|
-
const assistantId = randomUUID3();
|
|
17102
|
-
const buildMessages = () => {
|
|
17103
|
-
const draftSections = cloneSections(draft.sections);
|
|
17104
|
-
if (draft.currentTools.length > 0) {
|
|
17105
|
-
draftSections.push({ type: "tools", content: [...draft.currentTools] });
|
|
17106
|
-
}
|
|
17107
|
-
if (draft.currentText.length > 0) {
|
|
17108
|
-
draftSections.push({ type: "text", content: draft.currentText });
|
|
17109
|
-
}
|
|
17110
|
-
const userTurn = userMessage ? [userMessage] : [];
|
|
17111
|
-
const hasDraftContent = draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
|
|
17112
|
-
if (!hasDraftContent) {
|
|
17113
|
-
return [...historyMessages, ...userTurn];
|
|
17114
|
-
}
|
|
17115
|
-
return [
|
|
17116
|
-
...historyMessages,
|
|
17117
|
-
...userTurn,
|
|
17118
|
-
{
|
|
17119
|
-
role: "assistant",
|
|
17120
|
-
content: draft.assistantResponse,
|
|
17121
|
-
metadata: buildAssistantMetadata2(draft, draftSections, { id: assistantId, timestamp: turnTimestamp })
|
|
17122
|
-
}
|
|
17123
|
-
];
|
|
17124
|
-
};
|
|
17125
|
-
const persistDraftAssistantTurn = async () => {
|
|
17126
|
-
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
17127
|
-
conversation.messages = buildMessages();
|
|
17128
|
-
conversation.updatedAt = Date.now();
|
|
17129
|
-
await conversationStore.update(conversation);
|
|
17130
|
-
};
|
|
17045
|
+
let latestRunId = "";
|
|
17131
17046
|
try {
|
|
17132
|
-
{
|
|
17133
|
-
conversation.messages = [
|
|
17134
|
-
...historyMessages,
|
|
17135
|
-
...userMessage ? [userMessage] : []
|
|
17136
|
-
];
|
|
17137
|
-
conversation.subagentCallbackCount = 0;
|
|
17138
|
-
conversation._continuationCount = void 0;
|
|
17139
|
-
conversation.updatedAt = Date.now();
|
|
17140
|
-
conversationStore.update(conversation).catch((err) => {
|
|
17141
|
-
log.error(`failed to persist user turn: ${formatError(err)}`);
|
|
17142
|
-
});
|
|
17143
|
-
}
|
|
17144
|
-
const execution = await executeConversationTurn2({
|
|
17047
|
+
const result = await runConversationTurn({
|
|
17145
17048
|
harness,
|
|
17146
|
-
|
|
17147
|
-
|
|
17148
|
-
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
},
|
|
17155
|
-
initialContextTokens: conversation.contextTokens ?? 0,
|
|
17156
|
-
initialContextWindow: conversation.contextWindow ?? 0,
|
|
17157
|
-
onEvent: async (event, eventDraft) => {
|
|
17158
|
-
draft.assistantResponse = eventDraft.assistantResponse;
|
|
17159
|
-
draft.toolTimeline = eventDraft.toolTimeline;
|
|
17160
|
-
draft.sections = eventDraft.sections;
|
|
17161
|
-
draft.currentTools = eventDraft.currentTools;
|
|
17162
|
-
draft.currentText = eventDraft.currentText;
|
|
17049
|
+
conversationStore,
|
|
17050
|
+
conversationId,
|
|
17051
|
+
task: messageText,
|
|
17052
|
+
files: files.length > 0 ? files : void 0,
|
|
17053
|
+
parameters: buildTurnParameters(conversation, { bodyParameters }),
|
|
17054
|
+
abortSignal: abortController.signal,
|
|
17055
|
+
tenantId: ctx.tenantId ?? void 0,
|
|
17056
|
+
onEvent: async (event) => {
|
|
17163
17057
|
if (event.type === "run:started") {
|
|
17164
17058
|
latestRunId = event.runId;
|
|
17165
17059
|
runOwners.set(event.runId, ownerId);
|
|
@@ -17169,98 +17063,12 @@ data: ${JSON.stringify(frame)}
|
|
|
17169
17063
|
active.runId = event.runId;
|
|
17170
17064
|
}
|
|
17171
17065
|
}
|
|
17172
|
-
if (event.type === "run:cancelled") {
|
|
17173
|
-
runCancelled = true;
|
|
17174
|
-
if (event.messages) cancelHarnessMessages = event.messages;
|
|
17175
|
-
}
|
|
17176
|
-
if (event.type === "compaction:completed") {
|
|
17177
|
-
if (event.compactedMessages) {
|
|
17178
|
-
historyMessages.length = 0;
|
|
17179
|
-
historyMessages.push(...event.compactedMessages);
|
|
17180
|
-
const preservedFromHistory = historyMessages.length - 1;
|
|
17181
|
-
const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
|
|
17182
|
-
const existingHistory = conversation.compactedHistory ?? [];
|
|
17183
|
-
conversation.compactedHistory = [
|
|
17184
|
-
...existingHistory,
|
|
17185
|
-
...preRunMessages.slice(0, removedCount)
|
|
17186
|
-
];
|
|
17187
|
-
}
|
|
17188
|
-
}
|
|
17189
|
-
if (event.type === "step:completed") {
|
|
17190
|
-
await persistDraftAssistantTurn();
|
|
17191
|
-
}
|
|
17192
|
-
if (event.type === "tool:approval:required") {
|
|
17193
|
-
const toolText = `- approval required \`${event.tool}\``;
|
|
17194
|
-
draft.toolTimeline.push(toolText);
|
|
17195
|
-
draft.currentTools.push(toolText);
|
|
17196
|
-
const existingApprovals = Array.isArray(conversation.pendingApprovals) ? conversation.pendingApprovals : [];
|
|
17197
|
-
if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
|
|
17198
|
-
conversation.pendingApprovals = [
|
|
17199
|
-
...existingApprovals,
|
|
17200
|
-
{
|
|
17201
|
-
approvalId: event.approvalId,
|
|
17202
|
-
runId: latestRunId || conversation.runtimeRunId || "",
|
|
17203
|
-
tool: event.tool,
|
|
17204
|
-
toolCallId: void 0,
|
|
17205
|
-
input: event.input ?? {},
|
|
17206
|
-
checkpointMessages: void 0,
|
|
17207
|
-
baseMessageCount: historyMessages.length,
|
|
17208
|
-
pendingToolCalls: []
|
|
17209
|
-
}
|
|
17210
|
-
];
|
|
17211
|
-
conversation.updatedAt = Date.now();
|
|
17212
|
-
await conversationStore.update(conversation);
|
|
17213
|
-
}
|
|
17214
|
-
await persistDraftAssistantTurn();
|
|
17215
|
-
}
|
|
17216
|
-
if (event.type === "tool:approval:checkpoint") {
|
|
17217
|
-
conversation.messages = buildMessages();
|
|
17218
|
-
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
17219
|
-
approvals: event.approvals,
|
|
17220
|
-
runId: latestRunId,
|
|
17221
|
-
checkpointMessages: event.checkpointMessages,
|
|
17222
|
-
baseMessageCount: historyMessages.length,
|
|
17223
|
-
pendingToolCalls: event.pendingToolCalls
|
|
17224
|
-
});
|
|
17225
|
-
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
17226
|
-
conversation.updatedAt = Date.now();
|
|
17227
|
-
await conversationStore.update(conversation);
|
|
17228
|
-
checkpointedRun = true;
|
|
17229
|
-
}
|
|
17230
|
-
if (event.type === "run:completed") {
|
|
17231
|
-
if (event.result.continuation && event.result.continuationMessages) {
|
|
17232
|
-
runContinuationMessages = event.result.continuationMessages;
|
|
17233
|
-
conversation.messages = buildMessages();
|
|
17234
|
-
conversation._continuationMessages = runContinuationMessages;
|
|
17235
|
-
conversation._harnessMessages = runContinuationMessages;
|
|
17236
|
-
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
17237
|
-
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
17238
|
-
if (!checkpointedRun) {
|
|
17239
|
-
conversation.pendingApprovals = [];
|
|
17240
|
-
}
|
|
17241
|
-
if ((event.result.contextTokens ?? 0) > 0) conversation.contextTokens = event.result.contextTokens;
|
|
17242
|
-
if ((event.result.contextWindow ?? 0) > 0) conversation.contextWindow = event.result.contextWindow;
|
|
17243
|
-
conversation.updatedAt = Date.now();
|
|
17244
|
-
await conversationStore.update(conversation);
|
|
17245
|
-
if (!checkpointedRun) {
|
|
17246
|
-
doWaitUntil(
|
|
17247
|
-
new Promise((r) => setTimeout(r, 3e3)).then(
|
|
17248
|
-
() => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
|
|
17249
|
-
)
|
|
17250
|
-
);
|
|
17251
|
-
}
|
|
17252
|
-
}
|
|
17253
|
-
}
|
|
17254
17066
|
await telemetry.emit(event);
|
|
17255
17067
|
let sseEvent = event.type === "compaction:completed" && event.compactedMessages ? { ...event, compactedMessages: void 0 } : event;
|
|
17256
17068
|
if (sseEvent.type === "run:completed") {
|
|
17257
17069
|
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
17258
17070
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: void 0 } };
|
|
17259
|
-
|
|
17260
|
-
sseEvent = { ...stripped, pendingSubagents: true };
|
|
17261
|
-
} else {
|
|
17262
|
-
sseEvent = stripped;
|
|
17263
|
-
}
|
|
17071
|
+
sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
|
|
17264
17072
|
}
|
|
17265
17073
|
broadcastEvent(conversationId, sseEvent);
|
|
17266
17074
|
try {
|
|
@@ -17270,38 +17078,15 @@ data: ${JSON.stringify(frame)}
|
|
|
17270
17078
|
emitBrowserStatusIfActive(conversationId, event, response);
|
|
17271
17079
|
}
|
|
17272
17080
|
});
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
contextTokens: execution.runContextTokens,
|
|
17280
|
-
contextWindow: execution.runContextWindow,
|
|
17281
|
-
harnessMessages: execution.runHarnessMessages,
|
|
17282
|
-
toolResultArchive: harness.getToolResultArchive(conversationId)
|
|
17283
|
-
}, { shouldRebuildCanonical });
|
|
17284
|
-
await conversationStore.update(conversation);
|
|
17081
|
+
if (result.continuation && !result.checkpointed) {
|
|
17082
|
+
doWaitUntil(
|
|
17083
|
+
new Promise((r) => setTimeout(r, 3e3)).then(
|
|
17084
|
+
() => selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`)
|
|
17085
|
+
)
|
|
17086
|
+
);
|
|
17285
17087
|
}
|
|
17286
17088
|
} catch (error) {
|
|
17287
|
-
|
|
17288
|
-
if (abortController.signal.aborted || runCancelled) {
|
|
17289
|
-
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
17290
|
-
conversation.messages = buildMessages();
|
|
17291
|
-
applyTurnMetadata(conversation, {
|
|
17292
|
-
latestRunId,
|
|
17293
|
-
contextTokens: 0,
|
|
17294
|
-
contextWindow: 0,
|
|
17295
|
-
harnessMessages: cancelHarnessMessages,
|
|
17296
|
-
toolResultArchive: harness.getToolResultArchive(conversationId)
|
|
17297
|
-
}, { shouldRebuildCanonical: true });
|
|
17298
|
-
await conversationStore.update(conversation);
|
|
17299
|
-
}
|
|
17300
|
-
if (!checkpointedRun) {
|
|
17301
|
-
await clearPendingApprovalsForConversation(conversationId);
|
|
17302
|
-
}
|
|
17303
|
-
return;
|
|
17304
|
-
}
|
|
17089
|
+
log.error(`runConversationTurn threw: ${formatError(error)}`);
|
|
17305
17090
|
try {
|
|
17306
17091
|
response.write(
|
|
17307
17092
|
formatSseEvent({
|
|
@@ -17314,11 +17099,6 @@ data: ${JSON.stringify(frame)}
|
|
|
17314
17099
|
})
|
|
17315
17100
|
);
|
|
17316
17101
|
} catch {
|
|
17317
|
-
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
17318
|
-
conversation.messages = buildMessages();
|
|
17319
|
-
conversation.updatedAt = Date.now();
|
|
17320
|
-
await conversationStore.update(conversation);
|
|
17321
|
-
}
|
|
17322
17102
|
}
|
|
17323
17103
|
} finally {
|
|
17324
17104
|
unsubSubagentEvents();
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.3",
|
|
4
4
|
"description": "CLI for building and deploying AI agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"react": "^19.2.4",
|
|
29
29
|
"react-devtools-core": "^6.1.5",
|
|
30
30
|
"yaml": "^2.8.1",
|
|
31
|
-
"@poncho-ai/harness": "0.
|
|
31
|
+
"@poncho-ai/harness": "0.42.0",
|
|
32
32
|
"@poncho-ai/messaging": "0.8.5",
|
|
33
33
|
"@poncho-ai/sdk": "1.10.0"
|
|
34
34
|
},
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
createConversationStore,
|
|
17
17
|
createConversationStoreFromEngine,
|
|
18
18
|
createUploadStore,
|
|
19
|
-
deriveUploadKey,
|
|
20
19
|
ensureAgentIdentity,
|
|
21
20
|
loadPonchoConfig,
|
|
22
21
|
parseAgentMarkdown,
|
|
@@ -39,6 +38,7 @@ import {
|
|
|
39
38
|
normalizeApprovalCheckpoint,
|
|
40
39
|
buildApprovalCheckpoints,
|
|
41
40
|
applyTurnMetadata,
|
|
41
|
+
runConversationTurn,
|
|
42
42
|
TOOL_RESULT_ARCHIVE_PARAM,
|
|
43
43
|
withToolResultArchiveParam,
|
|
44
44
|
AgentOrchestrator,
|
|
@@ -93,7 +93,6 @@ const approvalLog = createLogger("approval");
|
|
|
93
93
|
const browserLog = createLogger("browser");
|
|
94
94
|
const selfFetchLog = createLogger("self-fetch");
|
|
95
95
|
const csrfLog = createLogger("csrf");
|
|
96
|
-
const uploadLog = createLogger("upload");
|
|
97
96
|
|
|
98
97
|
/**
|
|
99
98
|
* Walk a sequence of harness messages and collect all tool-call ids that
|
|
@@ -3209,7 +3208,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3209
3208
|
}
|
|
3210
3209
|
|
|
3211
3210
|
if (pathname === "/api/slash-commands" && request.method === "GET") {
|
|
3212
|
-
const
|
|
3211
|
+
const tenantSkills = await harness.listSkillsForTenant(ctx.tenantId);
|
|
3212
|
+
const skills: ApiSlashCommand[] = tenantSkills.map((s) => ({
|
|
3213
3213
|
command: "/" + s.name,
|
|
3214
3214
|
description: s.description,
|
|
3215
3215
|
type: "skill" as const,
|
|
@@ -3353,9 +3353,9 @@ export const createRequestHandler = async (options?: {
|
|
|
3353
3353
|
const conversationMessageMatch = pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
|
|
3354
3354
|
if (conversationMessageMatch && request.method === "POST") {
|
|
3355
3355
|
const conversationId = decodeURIComponent(conversationMessageMatch[1] ?? "");
|
|
3356
|
-
//
|
|
3357
|
-
//
|
|
3358
|
-
const conversation = await conversationStore.
|
|
3356
|
+
// Light access check first; the helper reloads the conversation
|
|
3357
|
+
// (with archive) when it actually runs the turn.
|
|
3358
|
+
const conversation = await conversationStore.get(conversationId);
|
|
3359
3359
|
if (!conversation || !canAccessConversation(conversation)) {
|
|
3360
3360
|
writeJson(response, 404, {
|
|
3361
3361
|
code: "CONVERSATION_NOT_FOUND",
|
|
@@ -3420,159 +3420,46 @@ export const createRequestHandler = async (options?: {
|
|
|
3420
3420
|
abortController,
|
|
3421
3421
|
runId: null,
|
|
3422
3422
|
});
|
|
3423
|
+
|
|
3424
|
+
// Auto-infer a title for fresh conversations. Persist it before the
|
|
3425
|
+
// helper reloads, so its in-memory copy carries the new title.
|
|
3423
3426
|
if (
|
|
3424
3427
|
conversation.messages.length === 0 &&
|
|
3425
3428
|
(conversation.title === "New conversation" || conversation.title.trim().length === 0)
|
|
3426
3429
|
) {
|
|
3427
3430
|
conversation.title = inferConversationTitle(messageText);
|
|
3431
|
+
conversation.updatedAt = Date.now();
|
|
3432
|
+
await conversationStore.update(conversation);
|
|
3428
3433
|
}
|
|
3434
|
+
|
|
3429
3435
|
response.writeHead(200, {
|
|
3430
3436
|
"Content-Type": "text/event-stream",
|
|
3431
3437
|
"Cache-Control": "no-cache",
|
|
3432
3438
|
Connection: "keep-alive",
|
|
3433
3439
|
"X-Accel-Buffering": "no",
|
|
3434
3440
|
});
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
});
|
|
3439
|
-
const shouldRebuildCanonical = canonicalHistory.shouldRebuildCanonical;
|
|
3440
|
-
const harnessMessages = [...canonicalHistory.messages];
|
|
3441
|
-
const historyMessages = [...conversation.messages];
|
|
3442
|
-
const preRunMessages = [...conversation.messages];
|
|
3443
|
-
log.debug(
|
|
3444
|
-
`conversation=${conversationId.slice(0, 8)} history=${canonicalHistory.source}`,
|
|
3445
|
-
);
|
|
3446
|
-
let latestRunId = conversation.runtimeRunId ?? "";
|
|
3447
|
-
let userContent: Message["content"] | undefined = messageText;
|
|
3448
|
-
if (files.length > 0) {
|
|
3449
|
-
try {
|
|
3450
|
-
const uploadedParts = await Promise.all(
|
|
3451
|
-
files.map(async (f) => {
|
|
3452
|
-
const buf = Buffer.from(f.data, "base64");
|
|
3453
|
-
const key = deriveUploadKey(buf, f.mediaType);
|
|
3454
|
-
const ref = await uploadStore.put(key, buf, f.mediaType);
|
|
3455
|
-
return {
|
|
3456
|
-
type: "file" as const,
|
|
3457
|
-
data: ref,
|
|
3458
|
-
mediaType: f.mediaType,
|
|
3459
|
-
filename: f.filename,
|
|
3460
|
-
};
|
|
3461
|
-
}),
|
|
3462
|
-
);
|
|
3463
|
-
userContent = [
|
|
3464
|
-
{ type: "text" as const, text: messageText },
|
|
3465
|
-
...uploadedParts,
|
|
3466
|
-
];
|
|
3467
|
-
} catch (uploadErr) {
|
|
3468
|
-
const errMsg = uploadErr instanceof Error ? uploadErr.message : String(uploadErr);
|
|
3469
|
-
uploadLog.error(`file upload failed: ${errMsg}`);
|
|
3470
|
-
const errorEvent: AgentEvent = {
|
|
3471
|
-
type: "run:error",
|
|
3472
|
-
runId: "",
|
|
3473
|
-
error: { code: "UPLOAD_ERROR", message: `File upload failed: ${errMsg}` },
|
|
3474
|
-
};
|
|
3475
|
-
broadcastEvent(conversationId, errorEvent);
|
|
3476
|
-
finishConversationStream(conversationId);
|
|
3477
|
-
activeConversationRuns.delete(conversationId);
|
|
3478
|
-
response.end();
|
|
3479
|
-
return;
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3441
|
+
|
|
3442
|
+
// Forward subagent events out-of-band — they're emitted from
|
|
3443
|
+
// child-conversation runs, not yielded by harness.run for this one.
|
|
3482
3444
|
const unsubSubagentEvents = onConversationEvent(conversationId, (evt) => {
|
|
3483
3445
|
if (evt.type.startsWith("subagent:")) {
|
|
3484
3446
|
try { response.write(formatSseEvent(evt)); } catch {}
|
|
3485
3447
|
}
|
|
3486
3448
|
});
|
|
3487
3449
|
|
|
3488
|
-
|
|
3489
|
-
let checkpointedRun = false;
|
|
3490
|
-
let runCancelled = false;
|
|
3491
|
-
let runContinuationMessages: Message[] | undefined;
|
|
3492
|
-
// Snapshot of the harness's in-flight messages emitted with run:cancelled,
|
|
3493
|
-
// so the catch-path (executeConversationTurn threw) can still persist a
|
|
3494
|
-
// canonical history that includes the cancelled work.
|
|
3495
|
-
let cancelHarnessMessages: Message[] | undefined;
|
|
3496
|
-
|
|
3497
|
-
// Hoist stable ids for this turn. The same userMessage / assistantId is
|
|
3498
|
-
// reused across every buildMessages() call so the in-flight assistant
|
|
3499
|
-
// bubble keeps a stable metadata.id from its very first persisted byte.
|
|
3500
|
-
const turnTimestamp = Date.now();
|
|
3501
|
-
const userMessage: Message | undefined = userContent != null
|
|
3502
|
-
? {
|
|
3503
|
-
role: "user" as const,
|
|
3504
|
-
content: userContent,
|
|
3505
|
-
metadata: { id: randomUUID(), timestamp: turnTimestamp },
|
|
3506
|
-
}
|
|
3507
|
-
: undefined;
|
|
3508
|
-
const assistantId = randomUUID();
|
|
3509
|
-
|
|
3510
|
-
const buildMessages = (): Message[] => {
|
|
3511
|
-
const draftSections = cloneSections(draft.sections);
|
|
3512
|
-
if (draft.currentTools.length > 0) {
|
|
3513
|
-
draftSections.push({ type: "tools", content: [...draft.currentTools] });
|
|
3514
|
-
}
|
|
3515
|
-
if (draft.currentText.length > 0) {
|
|
3516
|
-
draftSections.push({ type: "text", content: draft.currentText });
|
|
3517
|
-
}
|
|
3518
|
-
const userTurn: Message[] = userMessage ? [userMessage] : [];
|
|
3519
|
-
const hasDraftContent =
|
|
3520
|
-
draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draftSections.length > 0;
|
|
3521
|
-
if (!hasDraftContent) {
|
|
3522
|
-
return [...historyMessages, ...userTurn];
|
|
3523
|
-
}
|
|
3524
|
-
return [
|
|
3525
|
-
...historyMessages,
|
|
3526
|
-
...userTurn,
|
|
3527
|
-
{
|
|
3528
|
-
role: "assistant" as const,
|
|
3529
|
-
content: draft.assistantResponse,
|
|
3530
|
-
metadata: buildAssistantMetadata(draft, draftSections, { id: assistantId, timestamp: turnTimestamp }),
|
|
3531
|
-
},
|
|
3532
|
-
];
|
|
3533
|
-
};
|
|
3534
|
-
|
|
3535
|
-
const persistDraftAssistantTurn = async (): Promise<void> => {
|
|
3536
|
-
if (draft.assistantResponse.length === 0 && draft.toolTimeline.length === 0) return;
|
|
3537
|
-
conversation.messages = buildMessages();
|
|
3538
|
-
conversation.updatedAt = Date.now();
|
|
3539
|
-
await conversationStore.update(conversation);
|
|
3540
|
-
};
|
|
3450
|
+
let latestRunId = "";
|
|
3541
3451
|
|
|
3542
3452
|
try {
|
|
3543
|
-
{
|
|
3544
|
-
conversation.messages = [
|
|
3545
|
-
...historyMessages,
|
|
3546
|
-
...(userMessage ? [userMessage] : []),
|
|
3547
|
-
];
|
|
3548
|
-
conversation.subagentCallbackCount = 0;
|
|
3549
|
-
conversation._continuationCount = undefined;
|
|
3550
|
-
conversation.updatedAt = Date.now();
|
|
3551
|
-
conversationStore.update(conversation).catch((err) => {
|
|
3552
|
-
log.error(`failed to persist user turn: ${formatError(err)}`);
|
|
3553
|
-
});
|
|
3554
|
-
}
|
|
3555
|
-
|
|
3556
|
-
const execution = await executeConversationTurn({
|
|
3453
|
+
const result = await runConversationTurn({
|
|
3557
3454
|
harness,
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
},
|
|
3567
|
-
initialContextTokens: conversation.contextTokens ?? 0,
|
|
3568
|
-
initialContextWindow: conversation.contextWindow ?? 0,
|
|
3569
|
-
onEvent: async (event, eventDraft) => {
|
|
3570
|
-
draft.assistantResponse = eventDraft.assistantResponse;
|
|
3571
|
-
draft.toolTimeline = eventDraft.toolTimeline;
|
|
3572
|
-
draft.sections = eventDraft.sections;
|
|
3573
|
-
draft.currentTools = eventDraft.currentTools;
|
|
3574
|
-
draft.currentText = eventDraft.currentText;
|
|
3575
|
-
|
|
3455
|
+
conversationStore,
|
|
3456
|
+
conversationId,
|
|
3457
|
+
task: messageText,
|
|
3458
|
+
files: files.length > 0 ? files : undefined,
|
|
3459
|
+
parameters: buildTurnParameters(conversation, { bodyParameters }),
|
|
3460
|
+
abortSignal: abortController.signal,
|
|
3461
|
+
tenantId: ctx.tenantId ?? undefined,
|
|
3462
|
+
onEvent: async (event) => {
|
|
3576
3463
|
if (event.type === "run:started") {
|
|
3577
3464
|
latestRunId = event.runId;
|
|
3578
3465
|
runOwners.set(event.runId, ownerId);
|
|
@@ -3582,154 +3469,36 @@ export const createRequestHandler = async (options?: {
|
|
|
3582
3469
|
active.runId = event.runId;
|
|
3583
3470
|
}
|
|
3584
3471
|
}
|
|
3585
|
-
if (event.type === "run:cancelled") {
|
|
3586
|
-
runCancelled = true;
|
|
3587
|
-
if (event.messages) cancelHarnessMessages = event.messages;
|
|
3588
|
-
}
|
|
3589
|
-
if (event.type === "compaction:completed") {
|
|
3590
|
-
if (event.compactedMessages) {
|
|
3591
|
-
historyMessages.length = 0;
|
|
3592
|
-
historyMessages.push(...event.compactedMessages);
|
|
3593
|
-
|
|
3594
|
-
const preservedFromHistory = historyMessages.length - 1;
|
|
3595
|
-
const removedCount = preRunMessages.length - Math.max(0, preservedFromHistory);
|
|
3596
|
-
const existingHistory = conversation.compactedHistory ?? [];
|
|
3597
|
-
conversation.compactedHistory = [
|
|
3598
|
-
...existingHistory,
|
|
3599
|
-
...preRunMessages.slice(0, removedCount),
|
|
3600
|
-
];
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
if (event.type === "step:completed") {
|
|
3604
|
-
await persistDraftAssistantTurn();
|
|
3605
|
-
}
|
|
3606
|
-
if (event.type === "tool:approval:required") {
|
|
3607
|
-
const toolText = `- approval required \`${event.tool}\``;
|
|
3608
|
-
draft.toolTimeline.push(toolText);
|
|
3609
|
-
draft.currentTools.push(toolText);
|
|
3610
|
-
const existingApprovals = Array.isArray(conversation.pendingApprovals)
|
|
3611
|
-
? conversation.pendingApprovals
|
|
3612
|
-
: [];
|
|
3613
|
-
if (!existingApprovals.some((approval) => approval.approvalId === event.approvalId)) {
|
|
3614
|
-
conversation.pendingApprovals = [
|
|
3615
|
-
...existingApprovals,
|
|
3616
|
-
{
|
|
3617
|
-
approvalId: event.approvalId,
|
|
3618
|
-
runId: latestRunId || conversation.runtimeRunId || "",
|
|
3619
|
-
tool: event.tool,
|
|
3620
|
-
toolCallId: undefined,
|
|
3621
|
-
input: (event.input ?? {}) as Record<string, unknown>,
|
|
3622
|
-
checkpointMessages: undefined,
|
|
3623
|
-
baseMessageCount: historyMessages.length,
|
|
3624
|
-
pendingToolCalls: [],
|
|
3625
|
-
},
|
|
3626
|
-
];
|
|
3627
|
-
conversation.updatedAt = Date.now();
|
|
3628
|
-
await conversationStore.update(conversation);
|
|
3629
|
-
}
|
|
3630
|
-
await persistDraftAssistantTurn();
|
|
3631
|
-
}
|
|
3632
|
-
if (event.type === "tool:approval:checkpoint") {
|
|
3633
|
-
conversation.messages = buildMessages();
|
|
3634
|
-
conversation.pendingApprovals = buildApprovalCheckpoints({
|
|
3635
|
-
approvals: event.approvals,
|
|
3636
|
-
runId: latestRunId,
|
|
3637
|
-
checkpointMessages: event.checkpointMessages,
|
|
3638
|
-
baseMessageCount: historyMessages.length,
|
|
3639
|
-
pendingToolCalls: event.pendingToolCalls,
|
|
3640
|
-
});
|
|
3641
|
-
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
3642
|
-
conversation.updatedAt = Date.now();
|
|
3643
|
-
await conversationStore.update(conversation);
|
|
3644
|
-
checkpointedRun = true;
|
|
3645
|
-
}
|
|
3646
|
-
if (event.type === "run:completed") {
|
|
3647
|
-
if (event.result.continuation && event.result.continuationMessages) {
|
|
3648
|
-
runContinuationMessages = event.result.continuationMessages;
|
|
3649
|
-
|
|
3650
|
-
conversation.messages = buildMessages();
|
|
3651
|
-
conversation._continuationMessages = runContinuationMessages;
|
|
3652
|
-
conversation._harnessMessages = runContinuationMessages;
|
|
3653
|
-
conversation._toolResultArchive = harness.getToolResultArchive(conversationId);
|
|
3654
|
-
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
3655
|
-
if (!checkpointedRun) {
|
|
3656
|
-
conversation.pendingApprovals = [];
|
|
3657
|
-
}
|
|
3658
|
-
if ((event.result.contextTokens ?? 0) > 0) conversation.contextTokens = event.result.contextTokens!;
|
|
3659
|
-
if ((event.result.contextWindow ?? 0) > 0) conversation.contextWindow = event.result.contextWindow!;
|
|
3660
|
-
conversation.updatedAt = Date.now();
|
|
3661
|
-
await conversationStore.update(conversation);
|
|
3662
|
-
|
|
3663
|
-
if (!checkpointedRun) {
|
|
3664
|
-
doWaitUntil(
|
|
3665
|
-
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
3666
|
-
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
3667
|
-
),
|
|
3668
|
-
);
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
3472
|
await telemetry.emit(event);
|
|
3473
|
+
// Strip large internal payloads before sending over the wire.
|
|
3674
3474
|
let sseEvent: AgentEvent = event.type === "compaction:completed" && event.compactedMessages
|
|
3675
3475
|
? { ...event, compactedMessages: undefined }
|
|
3676
3476
|
: event;
|
|
3677
3477
|
if (sseEvent.type === "run:completed") {
|
|
3678
3478
|
const hasPendingSubagents = await hasPendingSubagentWorkForParent(conversationId, ownerId);
|
|
3679
3479
|
const stripped = { ...sseEvent, result: { ...sseEvent.result, continuationMessages: undefined } };
|
|
3680
|
-
|
|
3681
|
-
sseEvent = { ...stripped, pendingSubagents: true };
|
|
3682
|
-
} else {
|
|
3683
|
-
sseEvent = stripped;
|
|
3684
|
-
}
|
|
3480
|
+
sseEvent = hasPendingSubagents ? { ...stripped, pendingSubagents: true } : stripped;
|
|
3685
3481
|
}
|
|
3686
3482
|
broadcastEvent(conversationId, sseEvent);
|
|
3687
|
-
try {
|
|
3688
|
-
response.write(formatSseEvent(sseEvent));
|
|
3689
|
-
} catch {
|
|
3690
|
-
// Client disconnected — continue processing so the run completes.
|
|
3691
|
-
}
|
|
3483
|
+
try { response.write(formatSseEvent(sseEvent)); } catch {}
|
|
3692
3484
|
emitBrowserStatusIfActive(conversationId, event, response);
|
|
3693
3485
|
},
|
|
3694
3486
|
});
|
|
3695
3487
|
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
contextWindow: execution.runContextWindow,
|
|
3705
|
-
harnessMessages: execution.runHarnessMessages,
|
|
3706
|
-
toolResultArchive: harness.getToolResultArchive(conversationId),
|
|
3707
|
-
}, { shouldRebuildCanonical });
|
|
3708
|
-
await conversationStore.update(conversation);
|
|
3488
|
+
// Trigger the auto-continuation HTTP roundtrip so the next run
|
|
3489
|
+
// queues. Helper has already persisted the continuation messages.
|
|
3490
|
+
if (result.continuation && !result.checkpointed) {
|
|
3491
|
+
doWaitUntil(
|
|
3492
|
+
new Promise(r => setTimeout(r, 3000)).then(() =>
|
|
3493
|
+
selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(conversationId)}`),
|
|
3494
|
+
),
|
|
3495
|
+
);
|
|
3709
3496
|
}
|
|
3710
3497
|
} catch (error) {
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
// Keep _harnessMessages aligned with what the model just saw.
|
|
3716
|
-
// Without this, loadCanonicalHistory will hand the next turn a
|
|
3717
|
-
// pre-cancellation snapshot and the agent will have no memory of
|
|
3718
|
-
// the work it just did.
|
|
3719
|
-
applyTurnMetadata(conversation, {
|
|
3720
|
-
latestRunId,
|
|
3721
|
-
contextTokens: 0,
|
|
3722
|
-
contextWindow: 0,
|
|
3723
|
-
harnessMessages: cancelHarnessMessages,
|
|
3724
|
-
toolResultArchive: harness.getToolResultArchive(conversationId),
|
|
3725
|
-
}, { shouldRebuildCanonical: true });
|
|
3726
|
-
await conversationStore.update(conversation);
|
|
3727
|
-
}
|
|
3728
|
-
if (!checkpointedRun) {
|
|
3729
|
-
await clearPendingApprovalsForConversation(conversationId);
|
|
3730
|
-
}
|
|
3731
|
-
return;
|
|
3732
|
-
}
|
|
3498
|
+
// The helper handles cancel/error internally and returns gracefully.
|
|
3499
|
+
// This catches anything that escapes — typically a helper bug or
|
|
3500
|
+
// the conversation being deleted between auth check and helper reload.
|
|
3501
|
+
log.error(`runConversationTurn threw: ${formatError(error)}`);
|
|
3733
3502
|
try {
|
|
3734
3503
|
response.write(
|
|
3735
3504
|
formatSseEvent({
|
|
@@ -3741,13 +3510,7 @@ export const createRequestHandler = async (options?: {
|
|
|
3741
3510
|
},
|
|
3742
3511
|
}),
|
|
3743
3512
|
);
|
|
3744
|
-
} catch {
|
|
3745
|
-
if (draft.assistantResponse.length > 0 || draft.toolTimeline.length > 0 || draft.sections.length > 0) {
|
|
3746
|
-
conversation.messages = buildMessages();
|
|
3747
|
-
conversation.updatedAt = Date.now();
|
|
3748
|
-
await conversationStore.update(conversation);
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3513
|
+
} catch {}
|
|
3751
3514
|
} finally {
|
|
3752
3515
|
unsubSubagentEvents();
|
|
3753
3516
|
const active = activeConversationRuns.get(conversationId);
|
package/src/web-ui-styles.ts
CHANGED