@poncho-ai/cli 0.8.2 → 0.8.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 +6 -0
- package/dist/chunk-42U2R3FH.js +5752 -0
- package/dist/chunk-4UDNQZ3G.js +5752 -0
- package/dist/chunk-5NHWU4QU.js +5752 -0
- package/dist/chunk-6CDE6R7D.js +5752 -0
- package/dist/chunk-74HD63WM.js +5819 -0
- package/dist/chunk-7TRWWFGI.js +5752 -0
- package/dist/chunk-G67AWHXV.js +5752 -0
- package/dist/chunk-L65TFTEI.js +5752 -0
- package/dist/chunk-O5NLOW2I.js +5752 -0
- package/dist/chunk-OGTT4YJG.js +5752 -0
- package/dist/chunk-Q3WHF2FP.js +5752 -0
- package/dist/chunk-RN7FDRZH.js +5752 -0
- package/dist/chunk-ZCLLCLRR.js +5752 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/run-interactive-ink-2CVZHZLL.js +535 -0
- package/dist/run-interactive-ink-3TNAVPQ7.js +534 -0
- package/dist/run-interactive-ink-7EB3ZX6P.js +535 -0
- package/dist/run-interactive-ink-7OSESHKH.js +535 -0
- package/dist/run-interactive-ink-DORF57NC.js +535 -0
- package/dist/run-interactive-ink-EOW4MLEH.js +535 -0
- package/dist/run-interactive-ink-HMVUIZRO.js +533 -0
- package/dist/run-interactive-ink-O5AV46ZE.js +535 -0
- package/dist/run-interactive-ink-OC57VVOY.js +535 -0
- package/dist/run-interactive-ink-PTUDJKT5.js +535 -0
- package/dist/run-interactive-ink-PYQFHCNW.js +537 -0
- package/dist/run-interactive-ink-QXIIUBIC.js +534 -0
- package/dist/run-interactive-ink-XEK5ZPSU.js +535 -0
- package/package.json +1 -1
- package/src/index.ts +71 -2
- package/src/run-interactive-ink.ts +18 -4
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeFirstRunIntro,
|
|
3
|
+
inferConversationTitle,
|
|
4
|
+
resolveHarnessEnvironment
|
|
5
|
+
} from "./chunk-74HD63WM.js";
|
|
6
|
+
|
|
7
|
+
// src/run-interactive-ink.ts
|
|
8
|
+
import * as readline from "readline";
|
|
9
|
+
import { stdout } from "process";
|
|
10
|
+
import {
|
|
11
|
+
parseAgentFile
|
|
12
|
+
} from "@poncho-ai/harness";
|
|
13
|
+
var C = {
|
|
14
|
+
reset: "\x1B[0m",
|
|
15
|
+
bold: "\x1B[1m",
|
|
16
|
+
dim: "\x1B[2m",
|
|
17
|
+
cyan: "\x1B[36m",
|
|
18
|
+
green: "\x1B[32m",
|
|
19
|
+
yellow: "\x1B[33m",
|
|
20
|
+
red: "\x1B[31m",
|
|
21
|
+
gray: "\x1B[90m",
|
|
22
|
+
magenta: "\x1B[35m"
|
|
23
|
+
};
|
|
24
|
+
var green = (s) => `${C.green}${s}${C.reset}`;
|
|
25
|
+
var yellow = (s) => `${C.yellow}${s}${C.reset}`;
|
|
26
|
+
var red = (s) => `${C.red}${s}${C.reset}`;
|
|
27
|
+
var gray = (s) => `${C.gray}${s}${C.reset}`;
|
|
28
|
+
var magenta = (s) => `${C.magenta}${s}${C.reset}`;
|
|
29
|
+
var FAUX_TOOL_LOG_PATTERN = /Tool Used:|Tool Result:|\blist_skills\b|\bcreate_skill\b|\bedit_skill\b/i;
|
|
30
|
+
var formatDuration = (ms) => ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
31
|
+
var stringifyValue = (v) => {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.stringify(v);
|
|
34
|
+
} catch {
|
|
35
|
+
return String(v);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var truncate = (v, max) => v.length <= max ? v : `${v.slice(0, Math.max(0, max - 3))}...`;
|
|
39
|
+
var compactPreview = (v, max = 120) => truncate(stringifyValue(v).replace(/\s+/g, " "), max);
|
|
40
|
+
var loadMetadata = async (workingDir) => {
|
|
41
|
+
let agentName = "agent";
|
|
42
|
+
let model = "unknown";
|
|
43
|
+
let provider = "unknown";
|
|
44
|
+
try {
|
|
45
|
+
const parsed = await parseAgentFile(workingDir);
|
|
46
|
+
agentName = parsed.frontmatter.name ?? agentName;
|
|
47
|
+
model = parsed.frontmatter.model?.name ?? model;
|
|
48
|
+
provider = parsed.frontmatter.model?.provider ?? provider;
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
agentName,
|
|
53
|
+
model,
|
|
54
|
+
provider,
|
|
55
|
+
workingDir,
|
|
56
|
+
environment: resolveHarnessEnvironment()
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
var ask = (rl, prompt) => new Promise((res) => {
|
|
60
|
+
rl.question(prompt, (answer) => res(answer));
|
|
61
|
+
});
|
|
62
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
var streamTextAsTokens = async (text) => {
|
|
64
|
+
const tokens = text.match(/\S+\s*|\n/g) ?? [text];
|
|
65
|
+
for (const token of tokens) {
|
|
66
|
+
stdout.write(token);
|
|
67
|
+
const trimmed = token.trim();
|
|
68
|
+
const delay = trimmed.length === 0 ? 0 : Math.max(4, Math.min(18, Math.floor(trimmed.length / 2)));
|
|
69
|
+
await sleep(delay);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var OWNER_ID = "local-owner";
|
|
73
|
+
var computeTurn = (messages) => Math.max(1, Math.floor(messages.length / 2) + 1);
|
|
74
|
+
var formatDate = (value) => {
|
|
75
|
+
try {
|
|
76
|
+
return new Date(value).toLocaleString();
|
|
77
|
+
} catch {
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var handleSlash = async (command, state, conversationStore) => {
|
|
82
|
+
const [rawCommand, ...args] = command.trim().split(/\s+/);
|
|
83
|
+
const norm = rawCommand.toLowerCase();
|
|
84
|
+
if (norm === "/help") {
|
|
85
|
+
console.log(
|
|
86
|
+
gray(
|
|
87
|
+
"commands> /help /clear /exit /tools /list /open <id> /new [title] /delete [id] /continue /reset [all]"
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
return { shouldExit: false };
|
|
91
|
+
}
|
|
92
|
+
if (norm === "/clear") {
|
|
93
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
94
|
+
return { shouldExit: false };
|
|
95
|
+
}
|
|
96
|
+
if (norm === "/exit") {
|
|
97
|
+
return { shouldExit: true };
|
|
98
|
+
}
|
|
99
|
+
if (norm === "/list") {
|
|
100
|
+
const conversations = await conversationStore.list(OWNER_ID);
|
|
101
|
+
if (conversations.length === 0) {
|
|
102
|
+
console.log(gray("conversations> none"));
|
|
103
|
+
return { shouldExit: false };
|
|
104
|
+
}
|
|
105
|
+
console.log(gray("conversations>"));
|
|
106
|
+
for (const conversation of conversations) {
|
|
107
|
+
const activeMarker = state.activeConversationId === conversation.conversationId ? "*" : " ";
|
|
108
|
+
const maxTitleLen = 40;
|
|
109
|
+
const title = conversation.title.length > maxTitleLen ? conversation.title.slice(0, maxTitleLen - 1) + "\u2026" : conversation.title;
|
|
110
|
+
console.log(
|
|
111
|
+
gray(
|
|
112
|
+
`${activeMarker} ${conversation.conversationId} | ${title} | ${formatDate(conversation.updatedAt)}`
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return { shouldExit: false };
|
|
117
|
+
}
|
|
118
|
+
if (norm === "/open") {
|
|
119
|
+
const conversationId = args[0];
|
|
120
|
+
if (!conversationId) {
|
|
121
|
+
console.log(yellow("usage> /open <conversationId>"));
|
|
122
|
+
return { shouldExit: false };
|
|
123
|
+
}
|
|
124
|
+
const conversation = await conversationStore.get(conversationId);
|
|
125
|
+
if (!conversation) {
|
|
126
|
+
console.log(yellow(`conversations> not found: ${conversationId}`));
|
|
127
|
+
return { shouldExit: false };
|
|
128
|
+
}
|
|
129
|
+
state.activeConversationId = conversation.conversationId;
|
|
130
|
+
state.messages = [...conversation.messages];
|
|
131
|
+
state.turn = computeTurn(state.messages);
|
|
132
|
+
console.log(gray(`conversations> opened ${conversation.conversationId}`));
|
|
133
|
+
return { shouldExit: false };
|
|
134
|
+
}
|
|
135
|
+
if (norm === "/new") {
|
|
136
|
+
const title = args.join(" ").trim();
|
|
137
|
+
const conversation = await conversationStore.create(OWNER_ID, title || void 0);
|
|
138
|
+
state.activeConversationId = conversation.conversationId;
|
|
139
|
+
state.messages = [];
|
|
140
|
+
state.turn = 1;
|
|
141
|
+
console.log(gray(`conversations> new ${conversation.conversationId}`));
|
|
142
|
+
return { shouldExit: false };
|
|
143
|
+
}
|
|
144
|
+
if (norm === "/delete") {
|
|
145
|
+
const targetConversationId = args[0] ?? state.activeConversationId ?? "";
|
|
146
|
+
if (!targetConversationId) {
|
|
147
|
+
console.log(yellow("usage> /delete <conversationId>"));
|
|
148
|
+
return { shouldExit: false };
|
|
149
|
+
}
|
|
150
|
+
const removed = await conversationStore.delete(targetConversationId);
|
|
151
|
+
if (!removed) {
|
|
152
|
+
console.log(yellow(`conversations> not found: ${targetConversationId}`));
|
|
153
|
+
return { shouldExit: false };
|
|
154
|
+
}
|
|
155
|
+
if (state.activeConversationId === targetConversationId) {
|
|
156
|
+
state.activeConversationId = null;
|
|
157
|
+
state.messages = [];
|
|
158
|
+
state.turn = 1;
|
|
159
|
+
}
|
|
160
|
+
console.log(gray(`conversations> deleted ${targetConversationId}`));
|
|
161
|
+
return { shouldExit: false };
|
|
162
|
+
}
|
|
163
|
+
if (norm === "/continue") {
|
|
164
|
+
const conversations = await conversationStore.list(OWNER_ID);
|
|
165
|
+
const latest = conversations[0];
|
|
166
|
+
if (!latest) {
|
|
167
|
+
console.log(yellow("conversations> no conversations to continue"));
|
|
168
|
+
return { shouldExit: false };
|
|
169
|
+
}
|
|
170
|
+
state.activeConversationId = latest.conversationId;
|
|
171
|
+
state.messages = [...latest.messages];
|
|
172
|
+
state.turn = computeTurn(state.messages);
|
|
173
|
+
console.log(gray(`conversations> continued ${latest.conversationId}`));
|
|
174
|
+
return { shouldExit: false };
|
|
175
|
+
}
|
|
176
|
+
if (norm === "/reset") {
|
|
177
|
+
if (args[0]?.toLowerCase() === "all") {
|
|
178
|
+
const conversations = await conversationStore.list(OWNER_ID);
|
|
179
|
+
for (const conversation2 of conversations) {
|
|
180
|
+
await conversationStore.delete(conversation2.conversationId);
|
|
181
|
+
}
|
|
182
|
+
state.activeConversationId = null;
|
|
183
|
+
state.messages = [];
|
|
184
|
+
state.turn = 1;
|
|
185
|
+
console.log(gray("conversations> reset all"));
|
|
186
|
+
return { shouldExit: false };
|
|
187
|
+
}
|
|
188
|
+
if (!state.activeConversationId) {
|
|
189
|
+
state.messages = [];
|
|
190
|
+
state.turn = 1;
|
|
191
|
+
console.log(gray("conversations> current session reset"));
|
|
192
|
+
return { shouldExit: false };
|
|
193
|
+
}
|
|
194
|
+
const conversation = await conversationStore.get(state.activeConversationId);
|
|
195
|
+
if (!conversation) {
|
|
196
|
+
state.activeConversationId = null;
|
|
197
|
+
state.messages = [];
|
|
198
|
+
state.turn = 1;
|
|
199
|
+
console.log(yellow("conversations> active conversation no longer exists"));
|
|
200
|
+
return { shouldExit: false };
|
|
201
|
+
}
|
|
202
|
+
await conversationStore.update({
|
|
203
|
+
...conversation,
|
|
204
|
+
messages: []
|
|
205
|
+
});
|
|
206
|
+
state.messages = [];
|
|
207
|
+
state.turn = 1;
|
|
208
|
+
console.log(gray(`conversations> reset ${conversation.conversationId}`));
|
|
209
|
+
return { shouldExit: false };
|
|
210
|
+
}
|
|
211
|
+
console.log(yellow(`Unknown command: ${command}`));
|
|
212
|
+
return { shouldExit: false };
|
|
213
|
+
};
|
|
214
|
+
var runInteractiveInk = async ({
|
|
215
|
+
harness,
|
|
216
|
+
params,
|
|
217
|
+
workingDir,
|
|
218
|
+
config,
|
|
219
|
+
conversationStore,
|
|
220
|
+
onSetApprovalCallback
|
|
221
|
+
}) => {
|
|
222
|
+
const metadata = await loadMetadata(workingDir);
|
|
223
|
+
const rl = readline.createInterface({
|
|
224
|
+
input: process.stdin,
|
|
225
|
+
output: process.stdout,
|
|
226
|
+
terminal: true
|
|
227
|
+
});
|
|
228
|
+
if (onSetApprovalCallback) {
|
|
229
|
+
onSetApprovalCallback((req) => {
|
|
230
|
+
process.stdout.write("\n");
|
|
231
|
+
const preview = compactPreview(req.input, 100);
|
|
232
|
+
rl.question(
|
|
233
|
+
`${C.yellow}${C.bold}Tool "${req.tool}" requires approval${C.reset}
|
|
234
|
+
${C.gray}input: ${preview}${C.reset}
|
|
235
|
+
${C.yellow}approve? (y/n): ${C.reset}`,
|
|
236
|
+
(answer) => {
|
|
237
|
+
const approved = answer.trim().toLowerCase() === "y";
|
|
238
|
+
console.log(
|
|
239
|
+
approved ? green(` approved ${req.tool}`) : magenta(` denied ${req.tool}`)
|
|
240
|
+
);
|
|
241
|
+
req.resolve(approved);
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const mascot = [
|
|
247
|
+
`${C.yellow} \u28C0\u28C0\u28C0\u28C0\u28C0\u28C0${C.reset}`,
|
|
248
|
+
`${C.yellow} \u2820\u283E\u281B\u281B\u281B\u281B\u281B\u281B\u2837\u2804${C.reset}`,
|
|
249
|
+
`${C.gray} \u2847${C.cyan} \u2836 \u2836 ${C.gray}\u28B8${C.reset}`,
|
|
250
|
+
`${C.gray} \u2823\u2840${C.cyan} \u2812\u281A${C.gray}\u2880\u281C${C.reset}`,
|
|
251
|
+
`${C.yellow} \u28FF\u28FF\u28FF\u28FF\u28FF\u28FF${C.reset}`,
|
|
252
|
+
`${C.gray} \u2803 \u2818${C.reset}`
|
|
253
|
+
];
|
|
254
|
+
console.log("");
|
|
255
|
+
for (const line of mascot) {
|
|
256
|
+
console.log(line);
|
|
257
|
+
}
|
|
258
|
+
console.log(`${C.bold}${C.cyan} poncho${C.reset}`);
|
|
259
|
+
console.log("");
|
|
260
|
+
console.log(
|
|
261
|
+
gray(
|
|
262
|
+
` ${metadata.agentName} \xB7 ${metadata.provider}/${metadata.model} \xB7 ${metadata.environment}`
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
console.log(gray(' Type "exit" to quit, "/help" for commands'));
|
|
266
|
+
console.log(gray(" Press Ctrl+C during a run to stop streaming output."));
|
|
267
|
+
console.log(
|
|
268
|
+
gray(" Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n")
|
|
269
|
+
);
|
|
270
|
+
const intro = await consumeFirstRunIntro(workingDir, {
|
|
271
|
+
agentName: metadata.agentName,
|
|
272
|
+
provider: metadata.provider,
|
|
273
|
+
model: metadata.model,
|
|
274
|
+
config
|
|
275
|
+
});
|
|
276
|
+
if (intro) {
|
|
277
|
+
console.log(green("assistant>"));
|
|
278
|
+
await streamTextAsTokens(intro);
|
|
279
|
+
stdout.write("\n\n");
|
|
280
|
+
}
|
|
281
|
+
let messages = intro ? [{ role: "assistant", content: intro }] : [];
|
|
282
|
+
let turn = 1;
|
|
283
|
+
let activeConversationId = null;
|
|
284
|
+
let showToolPayloads = false;
|
|
285
|
+
let activeRunAbortController = null;
|
|
286
|
+
rl.on("SIGINT", () => {
|
|
287
|
+
if (activeRunAbortController && !activeRunAbortController.signal.aborted) {
|
|
288
|
+
activeRunAbortController.abort();
|
|
289
|
+
process.stdout.write("\n");
|
|
290
|
+
console.log(gray("stop> cancelling current run..."));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
rl.close();
|
|
294
|
+
});
|
|
295
|
+
const prompt = `${C.cyan}you> ${C.reset}`;
|
|
296
|
+
while (true) {
|
|
297
|
+
let task;
|
|
298
|
+
try {
|
|
299
|
+
task = await ask(rl, prompt);
|
|
300
|
+
} catch {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
const trimmed = task.trim();
|
|
304
|
+
if (!trimmed) continue;
|
|
305
|
+
if (trimmed.toLowerCase() === "exit") break;
|
|
306
|
+
if (trimmed.startsWith("/")) {
|
|
307
|
+
if (trimmed.toLowerCase() === "/exit") break;
|
|
308
|
+
if (trimmed.toLowerCase() === "/tools") {
|
|
309
|
+
showToolPayloads = !showToolPayloads;
|
|
310
|
+
console.log(gray(`tool payloads: ${showToolPayloads ? "on" : "off"}`));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const interactiveState = {
|
|
314
|
+
messages,
|
|
315
|
+
turn,
|
|
316
|
+
activeConversationId
|
|
317
|
+
};
|
|
318
|
+
const slashResult = await handleSlash(
|
|
319
|
+
trimmed,
|
|
320
|
+
interactiveState,
|
|
321
|
+
conversationStore
|
|
322
|
+
);
|
|
323
|
+
if (slashResult.shouldExit) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
messages = interactiveState.messages;
|
|
327
|
+
turn = interactiveState.turn;
|
|
328
|
+
activeConversationId = interactiveState.activeConversationId;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
console.log(gray(`
|
|
332
|
+
--- turn ${turn} ---`));
|
|
333
|
+
process.stdout.write(gray("thinking..."));
|
|
334
|
+
let thinkingCleared = false;
|
|
335
|
+
const clearThinking = () => {
|
|
336
|
+
if (thinkingCleared) return;
|
|
337
|
+
thinkingCleared = true;
|
|
338
|
+
readline.clearLine(process.stdout, 0);
|
|
339
|
+
readline.cursorTo(process.stdout, 0);
|
|
340
|
+
};
|
|
341
|
+
let responseText = "";
|
|
342
|
+
let streamedText = "";
|
|
343
|
+
let committedText = false;
|
|
344
|
+
let sawChunk = false;
|
|
345
|
+
let toolEvents = 0;
|
|
346
|
+
const toolTimeline = [];
|
|
347
|
+
const sections = [];
|
|
348
|
+
let currentText = "";
|
|
349
|
+
let currentTools = [];
|
|
350
|
+
let runFailed = false;
|
|
351
|
+
let runCancelled = false;
|
|
352
|
+
let usage;
|
|
353
|
+
let latestRunId = "";
|
|
354
|
+
const startedAt = Date.now();
|
|
355
|
+
activeRunAbortController = new AbortController();
|
|
356
|
+
try {
|
|
357
|
+
for await (const event of harness.run({
|
|
358
|
+
task: trimmed,
|
|
359
|
+
parameters: params,
|
|
360
|
+
messages,
|
|
361
|
+
abortSignal: activeRunAbortController.signal
|
|
362
|
+
})) {
|
|
363
|
+
if (event.type === "run:started") {
|
|
364
|
+
latestRunId = event.runId;
|
|
365
|
+
}
|
|
366
|
+
if (event.type === "model:chunk") {
|
|
367
|
+
sawChunk = true;
|
|
368
|
+
if (currentTools.length > 0) {
|
|
369
|
+
sections.push({ type: "tools", content: currentTools });
|
|
370
|
+
currentTools = [];
|
|
371
|
+
}
|
|
372
|
+
responseText += event.content;
|
|
373
|
+
streamedText += event.content;
|
|
374
|
+
currentText += event.content;
|
|
375
|
+
if (!thinkingCleared) {
|
|
376
|
+
clearThinking();
|
|
377
|
+
process.stdout.write(`${C.green}assistant> ${C.reset}`);
|
|
378
|
+
}
|
|
379
|
+
process.stdout.write(event.content);
|
|
380
|
+
} else if (event.type === "tool:started" || event.type === "tool:completed" || event.type === "tool:error" || event.type === "tool:approval:required" || event.type === "tool:approval:granted" || event.type === "tool:approval:denied") {
|
|
381
|
+
if (streamedText.length > 0) {
|
|
382
|
+
committedText = true;
|
|
383
|
+
streamedText = "";
|
|
384
|
+
process.stdout.write("\n");
|
|
385
|
+
}
|
|
386
|
+
clearThinking();
|
|
387
|
+
if (event.type === "tool:started") {
|
|
388
|
+
if (currentText.length > 0) {
|
|
389
|
+
sections.push({ type: "text", content: currentText });
|
|
390
|
+
currentText = "";
|
|
391
|
+
}
|
|
392
|
+
const preview = showToolPayloads ? compactPreview(event.input, 400) : compactPreview(event.input, 100);
|
|
393
|
+
console.log(yellow(`tools> start ${event.tool} input=${preview}`));
|
|
394
|
+
const toolText = `- start \`${event.tool}\``;
|
|
395
|
+
toolTimeline.push(toolText);
|
|
396
|
+
currentTools.push(toolText);
|
|
397
|
+
toolEvents += 1;
|
|
398
|
+
} else if (event.type === "tool:completed") {
|
|
399
|
+
const preview = showToolPayloads ? compactPreview(event.output, 400) : compactPreview(event.output, 100);
|
|
400
|
+
console.log(
|
|
401
|
+
yellow(
|
|
402
|
+
`tools> done ${event.tool} in ${formatDuration(event.duration)}`
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
if (showToolPayloads) {
|
|
406
|
+
console.log(yellow(`tools> output ${preview}`));
|
|
407
|
+
}
|
|
408
|
+
const toolText = `- done \`${event.tool}\` in ${formatDuration(event.duration)}`;
|
|
409
|
+
toolTimeline.push(toolText);
|
|
410
|
+
currentTools.push(toolText);
|
|
411
|
+
} else if (event.type === "tool:error") {
|
|
412
|
+
console.log(
|
|
413
|
+
red(`tools> error ${event.tool}: ${event.error}`)
|
|
414
|
+
);
|
|
415
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
416
|
+
toolTimeline.push(toolText);
|
|
417
|
+
currentTools.push(toolText);
|
|
418
|
+
} else if (event.type === "tool:approval:required") {
|
|
419
|
+
console.log(
|
|
420
|
+
magenta(`tools> approval required for ${event.tool}`)
|
|
421
|
+
);
|
|
422
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
423
|
+
toolTimeline.push(toolText);
|
|
424
|
+
currentTools.push(toolText);
|
|
425
|
+
} else if (event.type === "tool:approval:granted") {
|
|
426
|
+
console.log(
|
|
427
|
+
gray(`tools> approval granted (${event.approvalId})`)
|
|
428
|
+
);
|
|
429
|
+
const toolText = `- approval granted (${event.approvalId})`;
|
|
430
|
+
toolTimeline.push(toolText);
|
|
431
|
+
currentTools.push(toolText);
|
|
432
|
+
} else if (event.type === "tool:approval:denied") {
|
|
433
|
+
console.log(
|
|
434
|
+
magenta(`tools> approval denied (${event.approvalId})`)
|
|
435
|
+
);
|
|
436
|
+
const toolText = `- approval denied (${event.approvalId})`;
|
|
437
|
+
toolTimeline.push(toolText);
|
|
438
|
+
currentTools.push(toolText);
|
|
439
|
+
}
|
|
440
|
+
} else if (event.type === "run:error") {
|
|
441
|
+
clearThinking();
|
|
442
|
+
runFailed = true;
|
|
443
|
+
console.log(red(`error> ${event.error.message}`));
|
|
444
|
+
} else if (event.type === "run:cancelled") {
|
|
445
|
+
clearThinking();
|
|
446
|
+
runCancelled = true;
|
|
447
|
+
} else if (event.type === "model:response") {
|
|
448
|
+
usage = event.usage;
|
|
449
|
+
} else if (event.type === "run:completed" && !sawChunk) {
|
|
450
|
+
clearThinking();
|
|
451
|
+
responseText = event.result.response ?? "";
|
|
452
|
+
if (responseText.length > 0) {
|
|
453
|
+
process.stdout.write(
|
|
454
|
+
`${C.green}assistant> ${C.reset}${responseText}
|
|
455
|
+
`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch (error) {
|
|
461
|
+
clearThinking();
|
|
462
|
+
if (activeRunAbortController.signal.aborted) {
|
|
463
|
+
runCancelled = true;
|
|
464
|
+
} else {
|
|
465
|
+
runFailed = true;
|
|
466
|
+
console.log(
|
|
467
|
+
red(
|
|
468
|
+
`error> ${error instanceof Error ? error.message : "Unknown error"}`
|
|
469
|
+
)
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
activeRunAbortController = null;
|
|
474
|
+
}
|
|
475
|
+
if (sawChunk && streamedText.length > 0) {
|
|
476
|
+
process.stdout.write("\n");
|
|
477
|
+
} else if (!sawChunk && !runFailed && !runCancelled && responseText.length === 0) {
|
|
478
|
+
clearThinking();
|
|
479
|
+
console.log(green("assistant> (no response)"));
|
|
480
|
+
}
|
|
481
|
+
const fullResponse = responseText || streamedText;
|
|
482
|
+
if (!runFailed && toolEvents === 0 && FAUX_TOOL_LOG_PATTERN.test(fullResponse)) {
|
|
483
|
+
console.log(
|
|
484
|
+
magenta(
|
|
485
|
+
"warning> assistant described tool execution but no real tool events occurred."
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const durationMs = Date.now() - startedAt;
|
|
490
|
+
console.log(
|
|
491
|
+
gray(`meta> ${formatDuration(durationMs)} | tools: ${toolEvents}
|
|
492
|
+
`)
|
|
493
|
+
);
|
|
494
|
+
if (!activeConversationId) {
|
|
495
|
+
const created = await conversationStore.create(
|
|
496
|
+
OWNER_ID,
|
|
497
|
+
inferConversationTitle(trimmed)
|
|
498
|
+
);
|
|
499
|
+
activeConversationId = created.conversationId;
|
|
500
|
+
}
|
|
501
|
+
if (currentTools.length > 0) {
|
|
502
|
+
sections.push({ type: "tools", content: currentTools });
|
|
503
|
+
}
|
|
504
|
+
if (currentText.length > 0) {
|
|
505
|
+
sections.push({ type: "text", content: currentText });
|
|
506
|
+
}
|
|
507
|
+
messages.push({ role: "user", content: trimmed });
|
|
508
|
+
const hasAssistantContent = responseText.length > 0 || toolTimeline.length > 0 || sections.length > 0;
|
|
509
|
+
if (hasAssistantContent) {
|
|
510
|
+
messages.push({
|
|
511
|
+
role: "assistant",
|
|
512
|
+
content: responseText,
|
|
513
|
+
metadata: toolTimeline.length > 0 || sections.length > 0 ? {
|
|
514
|
+
toolActivity: toolTimeline,
|
|
515
|
+
sections: sections.length > 0 ? sections : void 0
|
|
516
|
+
} : void 0
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
turn = computeTurn(messages);
|
|
520
|
+
const conversation = await conversationStore.get(activeConversationId);
|
|
521
|
+
if (conversation) {
|
|
522
|
+
const maybeTitle = conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0) ? inferConversationTitle(trimmed) : conversation.title;
|
|
523
|
+
await conversationStore.update({
|
|
524
|
+
...conversation,
|
|
525
|
+
title: maybeTitle,
|
|
526
|
+
messages: [...messages],
|
|
527
|
+
runtimeRunId: latestRunId || conversation.runtimeRunId
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
rl.close();
|
|
532
|
+
};
|
|
533
|
+
export {
|
|
534
|
+
runInteractiveInk
|
|
535
|
+
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -478,10 +478,14 @@ ${name}/
|
|
|
478
478
|
├── tests/
|
|
479
479
|
│ └── basic.yaml # Test suite
|
|
480
480
|
└── skills/
|
|
481
|
-
|
|
481
|
+
├── starter/
|
|
482
|
+
│ ├── SKILL.md
|
|
483
|
+
│ └── scripts/
|
|
484
|
+
│ └── starter-echo.ts
|
|
485
|
+
└── fetch-page/
|
|
482
486
|
├── SKILL.md
|
|
483
487
|
└── scripts/
|
|
484
|
-
└──
|
|
488
|
+
└── fetch-page.ts
|
|
485
489
|
\`\`\`
|
|
486
490
|
|
|
487
491
|
## Deployment
|
|
@@ -545,6 +549,69 @@ const SKILL_TOOL_TEMPLATE = `export default async function run(input) {
|
|
|
545
549
|
}
|
|
546
550
|
`;
|
|
547
551
|
|
|
552
|
+
const FETCH_PAGE_SKILL_TEMPLATE = `---
|
|
553
|
+
name: fetch-page
|
|
554
|
+
description: Fetch a web page and return its text content
|
|
555
|
+
allowed-tools:
|
|
556
|
+
- ./scripts/fetch-page.ts
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
# Fetch Page
|
|
560
|
+
|
|
561
|
+
Fetches a URL and returns the page body as plain text (HTML tags stripped).
|
|
562
|
+
|
|
563
|
+
## Usage
|
|
564
|
+
|
|
565
|
+
Call \`run_skill_script\` with:
|
|
566
|
+
- **skill**: \`fetch-page\`
|
|
567
|
+
- **script**: \`./scripts/fetch-page.ts\`
|
|
568
|
+
- **input**: \`{ "url": "https://example.com" }\`
|
|
569
|
+
|
|
570
|
+
The script returns \`{ url, status, content }\` where \`content\` is the
|
|
571
|
+
text-only body (capped at ~32 000 chars to stay context-friendly).
|
|
572
|
+
`;
|
|
573
|
+
|
|
574
|
+
const FETCH_PAGE_SCRIPT_TEMPLATE = `export default async function run(input) {
|
|
575
|
+
const url = typeof input?.url === "string" ? input.url.trim() : "";
|
|
576
|
+
if (!url) {
|
|
577
|
+
return { error: "A \\"url\\" string is required." };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const MAX_LENGTH = 32_000;
|
|
581
|
+
|
|
582
|
+
const response = await fetch(url, {
|
|
583
|
+
headers: { "User-Agent": "poncho-fetch-page/1.0" },
|
|
584
|
+
redirect: "follow",
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
return { url, status: response.status, error: response.statusText };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const html = await response.text();
|
|
592
|
+
|
|
593
|
+
// Lightweight HTML-to-text: strip tags, collapse whitespace.
|
|
594
|
+
const text = html
|
|
595
|
+
.replace(/<script[\\s\\S]*?<\\/script>/gi, "")
|
|
596
|
+
.replace(/<style[\\s\\S]*?<\\/style>/gi, "")
|
|
597
|
+
.replace(/<[^>]+>/g, " ")
|
|
598
|
+
.replace(/ /gi, " ")
|
|
599
|
+
.replace(/&/gi, "&")
|
|
600
|
+
.replace(/</gi, "<")
|
|
601
|
+
.replace(/>/gi, ">")
|
|
602
|
+
.replace(/"/gi, '"')
|
|
603
|
+
.replace(/'/gi, "'")
|
|
604
|
+
.replace(/\\s+/g, " ")
|
|
605
|
+
.trim();
|
|
606
|
+
|
|
607
|
+
const content = text.length > MAX_LENGTH
|
|
608
|
+
? text.slice(0, MAX_LENGTH) + "… (truncated)"
|
|
609
|
+
: text;
|
|
610
|
+
|
|
611
|
+
return { url, status: response.status, content };
|
|
612
|
+
}
|
|
613
|
+
`;
|
|
614
|
+
|
|
548
615
|
const ensureFile = async (path: string, content: string): Promise<void> => {
|
|
549
616
|
await mkdir(dirname(path), { recursive: true });
|
|
550
617
|
await writeFile(path, content, { encoding: "utf8", flag: "wx" });
|
|
@@ -765,6 +832,8 @@ export const initProject = async (
|
|
|
765
832
|
{ path: "tests/basic.yaml", content: TEST_TEMPLATE },
|
|
766
833
|
{ path: "skills/starter/SKILL.md", content: SKILL_TEMPLATE },
|
|
767
834
|
{ path: "skills/starter/scripts/starter-echo.ts", content: SKILL_TOOL_TEMPLATE },
|
|
835
|
+
{ path: "skills/fetch-page/SKILL.md", content: FETCH_PAGE_SKILL_TEMPLATE },
|
|
836
|
+
{ path: "skills/fetch-page/scripts/fetch-page.ts", content: FETCH_PAGE_SCRIPT_TEMPLATE },
|
|
768
837
|
];
|
|
769
838
|
if (onboarding.envFile) {
|
|
770
839
|
scaffoldFiles.push({ path: ".env", content: onboarding.envFile });
|
|
@@ -354,15 +354,29 @@ export const runInteractiveInk = async ({
|
|
|
354
354
|
|
|
355
355
|
// --- Print header ----------------------------------------------------------
|
|
356
356
|
|
|
357
|
+
const mascot = [
|
|
358
|
+
`${C.yellow} ⣀⣀⣀⣀⣀⣀${C.reset}`,
|
|
359
|
+
`${C.yellow} ⠠⠾⠛⠛⠛⠛⠛⠛⠷⠄${C.reset}`,
|
|
360
|
+
`${C.gray} ⡇${C.cyan} ⠶ ⠶ ${C.gray}⢸${C.reset}`,
|
|
361
|
+
`${C.gray} ⠣⡀${C.cyan} ⠒⠚${C.gray}⢀⠜${C.reset}`,
|
|
362
|
+
`${C.yellow} ⣿⣿⣿⣿⣿⣿${C.reset}`,
|
|
363
|
+
`${C.gray} ⠃ ⠘${C.reset}`,
|
|
364
|
+
];
|
|
365
|
+
console.log("");
|
|
366
|
+
for (const line of mascot) {
|
|
367
|
+
console.log(line);
|
|
368
|
+
}
|
|
369
|
+
console.log(`${C.bold}${C.cyan} poncho${C.reset}`);
|
|
370
|
+
console.log("");
|
|
357
371
|
console.log(
|
|
358
372
|
gray(
|
|
359
|
-
|
|
373
|
+
` ${metadata.agentName} · ${metadata.provider}/${metadata.model} · ${metadata.environment}`,
|
|
360
374
|
),
|
|
361
375
|
);
|
|
362
|
-
console.log(gray('Type "exit" to quit, "/help" for commands'));
|
|
363
|
-
console.log(gray("Press Ctrl+C during a run to stop streaming output."));
|
|
376
|
+
console.log(gray(' Type "exit" to quit, "/help" for commands'));
|
|
377
|
+
console.log(gray(" Press Ctrl+C during a run to stop streaming output."));
|
|
364
378
|
console.log(
|
|
365
|
-
gray("Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n"),
|
|
379
|
+
gray(" Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n"),
|
|
366
380
|
);
|
|
367
381
|
const intro = await consumeFirstRunIntro(workingDir, {
|
|
368
382
|
agentName: metadata.agentName,
|