@poncho-ai/cli 0.5.1 → 0.6.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 +15 -15
- package/CHANGELOG.md +11 -0
- package/dist/chunk-2QHGXOCW.js +4190 -0
- package/dist/chunk-3BHIZKV4.js +4433 -0
- package/dist/chunk-5KGEWTN2.js +4451 -0
- package/dist/chunk-6RKPJ6M3.js +4450 -0
- package/dist/chunk-76TYYF2B.js +4596 -0
- package/dist/chunk-AKTDJV35.js +4445 -0
- package/dist/chunk-ANMTOB6T.js +4655 -0
- package/dist/chunk-AWYXKGGD.js +4429 -0
- package/dist/chunk-BA5FFDMU.js +4536 -0
- package/dist/chunk-BOYZXUX6.js +4193 -0
- package/dist/chunk-DRUAHCF2.js +4192 -0
- package/dist/chunk-ESXYQFEA.js +4420 -0
- package/dist/chunk-FOUP464Z.js +4450 -0
- package/dist/chunk-HQK5KX73.js +4443 -0
- package/dist/chunk-JEI3ECVY.js +4706 -0
- package/dist/chunk-KQJYYNEZ.js +4924 -0
- package/dist/chunk-L7ZTJ2D4.js +4566 -0
- package/dist/chunk-LLP2S2BI.js +4620 -0
- package/dist/chunk-PAJHE4RF.js +4193 -0
- package/dist/chunk-PMM3UA6S.js +4706 -0
- package/dist/chunk-PYSTVO25.js +4793 -0
- package/dist/chunk-R5Z54SY3.js +4500 -0
- package/dist/chunk-R7YJ44RM.js +4449 -0
- package/dist/chunk-SBTOGSNJ.js +4216 -0
- package/dist/chunk-SLMKOFSJ.js +4771 -0
- package/dist/chunk-TK6BUSBH.js +4467 -0
- package/dist/chunk-TZTINGAP.js +4702 -0
- package/dist/chunk-UBCCPTBC.js +4678 -0
- package/dist/chunk-WJVYDTUE.js +4218 -0
- package/dist/chunk-XFUFWOS4.js +4192 -0
- package/dist/chunk-XI6V7BKE.js +4761 -0
- package/dist/chunk-YIVONFU2.js +4706 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/run-interactive-ink-3F3GQQH2.js +494 -0
- package/dist/run-interactive-ink-47PEYK57.js +494 -0
- package/dist/run-interactive-ink-4W7B6MZM.js +494 -0
- package/dist/run-interactive-ink-6YSU2AFP.js +494 -0
- package/dist/run-interactive-ink-A7HDSDJV.js +494 -0
- package/dist/run-interactive-ink-BHF2CAVH.js +494 -0
- package/dist/run-interactive-ink-BOKRENUR.js +494 -0
- package/dist/run-interactive-ink-BUF3RYIK.js +494 -0
- package/dist/run-interactive-ink-C4H4JNBP.js +494 -0
- package/dist/run-interactive-ink-CEWPPPGQ.js +494 -0
- package/dist/run-interactive-ink-CWMASTAZ.js +494 -0
- package/dist/run-interactive-ink-EB5AGYLN.js +494 -0
- package/dist/run-interactive-ink-FWANCYNR.js +494 -0
- package/dist/run-interactive-ink-GDNHWAKW.js +494 -0
- package/dist/run-interactive-ink-HLJ2YQ5N.js +494 -0
- package/dist/run-interactive-ink-J2S3V2LB.js +494 -0
- package/dist/run-interactive-ink-JDTNFRN2.js +494 -0
- package/dist/run-interactive-ink-JJCOMTJ2.js +494 -0
- package/dist/run-interactive-ink-JOHZ23OL.js +494 -0
- package/dist/run-interactive-ink-KMS44EYF.js +494 -0
- package/dist/run-interactive-ink-L3EB5GYB.js +494 -0
- package/dist/run-interactive-ink-N7MMFB4H.js +494 -0
- package/dist/run-interactive-ink-PK4NP4JQ.js +494 -0
- package/dist/run-interactive-ink-QFK6ZQFM.js +494 -0
- package/dist/run-interactive-ink-RX2IX3CH.js +494 -0
- package/dist/run-interactive-ink-SXK3AQJJ.js +494 -0
- package/dist/run-interactive-ink-THTNJZHB.js +494 -0
- package/dist/run-interactive-ink-VJMRO6OF.js +494 -0
- package/dist/run-interactive-ink-ZAVTGCKW.js +494 -0
- package/dist/run-interactive-ink-ZBCX4NUD.js +494 -0
- package/dist/run-interactive-ink-ZRQIK4AP.js +494 -0
- package/dist/run-interactive-ink-ZUS5DFPZ.js +494 -0
- package/package.json +2 -2
- package/src/index.ts +337 -71
- package/src/web-ui.ts +637 -115
package/src/index.ts
CHANGED
|
@@ -348,7 +348,7 @@ poncho mcp add --url https://mcp.example.com/github --name github --auth-bearer-
|
|
|
348
348
|
# List configured servers
|
|
349
349
|
poncho mcp list
|
|
350
350
|
|
|
351
|
-
# Discover
|
|
351
|
+
# Discover MCP tools and print frontmatter intent snippets
|
|
352
352
|
poncho mcp tools list github
|
|
353
353
|
poncho mcp tools select github
|
|
354
354
|
|
|
@@ -358,32 +358,32 @@ poncho mcp remove github
|
|
|
358
358
|
|
|
359
359
|
Set required secrets in \`.env\` (for example, \`GITHUB_TOKEN=...\`).
|
|
360
360
|
|
|
361
|
-
## Tool Intent in Frontmatter
|
|
361
|
+
## Tool Intent and Approvals in Frontmatter
|
|
362
362
|
|
|
363
363
|
Declare tool intent directly in \`AGENT.md\` and \`SKILL.md\` frontmatter:
|
|
364
364
|
|
|
365
365
|
\`\`\`yaml
|
|
366
|
-
tools:
|
|
367
|
-
mcp:
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
366
|
+
allowed-tools:
|
|
367
|
+
- mcp:github/list_issues
|
|
368
|
+
- mcp:github/*
|
|
369
|
+
approval-required:
|
|
370
|
+
- mcp:github/create_issue
|
|
371
|
+
- ./scripts/deploy.ts
|
|
372
372
|
\`\`\`
|
|
373
373
|
|
|
374
374
|
How it works:
|
|
375
375
|
|
|
376
376
|
- \`AGENT.md\` provides fallback MCP intent when no skill is active.
|
|
377
377
|
- \`SKILL.md\` intent applies when you activate that skill (\`activate_skill\`).
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
378
|
+
- Scripts in a sibling \`scripts/\` directory are available by convention.
|
|
379
|
+
- For non-standard script folders (for example \`tools/\`), add explicit relative entries in \`allowed-tools\`.
|
|
380
|
+
- Use \`approval-required\` to require human approval for specific MCP calls or script files.
|
|
381
381
|
- Deactivating a skill (\`deactivate_skill\`) removes its MCP tools from runtime registration.
|
|
382
382
|
|
|
383
383
|
Pattern format is strict slash-only:
|
|
384
384
|
|
|
385
385
|
- MCP: \`server/tool\`, \`server/*\`
|
|
386
|
-
- Scripts:
|
|
386
|
+
- Scripts: relative paths such as \`./scripts/file.ts\`, \`./scripts/*\`, \`./tools/deploy.ts\`
|
|
387
387
|
|
|
388
388
|
## Configuration
|
|
389
389
|
|
|
@@ -415,16 +415,8 @@ export default {
|
|
|
415
415
|
name: "github",
|
|
416
416
|
url: "https://mcp.example.com/github",
|
|
417
417
|
auth: { type: "bearer", tokenEnv: "GITHUB_TOKEN" },
|
|
418
|
-
tools: {
|
|
419
|
-
mode: "allowlist",
|
|
420
|
-
include: ["github/list_issues", "github/get_issue"],
|
|
421
|
-
},
|
|
422
418
|
},
|
|
423
419
|
],
|
|
424
|
-
scripts: {
|
|
425
|
-
mode: "allowlist",
|
|
426
|
-
include: ["starter/scripts/*"],
|
|
427
|
-
},
|
|
428
420
|
tools: {
|
|
429
421
|
defaults: {
|
|
430
422
|
list_directory: true,
|
|
@@ -824,7 +816,95 @@ export const createRequestHandler = async (options?: {
|
|
|
824
816
|
agentModelName = modelMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
825
817
|
}
|
|
826
818
|
} catch {}
|
|
827
|
-
const
|
|
819
|
+
const runOwners = new Map<string, string>();
|
|
820
|
+
const runConversations = new Map<string, string>();
|
|
821
|
+
type PendingApproval = {
|
|
822
|
+
ownerId: string;
|
|
823
|
+
runId: string;
|
|
824
|
+
conversationId: string | null;
|
|
825
|
+
tool: string;
|
|
826
|
+
input: Record<string, unknown>;
|
|
827
|
+
resolve: (approved: boolean) => void;
|
|
828
|
+
};
|
|
829
|
+
const pendingApprovals = new Map<string, PendingApproval>();
|
|
830
|
+
|
|
831
|
+
// Per-conversation event streaming: buffer events and allow SSE subscribers
|
|
832
|
+
type ConversationEventStream = {
|
|
833
|
+
buffer: AgentEvent[];
|
|
834
|
+
subscribers: Set<ServerResponse>;
|
|
835
|
+
finished: boolean;
|
|
836
|
+
};
|
|
837
|
+
const conversationEventStreams = new Map<string, ConversationEventStream>();
|
|
838
|
+
const broadcastEvent = (conversationId: string, event: AgentEvent): void => {
|
|
839
|
+
let stream = conversationEventStreams.get(conversationId);
|
|
840
|
+
if (!stream) {
|
|
841
|
+
stream = { buffer: [], subscribers: new Set(), finished: false };
|
|
842
|
+
conversationEventStreams.set(conversationId, stream);
|
|
843
|
+
}
|
|
844
|
+
stream.buffer.push(event);
|
|
845
|
+
for (const subscriber of stream.subscribers) {
|
|
846
|
+
try {
|
|
847
|
+
subscriber.write(formatSseEvent(event));
|
|
848
|
+
} catch {
|
|
849
|
+
stream.subscribers.delete(subscriber);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const finishConversationStream = (conversationId: string): void => {
|
|
854
|
+
const stream = conversationEventStreams.get(conversationId);
|
|
855
|
+
if (stream) {
|
|
856
|
+
stream.finished = true;
|
|
857
|
+
for (const subscriber of stream.subscribers) {
|
|
858
|
+
try {
|
|
859
|
+
subscriber.write("event: stream:end\ndata: {}\n\n");
|
|
860
|
+
subscriber.end();
|
|
861
|
+
} catch {
|
|
862
|
+
// Already closed.
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
stream.subscribers.clear();
|
|
866
|
+
// Keep buffer for a short time so late-joining clients get replay
|
|
867
|
+
setTimeout(() => conversationEventStreams.delete(conversationId), 30_000);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
const persistConversationPendingApprovals = async (conversationId: string): Promise<void> => {
|
|
871
|
+
const conversation = await conversationStore.get(conversationId);
|
|
872
|
+
if (!conversation) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
conversation.pendingApprovals = Array.from(pendingApprovals.entries())
|
|
876
|
+
.filter(
|
|
877
|
+
([, pending]) =>
|
|
878
|
+
pending.ownerId === conversation.ownerId && pending.conversationId === conversationId,
|
|
879
|
+
)
|
|
880
|
+
.map(([approvalId, pending]) => ({
|
|
881
|
+
approvalId,
|
|
882
|
+
runId: pending.runId,
|
|
883
|
+
tool: pending.tool,
|
|
884
|
+
input: pending.input,
|
|
885
|
+
}));
|
|
886
|
+
await conversationStore.update(conversation);
|
|
887
|
+
};
|
|
888
|
+
const harness = new AgentHarness({
|
|
889
|
+
workingDir,
|
|
890
|
+
environment: resolveHarnessEnvironment(),
|
|
891
|
+
approvalHandler: async (request) =>
|
|
892
|
+
new Promise<boolean>((resolveApproval) => {
|
|
893
|
+
const ownerIdForRun = runOwners.get(request.runId) ?? "local-owner";
|
|
894
|
+
const conversationIdForRun = runConversations.get(request.runId) ?? null;
|
|
895
|
+
pendingApprovals.set(request.approvalId, {
|
|
896
|
+
ownerId: ownerIdForRun,
|
|
897
|
+
runId: request.runId,
|
|
898
|
+
conversationId: conversationIdForRun,
|
|
899
|
+
tool: request.tool,
|
|
900
|
+
input: request.input,
|
|
901
|
+
resolve: resolveApproval,
|
|
902
|
+
});
|
|
903
|
+
if (conversationIdForRun) {
|
|
904
|
+
void persistConversationPendingApprovals(conversationIdForRun);
|
|
905
|
+
}
|
|
906
|
+
}),
|
|
907
|
+
});
|
|
828
908
|
await harness.initialize();
|
|
829
909
|
const telemetry = new TelemetryEmitter(config?.telemetry);
|
|
830
910
|
const conversationStore = createConversationStore(resolveStateConfig(config), { workingDir });
|
|
@@ -1045,6 +1125,94 @@ export const createRequestHandler = async (options?: {
|
|
|
1045
1125
|
return;
|
|
1046
1126
|
}
|
|
1047
1127
|
|
|
1128
|
+
const approvalMatch = pathname.match(/^\/api\/approvals\/([^/]+)$/);
|
|
1129
|
+
if (approvalMatch && request.method === "POST") {
|
|
1130
|
+
const approvalId = decodeURIComponent(approvalMatch[1] ?? "");
|
|
1131
|
+
const pending = pendingApprovals.get(approvalId);
|
|
1132
|
+
if (!pending || pending.ownerId !== ownerId) {
|
|
1133
|
+
// If the server restarted, an old pending approval can remain in
|
|
1134
|
+
// conversation history without an active resolver. Prune stale entries.
|
|
1135
|
+
const conversations = await conversationStore.list(ownerId);
|
|
1136
|
+
let prunedStale = false;
|
|
1137
|
+
for (const conversation of conversations) {
|
|
1138
|
+
if (!Array.isArray(conversation.pendingApprovals)) {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
const next = conversation.pendingApprovals.filter(
|
|
1142
|
+
(approval) => approval.approvalId !== approvalId,
|
|
1143
|
+
);
|
|
1144
|
+
if (next.length !== conversation.pendingApprovals.length) {
|
|
1145
|
+
conversation.pendingApprovals = next;
|
|
1146
|
+
await conversationStore.update(conversation);
|
|
1147
|
+
prunedStale = true;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
writeJson(response, 404, {
|
|
1151
|
+
code: "APPROVAL_NOT_FOUND",
|
|
1152
|
+
message: prunedStale
|
|
1153
|
+
? "Approval request is no longer active"
|
|
1154
|
+
: "Approval request not found",
|
|
1155
|
+
});
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const body = (await readRequestBody(request)) as { approved?: boolean };
|
|
1159
|
+
const approved = body.approved === true;
|
|
1160
|
+
pendingApprovals.delete(approvalId);
|
|
1161
|
+
if (pending.conversationId) {
|
|
1162
|
+
await persistConversationPendingApprovals(pending.conversationId);
|
|
1163
|
+
}
|
|
1164
|
+
pending.resolve(approved);
|
|
1165
|
+
writeJson(response, 200, { ok: true, approvalId, approved });
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const conversationEventsMatch = pathname.match(
|
|
1170
|
+
/^\/api\/conversations\/([^/]+)\/events$/,
|
|
1171
|
+
);
|
|
1172
|
+
if (conversationEventsMatch && request.method === "GET") {
|
|
1173
|
+
const conversationId = decodeURIComponent(conversationEventsMatch[1] ?? "");
|
|
1174
|
+
const conversation = await conversationStore.get(conversationId);
|
|
1175
|
+
if (!conversation || conversation.ownerId !== ownerId) {
|
|
1176
|
+
writeJson(response, 404, {
|
|
1177
|
+
code: "CONVERSATION_NOT_FOUND",
|
|
1178
|
+
message: "Conversation not found",
|
|
1179
|
+
});
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
response.writeHead(200, {
|
|
1183
|
+
"Content-Type": "text/event-stream",
|
|
1184
|
+
"Cache-Control": "no-cache",
|
|
1185
|
+
Connection: "keep-alive",
|
|
1186
|
+
});
|
|
1187
|
+
const stream = conversationEventStreams.get(conversationId);
|
|
1188
|
+
if (!stream) {
|
|
1189
|
+
// No active run — close immediately
|
|
1190
|
+
response.write("event: stream:end\ndata: {}\n\n");
|
|
1191
|
+
response.end();
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
// Replay buffered events
|
|
1195
|
+
for (const bufferedEvent of stream.buffer) {
|
|
1196
|
+
try {
|
|
1197
|
+
response.write(formatSseEvent(bufferedEvent));
|
|
1198
|
+
} catch {
|
|
1199
|
+
response.end();
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (stream.finished) {
|
|
1204
|
+
response.write("event: stream:end\ndata: {}\n\n");
|
|
1205
|
+
response.end();
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
// Subscribe to live events
|
|
1209
|
+
stream.subscribers.add(response);
|
|
1210
|
+
request.on("close", () => {
|
|
1211
|
+
stream.subscribers.delete(response);
|
|
1212
|
+
});
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1048
1216
|
const conversationPathMatch = pathname.match(/^\/api\/conversations\/([^/]+)$/);
|
|
1049
1217
|
if (conversationPathMatch) {
|
|
1050
1218
|
const conversationId = decodeURIComponent(conversationPathMatch[1] ?? "");
|
|
@@ -1057,7 +1225,35 @@ export const createRequestHandler = async (options?: {
|
|
|
1057
1225
|
return;
|
|
1058
1226
|
}
|
|
1059
1227
|
if (request.method === "GET") {
|
|
1060
|
-
|
|
1228
|
+
const storedPending = Array.isArray(conversation.pendingApprovals)
|
|
1229
|
+
? conversation.pendingApprovals
|
|
1230
|
+
: [];
|
|
1231
|
+
const livePending = Array.from(pendingApprovals.entries())
|
|
1232
|
+
.filter(
|
|
1233
|
+
([, pending]) =>
|
|
1234
|
+
pending.ownerId === ownerId && pending.conversationId === conversationId,
|
|
1235
|
+
)
|
|
1236
|
+
.map(([approvalId, pending]) => ({
|
|
1237
|
+
approvalId,
|
|
1238
|
+
runId: pending.runId,
|
|
1239
|
+
tool: pending.tool,
|
|
1240
|
+
input: pending.input,
|
|
1241
|
+
}));
|
|
1242
|
+
const mergedPendingById = new Map<string, (typeof livePending)[number]>();
|
|
1243
|
+
for (const approval of storedPending) {
|
|
1244
|
+
if (approval && typeof approval.approvalId === "string") {
|
|
1245
|
+
mergedPendingById.set(approval.approvalId, approval);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
for (const approval of livePending) {
|
|
1249
|
+
mergedPendingById.set(approval.approvalId, approval);
|
|
1250
|
+
}
|
|
1251
|
+
writeJson(response, 200, {
|
|
1252
|
+
conversation: {
|
|
1253
|
+
...conversation,
|
|
1254
|
+
pendingApprovals: Array.from(mergedPendingById.values()),
|
|
1255
|
+
},
|
|
1256
|
+
});
|
|
1061
1257
|
return;
|
|
1062
1258
|
}
|
|
1063
1259
|
if (request.method === "PATCH") {
|
|
@@ -1114,6 +1310,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1114
1310
|
"Cache-Control": "no-cache",
|
|
1115
1311
|
Connection: "keep-alive",
|
|
1116
1312
|
});
|
|
1313
|
+
const historyMessages = [...conversation.messages];
|
|
1117
1314
|
let latestRunId = conversation.runtimeRunId ?? "";
|
|
1118
1315
|
let assistantResponse = "";
|
|
1119
1316
|
const toolTimeline: string[] = [];
|
|
@@ -1121,6 +1318,48 @@ export const createRequestHandler = async (options?: {
|
|
|
1121
1318
|
let currentText = "";
|
|
1122
1319
|
let currentTools: string[] = [];
|
|
1123
1320
|
try {
|
|
1321
|
+
// Persist the user turn immediately so refreshing mid-run keeps chat context.
|
|
1322
|
+
conversation.messages = [...historyMessages, { role: "user", content: messageText }];
|
|
1323
|
+
conversation.updatedAt = Date.now();
|
|
1324
|
+
await conversationStore.update(conversation);
|
|
1325
|
+
|
|
1326
|
+
const persistDraftAssistantTurn = async (): Promise<void> => {
|
|
1327
|
+
const draftSections: Array<{ type: "text" | "tools"; content: string | string[] }> = [
|
|
1328
|
+
...sections.map((section) => ({
|
|
1329
|
+
type: section.type,
|
|
1330
|
+
content: Array.isArray(section.content) ? [...section.content] : section.content,
|
|
1331
|
+
})),
|
|
1332
|
+
];
|
|
1333
|
+
if (currentTools.length > 0) {
|
|
1334
|
+
draftSections.push({ type: "tools", content: [...currentTools] });
|
|
1335
|
+
}
|
|
1336
|
+
if (currentText.length > 0) {
|
|
1337
|
+
draftSections.push({ type: "text", content: currentText });
|
|
1338
|
+
}
|
|
1339
|
+
const hasDraftContent =
|
|
1340
|
+
assistantResponse.length > 0 || toolTimeline.length > 0 || draftSections.length > 0;
|
|
1341
|
+
if (!hasDraftContent) {
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
conversation.messages = [
|
|
1345
|
+
...historyMessages,
|
|
1346
|
+
{ role: "user", content: messageText },
|
|
1347
|
+
{
|
|
1348
|
+
role: "assistant",
|
|
1349
|
+
content: assistantResponse,
|
|
1350
|
+
metadata:
|
|
1351
|
+
toolTimeline.length > 0 || draftSections.length > 0
|
|
1352
|
+
? ({
|
|
1353
|
+
toolActivity: [...toolTimeline],
|
|
1354
|
+
sections: draftSections.length > 0 ? draftSections : undefined,
|
|
1355
|
+
} as Message["metadata"])
|
|
1356
|
+
: undefined,
|
|
1357
|
+
},
|
|
1358
|
+
];
|
|
1359
|
+
conversation.updatedAt = Date.now();
|
|
1360
|
+
await conversationStore.update(conversation);
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1124
1363
|
const recallCorpus = (await conversationStore.list(ownerId))
|
|
1125
1364
|
.filter((item) => item.conversationId !== conversationId)
|
|
1126
1365
|
.slice(0, 20)
|
|
@@ -1143,10 +1382,12 @@ export const createRequestHandler = async (options?: {
|
|
|
1143
1382
|
__conversationRecallCorpus: recallCorpus,
|
|
1144
1383
|
__activeConversationId: conversationId,
|
|
1145
1384
|
},
|
|
1146
|
-
messages:
|
|
1385
|
+
messages: historyMessages,
|
|
1147
1386
|
})) {
|
|
1148
1387
|
if (event.type === "run:started") {
|
|
1149
1388
|
latestRunId = event.runId;
|
|
1389
|
+
runOwners.set(event.runId, ownerId);
|
|
1390
|
+
runConversations.set(event.runId, conversationId);
|
|
1150
1391
|
}
|
|
1151
1392
|
if (event.type === "model:chunk") {
|
|
1152
1393
|
// If we have tools accumulated and text starts again, push tools as a section
|
|
@@ -1181,16 +1422,19 @@ export const createRequestHandler = async (options?: {
|
|
|
1181
1422
|
const toolText = `- approval required \`${event.tool}\``;
|
|
1182
1423
|
toolTimeline.push(toolText);
|
|
1183
1424
|
currentTools.push(toolText);
|
|
1425
|
+
await persistDraftAssistantTurn();
|
|
1184
1426
|
}
|
|
1185
1427
|
if (event.type === "tool:approval:granted") {
|
|
1186
1428
|
const toolText = `- approval granted (${event.approvalId})`;
|
|
1187
1429
|
toolTimeline.push(toolText);
|
|
1188
1430
|
currentTools.push(toolText);
|
|
1431
|
+
await persistDraftAssistantTurn();
|
|
1189
1432
|
}
|
|
1190
1433
|
if (event.type === "tool:approval:denied") {
|
|
1191
1434
|
const toolText = `- approval denied (${event.approvalId})`;
|
|
1192
1435
|
toolTimeline.push(toolText);
|
|
1193
1436
|
currentTools.push(toolText);
|
|
1437
|
+
await persistDraftAssistantTurn();
|
|
1194
1438
|
}
|
|
1195
1439
|
if (
|
|
1196
1440
|
event.type === "run:completed" &&
|
|
@@ -1200,7 +1444,13 @@ export const createRequestHandler = async (options?: {
|
|
|
1200
1444
|
assistantResponse = event.result.response;
|
|
1201
1445
|
}
|
|
1202
1446
|
await telemetry.emit(event);
|
|
1203
|
-
|
|
1447
|
+
broadcastEvent(conversationId, event);
|
|
1448
|
+
try {
|
|
1449
|
+
response.write(formatSseEvent(event));
|
|
1450
|
+
} catch {
|
|
1451
|
+
// Client disconnected (e.g. browser refresh). Continue processing
|
|
1452
|
+
// so the run completes and conversation is persisted.
|
|
1453
|
+
}
|
|
1204
1454
|
}
|
|
1205
1455
|
// Finalize sections
|
|
1206
1456
|
if (currentTools.length > 0) {
|
|
@@ -1210,7 +1460,7 @@ export const createRequestHandler = async (options?: {
|
|
|
1210
1460
|
sections.push({ type: "text", content: currentText });
|
|
1211
1461
|
}
|
|
1212
1462
|
conversation.messages = [
|
|
1213
|
-
...
|
|
1463
|
+
...historyMessages,
|
|
1214
1464
|
{ role: "user", content: messageText },
|
|
1215
1465
|
{
|
|
1216
1466
|
role: "assistant",
|
|
@@ -1225,21 +1475,62 @@ export const createRequestHandler = async (options?: {
|
|
|
1225
1475
|
},
|
|
1226
1476
|
];
|
|
1227
1477
|
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
1478
|
+
conversation.pendingApprovals = [];
|
|
1228
1479
|
conversation.updatedAt = Date.now();
|
|
1229
1480
|
await conversationStore.update(conversation);
|
|
1230
1481
|
} catch (error) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1482
|
+
try {
|
|
1483
|
+
response.write(
|
|
1484
|
+
formatSseEvent({
|
|
1485
|
+
type: "run:error",
|
|
1486
|
+
runId: latestRunId || "run_unknown",
|
|
1487
|
+
error: {
|
|
1488
|
+
code: "RUN_ERROR",
|
|
1489
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
1490
|
+
},
|
|
1491
|
+
}),
|
|
1492
|
+
);
|
|
1493
|
+
} catch {
|
|
1494
|
+
// Client already disconnected; persist whatever we accumulated.
|
|
1495
|
+
const fallbackSections = [...sections];
|
|
1496
|
+
if (currentTools.length > 0) {
|
|
1497
|
+
fallbackSections.push({ type: "tools", content: [...currentTools] });
|
|
1498
|
+
}
|
|
1499
|
+
if (currentText.length > 0) {
|
|
1500
|
+
fallbackSections.push({ type: "text", content: currentText });
|
|
1501
|
+
}
|
|
1502
|
+
if (assistantResponse.length > 0 || toolTimeline.length > 0 || fallbackSections.length > 0) {
|
|
1503
|
+
conversation.messages = [
|
|
1504
|
+
...historyMessages,
|
|
1505
|
+
{ role: "user", content: messageText },
|
|
1506
|
+
{
|
|
1507
|
+
role: "assistant",
|
|
1508
|
+
content: assistantResponse,
|
|
1509
|
+
metadata:
|
|
1510
|
+
toolTimeline.length > 0 || fallbackSections.length > 0
|
|
1511
|
+
? ({
|
|
1512
|
+
toolActivity: [...toolTimeline],
|
|
1513
|
+
sections: fallbackSections.length > 0 ? fallbackSections : undefined,
|
|
1514
|
+
} as Message["metadata"])
|
|
1515
|
+
: undefined,
|
|
1516
|
+
},
|
|
1517
|
+
];
|
|
1518
|
+
conversation.updatedAt = Date.now();
|
|
1519
|
+
await conversationStore.update(conversation);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1241
1522
|
} finally {
|
|
1242
|
-
|
|
1523
|
+
finishConversationStream(conversationId);
|
|
1524
|
+
await persistConversationPendingApprovals(conversationId);
|
|
1525
|
+
if (latestRunId) {
|
|
1526
|
+
runOwners.delete(latestRunId);
|
|
1527
|
+
runConversations.delete(latestRunId);
|
|
1528
|
+
}
|
|
1529
|
+
try {
|
|
1530
|
+
response.end();
|
|
1531
|
+
} catch {
|
|
1532
|
+
// Already closed.
|
|
1533
|
+
}
|
|
1243
1534
|
}
|
|
1244
1535
|
return;
|
|
1245
1536
|
}
|
|
@@ -1851,25 +2142,14 @@ export const mcpList = async (workingDir: string): Promise<void> => {
|
|
|
1851
2142
|
const mcp = config?.mcp ?? [];
|
|
1852
2143
|
if (mcp.length === 0) {
|
|
1853
2144
|
process.stdout.write("No MCP servers configured.\n");
|
|
1854
|
-
if (config?.scripts) {
|
|
1855
|
-
process.stdout.write(
|
|
1856
|
-
`Script policy: mode=${config.scripts.mode ?? "all"} include=${config.scripts.include?.length ?? 0} exclude=${config.scripts.exclude?.length ?? 0}\n`,
|
|
1857
|
-
);
|
|
1858
|
-
}
|
|
1859
2145
|
return;
|
|
1860
2146
|
}
|
|
1861
2147
|
process.stdout.write("Configured MCP servers:\n");
|
|
1862
2148
|
for (const entry of mcp) {
|
|
1863
2149
|
const auth =
|
|
1864
2150
|
entry.auth?.type === "bearer" ? `auth=bearer:${entry.auth.tokenEnv}` : "auth=none";
|
|
1865
|
-
const mode = entry.tools?.mode ?? "all";
|
|
1866
|
-
process.stdout.write(
|
|
1867
|
-
`- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth}, mode=${mode})\n`,
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
if (config?.scripts) {
|
|
1871
2151
|
process.stdout.write(
|
|
1872
|
-
|
|
2152
|
+
`- ${entry.name ?? entry.url} (remote: ${entry.url}, ${auth})\n`,
|
|
1873
2153
|
);
|
|
1874
2154
|
}
|
|
1875
2155
|
};
|
|
@@ -2013,40 +2293,26 @@ export const mcpToolsSelect = async (
|
|
|
2013
2293
|
selected.length === discovered.length
|
|
2014
2294
|
? [`${serverName}/*`]
|
|
2015
2295
|
: selected.sort();
|
|
2016
|
-
|
|
2017
|
-
const mcp = [...(config.mcp ?? [])];
|
|
2018
|
-
const existing = mcp[index];
|
|
2019
|
-
mcp[index] = {
|
|
2020
|
-
...existing,
|
|
2021
|
-
tools: {
|
|
2022
|
-
...(existing.tools ?? {}),
|
|
2023
|
-
mode: "allowlist",
|
|
2024
|
-
include: includePatterns,
|
|
2025
|
-
},
|
|
2026
|
-
};
|
|
2027
|
-
await writeConfigFile(workingDir, { ...config, mcp });
|
|
2028
|
-
process.stdout.write(
|
|
2029
|
-
`Updated ${serverName} to allowlist ${includePatterns.join(", ")} in poncho.config.js.\n`,
|
|
2030
|
-
);
|
|
2296
|
+
process.stdout.write(`Selected MCP tools: ${includePatterns.join(", ")}\n`);
|
|
2031
2297
|
process.stdout.write(
|
|
2032
|
-
"\nRequired next step: add MCP intent in AGENT.md or SKILL.md. Without this, these MCP tools will not be registered for the model.\n",
|
|
2298
|
+
"\nRequired next step: add MCP intent in AGENT.md or SKILL.md allowed-tools. Without this, these MCP tools will not be registered for the model.\n",
|
|
2033
2299
|
);
|
|
2034
2300
|
process.stdout.write(
|
|
2035
2301
|
"\nOption A: AGENT.md (global fallback intent)\n" +
|
|
2036
2302
|
"Paste this into AGENT.md frontmatter:\n" +
|
|
2037
2303
|
"---\n" +
|
|
2038
|
-
"tools:\n" +
|
|
2039
|
-
|
|
2040
|
-
includePatterns.map((tool) => ` - ${tool}`).join("\n") +
|
|
2304
|
+
"allowed-tools:\n" +
|
|
2305
|
+
includePatterns.map((tool) => ` - mcp:${tool}`).join("\n") +
|
|
2041
2306
|
"\n---\n",
|
|
2042
2307
|
);
|
|
2043
2308
|
process.stdout.write(
|
|
2044
2309
|
"\nOption B: SKILL.md (only when that skill is activated)\n" +
|
|
2045
2310
|
"Paste this into SKILL.md frontmatter:\n" +
|
|
2046
2311
|
"---\n" +
|
|
2047
|
-
"tools:\n" +
|
|
2048
|
-
|
|
2049
|
-
|
|
2312
|
+
"allowed-tools:\n" +
|
|
2313
|
+
includePatterns.map((tool) => ` - mcp:${tool}`).join("\n") +
|
|
2314
|
+
"\napproval-required:\n" +
|
|
2315
|
+
includePatterns.map((tool) => ` - mcp:${tool}`).join("\n") +
|
|
2050
2316
|
"\n---\n",
|
|
2051
2317
|
);
|
|
2052
2318
|
};
|
|
@@ -2220,7 +2486,7 @@ export const buildCli = (): Command => {
|
|
|
2220
2486
|
mcpToolsCommand
|
|
2221
2487
|
.command("select")
|
|
2222
2488
|
.argument("<name>", "server name")
|
|
2223
|
-
.description("Select MCP tools and
|
|
2489
|
+
.description("Select MCP tools and print frontmatter allowed-tools entries")
|
|
2224
2490
|
.option("--all", "select all discovered tools", false)
|
|
2225
2491
|
.option("--tools <csv>", "comma-separated discovered tool names")
|
|
2226
2492
|
.action(
|