@poncho-ai/cli 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/dist/chunk-JIRFQFIX.js +5463 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/run-interactive-ink-JGN4BBJB.js +494 -0
- package/package.json +4 -4
- package/src/index.ts +22 -3
- package/src/init-feature-context.ts +12 -34
- package/test/cli.test.ts +13 -0
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeFirstRunIntro,
|
|
3
|
+
inferConversationTitle,
|
|
4
|
+
resolveHarnessEnvironment
|
|
5
|
+
} from "./chunk-JIRFQFIX.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
|
+
console.log(
|
|
109
|
+
gray(
|
|
110
|
+
`${activeMarker} ${conversation.conversationId} | ${conversation.title} | ${formatDate(conversation.updatedAt)}`
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return { shouldExit: false };
|
|
115
|
+
}
|
|
116
|
+
if (norm === "/open") {
|
|
117
|
+
const conversationId = args[0];
|
|
118
|
+
if (!conversationId) {
|
|
119
|
+
console.log(yellow("usage> /open <conversationId>"));
|
|
120
|
+
return { shouldExit: false };
|
|
121
|
+
}
|
|
122
|
+
const conversation = await conversationStore.get(conversationId);
|
|
123
|
+
if (!conversation) {
|
|
124
|
+
console.log(yellow(`conversations> not found: ${conversationId}`));
|
|
125
|
+
return { shouldExit: false };
|
|
126
|
+
}
|
|
127
|
+
state.activeConversationId = conversation.conversationId;
|
|
128
|
+
state.messages = [...conversation.messages];
|
|
129
|
+
state.turn = computeTurn(state.messages);
|
|
130
|
+
console.log(gray(`conversations> opened ${conversation.conversationId}`));
|
|
131
|
+
return { shouldExit: false };
|
|
132
|
+
}
|
|
133
|
+
if (norm === "/new") {
|
|
134
|
+
const title = args.join(" ").trim();
|
|
135
|
+
const conversation = await conversationStore.create(OWNER_ID, title || void 0);
|
|
136
|
+
state.activeConversationId = conversation.conversationId;
|
|
137
|
+
state.messages = [];
|
|
138
|
+
state.turn = 1;
|
|
139
|
+
console.log(gray(`conversations> new ${conversation.conversationId}`));
|
|
140
|
+
return { shouldExit: false };
|
|
141
|
+
}
|
|
142
|
+
if (norm === "/delete") {
|
|
143
|
+
const targetConversationId = args[0] ?? state.activeConversationId ?? "";
|
|
144
|
+
if (!targetConversationId) {
|
|
145
|
+
console.log(yellow("usage> /delete <conversationId>"));
|
|
146
|
+
return { shouldExit: false };
|
|
147
|
+
}
|
|
148
|
+
const removed = await conversationStore.delete(targetConversationId);
|
|
149
|
+
if (!removed) {
|
|
150
|
+
console.log(yellow(`conversations> not found: ${targetConversationId}`));
|
|
151
|
+
return { shouldExit: false };
|
|
152
|
+
}
|
|
153
|
+
if (state.activeConversationId === targetConversationId) {
|
|
154
|
+
state.activeConversationId = null;
|
|
155
|
+
state.messages = [];
|
|
156
|
+
state.turn = 1;
|
|
157
|
+
}
|
|
158
|
+
console.log(gray(`conversations> deleted ${targetConversationId}`));
|
|
159
|
+
return { shouldExit: false };
|
|
160
|
+
}
|
|
161
|
+
if (norm === "/continue") {
|
|
162
|
+
const conversations = await conversationStore.list(OWNER_ID);
|
|
163
|
+
const latest = conversations[0];
|
|
164
|
+
if (!latest) {
|
|
165
|
+
console.log(yellow("conversations> no conversations to continue"));
|
|
166
|
+
return { shouldExit: false };
|
|
167
|
+
}
|
|
168
|
+
state.activeConversationId = latest.conversationId;
|
|
169
|
+
state.messages = [...latest.messages];
|
|
170
|
+
state.turn = computeTurn(state.messages);
|
|
171
|
+
console.log(gray(`conversations> continued ${latest.conversationId}`));
|
|
172
|
+
return { shouldExit: false };
|
|
173
|
+
}
|
|
174
|
+
if (norm === "/reset") {
|
|
175
|
+
if (args[0]?.toLowerCase() === "all") {
|
|
176
|
+
const conversations = await conversationStore.list(OWNER_ID);
|
|
177
|
+
for (const conversation2 of conversations) {
|
|
178
|
+
await conversationStore.delete(conversation2.conversationId);
|
|
179
|
+
}
|
|
180
|
+
state.activeConversationId = null;
|
|
181
|
+
state.messages = [];
|
|
182
|
+
state.turn = 1;
|
|
183
|
+
console.log(gray("conversations> reset all"));
|
|
184
|
+
return { shouldExit: false };
|
|
185
|
+
}
|
|
186
|
+
if (!state.activeConversationId) {
|
|
187
|
+
state.messages = [];
|
|
188
|
+
state.turn = 1;
|
|
189
|
+
console.log(gray("conversations> current session reset"));
|
|
190
|
+
return { shouldExit: false };
|
|
191
|
+
}
|
|
192
|
+
const conversation = await conversationStore.get(state.activeConversationId);
|
|
193
|
+
if (!conversation) {
|
|
194
|
+
state.activeConversationId = null;
|
|
195
|
+
state.messages = [];
|
|
196
|
+
state.turn = 1;
|
|
197
|
+
console.log(yellow("conversations> active conversation no longer exists"));
|
|
198
|
+
return { shouldExit: false };
|
|
199
|
+
}
|
|
200
|
+
await conversationStore.update({
|
|
201
|
+
...conversation,
|
|
202
|
+
messages: []
|
|
203
|
+
});
|
|
204
|
+
state.messages = [];
|
|
205
|
+
state.turn = 1;
|
|
206
|
+
console.log(gray(`conversations> reset ${conversation.conversationId}`));
|
|
207
|
+
return { shouldExit: false };
|
|
208
|
+
}
|
|
209
|
+
console.log(yellow(`Unknown command: ${command}`));
|
|
210
|
+
return { shouldExit: false };
|
|
211
|
+
};
|
|
212
|
+
var runInteractiveInk = async ({
|
|
213
|
+
harness,
|
|
214
|
+
params,
|
|
215
|
+
workingDir,
|
|
216
|
+
config,
|
|
217
|
+
conversationStore,
|
|
218
|
+
onSetApprovalCallback
|
|
219
|
+
}) => {
|
|
220
|
+
const metadata = await loadMetadata(workingDir);
|
|
221
|
+
const rl = readline.createInterface({
|
|
222
|
+
input: process.stdin,
|
|
223
|
+
output: process.stdout,
|
|
224
|
+
terminal: true
|
|
225
|
+
});
|
|
226
|
+
if (onSetApprovalCallback) {
|
|
227
|
+
onSetApprovalCallback((req) => {
|
|
228
|
+
process.stdout.write("\n");
|
|
229
|
+
const preview = compactPreview(req.input, 100);
|
|
230
|
+
rl.question(
|
|
231
|
+
`${C.yellow}${C.bold}Tool "${req.tool}" requires approval${C.reset}
|
|
232
|
+
${C.gray}input: ${preview}${C.reset}
|
|
233
|
+
${C.yellow}approve? (y/n): ${C.reset}`,
|
|
234
|
+
(answer) => {
|
|
235
|
+
const approved = answer.trim().toLowerCase() === "y";
|
|
236
|
+
console.log(
|
|
237
|
+
approved ? green(` approved ${req.tool}`) : magenta(` denied ${req.tool}`)
|
|
238
|
+
);
|
|
239
|
+
req.resolve(approved);
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
console.log(
|
|
245
|
+
gray(
|
|
246
|
+
`
|
|
247
|
+
${metadata.agentName} | ${metadata.provider}/${metadata.model} | ${metadata.environment}`
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
console.log(gray('Type "exit" to quit, "/help" for commands'));
|
|
251
|
+
console.log(
|
|
252
|
+
gray("Conversation controls: /list /open <id> /new [title] /delete [id] /continue /reset [all]\n")
|
|
253
|
+
);
|
|
254
|
+
const intro = await consumeFirstRunIntro(workingDir, {
|
|
255
|
+
agentName: metadata.agentName,
|
|
256
|
+
provider: metadata.provider,
|
|
257
|
+
model: metadata.model,
|
|
258
|
+
config
|
|
259
|
+
});
|
|
260
|
+
if (intro) {
|
|
261
|
+
console.log(green("assistant>"));
|
|
262
|
+
await streamTextAsTokens(intro);
|
|
263
|
+
stdout.write("\n\n");
|
|
264
|
+
}
|
|
265
|
+
let messages = intro ? [{ role: "assistant", content: intro }] : [];
|
|
266
|
+
let turn = 1;
|
|
267
|
+
let activeConversationId = null;
|
|
268
|
+
let showToolPayloads = false;
|
|
269
|
+
const prompt = `${C.cyan}you> ${C.reset}`;
|
|
270
|
+
while (true) {
|
|
271
|
+
let task;
|
|
272
|
+
try {
|
|
273
|
+
task = await ask(rl, prompt);
|
|
274
|
+
} catch {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
const trimmed = task.trim();
|
|
278
|
+
if (!trimmed) continue;
|
|
279
|
+
if (trimmed.toLowerCase() === "exit") break;
|
|
280
|
+
if (trimmed.startsWith("/")) {
|
|
281
|
+
if (trimmed.toLowerCase() === "/exit") break;
|
|
282
|
+
if (trimmed.toLowerCase() === "/tools") {
|
|
283
|
+
showToolPayloads = !showToolPayloads;
|
|
284
|
+
console.log(gray(`tool payloads: ${showToolPayloads ? "on" : "off"}`));
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const interactiveState = {
|
|
288
|
+
messages,
|
|
289
|
+
turn,
|
|
290
|
+
activeConversationId
|
|
291
|
+
};
|
|
292
|
+
const slashResult = await handleSlash(
|
|
293
|
+
trimmed,
|
|
294
|
+
interactiveState,
|
|
295
|
+
conversationStore
|
|
296
|
+
);
|
|
297
|
+
if (slashResult.shouldExit) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
messages = interactiveState.messages;
|
|
301
|
+
turn = interactiveState.turn;
|
|
302
|
+
activeConversationId = interactiveState.activeConversationId;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
console.log(gray(`
|
|
306
|
+
--- turn ${turn} ---`));
|
|
307
|
+
process.stdout.write(gray("thinking..."));
|
|
308
|
+
let thinkingCleared = false;
|
|
309
|
+
const clearThinking = () => {
|
|
310
|
+
if (thinkingCleared) return;
|
|
311
|
+
thinkingCleared = true;
|
|
312
|
+
readline.clearLine(process.stdout, 0);
|
|
313
|
+
readline.cursorTo(process.stdout, 0);
|
|
314
|
+
};
|
|
315
|
+
let responseText = "";
|
|
316
|
+
let streamedText = "";
|
|
317
|
+
let committedText = false;
|
|
318
|
+
let sawChunk = false;
|
|
319
|
+
let toolEvents = 0;
|
|
320
|
+
const toolTimeline = [];
|
|
321
|
+
const sections = [];
|
|
322
|
+
let currentText = "";
|
|
323
|
+
let currentTools = [];
|
|
324
|
+
let runFailed = false;
|
|
325
|
+
let usage;
|
|
326
|
+
let latestRunId = "";
|
|
327
|
+
const startedAt = Date.now();
|
|
328
|
+
try {
|
|
329
|
+
for await (const event of harness.run({
|
|
330
|
+
task: trimmed,
|
|
331
|
+
parameters: params,
|
|
332
|
+
messages
|
|
333
|
+
})) {
|
|
334
|
+
if (event.type === "run:started") {
|
|
335
|
+
latestRunId = event.runId;
|
|
336
|
+
}
|
|
337
|
+
if (event.type === "model:chunk") {
|
|
338
|
+
sawChunk = true;
|
|
339
|
+
if (currentTools.length > 0) {
|
|
340
|
+
sections.push({ type: "tools", content: currentTools });
|
|
341
|
+
currentTools = [];
|
|
342
|
+
}
|
|
343
|
+
responseText += event.content;
|
|
344
|
+
streamedText += event.content;
|
|
345
|
+
currentText += event.content;
|
|
346
|
+
if (!thinkingCleared) {
|
|
347
|
+
clearThinking();
|
|
348
|
+
process.stdout.write(`${C.green}assistant> ${C.reset}`);
|
|
349
|
+
}
|
|
350
|
+
process.stdout.write(event.content);
|
|
351
|
+
} 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") {
|
|
352
|
+
if (streamedText.length > 0) {
|
|
353
|
+
committedText = true;
|
|
354
|
+
streamedText = "";
|
|
355
|
+
process.stdout.write("\n");
|
|
356
|
+
}
|
|
357
|
+
clearThinking();
|
|
358
|
+
if (event.type === "tool:started") {
|
|
359
|
+
if (currentText.length > 0) {
|
|
360
|
+
sections.push({ type: "text", content: currentText });
|
|
361
|
+
currentText = "";
|
|
362
|
+
}
|
|
363
|
+
const preview = showToolPayloads ? compactPreview(event.input, 400) : compactPreview(event.input, 100);
|
|
364
|
+
console.log(yellow(`tools> start ${event.tool} input=${preview}`));
|
|
365
|
+
const toolText = `- start \`${event.tool}\``;
|
|
366
|
+
toolTimeline.push(toolText);
|
|
367
|
+
currentTools.push(toolText);
|
|
368
|
+
toolEvents += 1;
|
|
369
|
+
} else if (event.type === "tool:completed") {
|
|
370
|
+
const preview = showToolPayloads ? compactPreview(event.output, 400) : compactPreview(event.output, 100);
|
|
371
|
+
console.log(
|
|
372
|
+
yellow(
|
|
373
|
+
`tools> done ${event.tool} in ${formatDuration(event.duration)}`
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
if (showToolPayloads) {
|
|
377
|
+
console.log(yellow(`tools> output ${preview}`));
|
|
378
|
+
}
|
|
379
|
+
const toolText = `- done \`${event.tool}\` in ${formatDuration(event.duration)}`;
|
|
380
|
+
toolTimeline.push(toolText);
|
|
381
|
+
currentTools.push(toolText);
|
|
382
|
+
} else if (event.type === "tool:error") {
|
|
383
|
+
console.log(
|
|
384
|
+
red(`tools> error ${event.tool}: ${event.error}`)
|
|
385
|
+
);
|
|
386
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
387
|
+
toolTimeline.push(toolText);
|
|
388
|
+
currentTools.push(toolText);
|
|
389
|
+
} else if (event.type === "tool:approval:required") {
|
|
390
|
+
console.log(
|
|
391
|
+
magenta(`tools> approval required for ${event.tool}`)
|
|
392
|
+
);
|
|
393
|
+
const toolText = `- approval required \`${event.tool}\``;
|
|
394
|
+
toolTimeline.push(toolText);
|
|
395
|
+
currentTools.push(toolText);
|
|
396
|
+
} else if (event.type === "tool:approval:granted") {
|
|
397
|
+
console.log(
|
|
398
|
+
gray(`tools> approval granted (${event.approvalId})`)
|
|
399
|
+
);
|
|
400
|
+
const toolText = `- approval granted (${event.approvalId})`;
|
|
401
|
+
toolTimeline.push(toolText);
|
|
402
|
+
currentTools.push(toolText);
|
|
403
|
+
} else if (event.type === "tool:approval:denied") {
|
|
404
|
+
console.log(
|
|
405
|
+
magenta(`tools> approval denied (${event.approvalId})`)
|
|
406
|
+
);
|
|
407
|
+
const toolText = `- approval denied (${event.approvalId})`;
|
|
408
|
+
toolTimeline.push(toolText);
|
|
409
|
+
currentTools.push(toolText);
|
|
410
|
+
}
|
|
411
|
+
} else if (event.type === "run:error") {
|
|
412
|
+
clearThinking();
|
|
413
|
+
runFailed = true;
|
|
414
|
+
console.log(red(`error> ${event.error.message}`));
|
|
415
|
+
} else if (event.type === "model:response") {
|
|
416
|
+
usage = event.usage;
|
|
417
|
+
} else if (event.type === "run:completed" && !sawChunk) {
|
|
418
|
+
clearThinking();
|
|
419
|
+
responseText = event.result.response ?? "";
|
|
420
|
+
if (responseText.length > 0) {
|
|
421
|
+
process.stdout.write(
|
|
422
|
+
`${C.green}assistant> ${C.reset}${responseText}
|
|
423
|
+
`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
clearThinking();
|
|
430
|
+
runFailed = true;
|
|
431
|
+
console.log(
|
|
432
|
+
red(
|
|
433
|
+
`error> ${error instanceof Error ? error.message : "Unknown error"}`
|
|
434
|
+
)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (sawChunk && streamedText.length > 0) {
|
|
438
|
+
process.stdout.write("\n");
|
|
439
|
+
} else if (!sawChunk && !runFailed && responseText.length === 0) {
|
|
440
|
+
clearThinking();
|
|
441
|
+
console.log(green("assistant> (no response)"));
|
|
442
|
+
}
|
|
443
|
+
const fullResponse = responseText || streamedText;
|
|
444
|
+
if (!runFailed && toolEvents === 0 && FAUX_TOOL_LOG_PATTERN.test(fullResponse)) {
|
|
445
|
+
console.log(
|
|
446
|
+
magenta(
|
|
447
|
+
"warning> assistant described tool execution but no real tool events occurred."
|
|
448
|
+
)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
const durationMs = Date.now() - startedAt;
|
|
452
|
+
console.log(
|
|
453
|
+
gray(`meta> ${formatDuration(durationMs)} | tools: ${toolEvents}
|
|
454
|
+
`)
|
|
455
|
+
);
|
|
456
|
+
if (!activeConversationId) {
|
|
457
|
+
const created = await conversationStore.create(
|
|
458
|
+
OWNER_ID,
|
|
459
|
+
inferConversationTitle(trimmed)
|
|
460
|
+
);
|
|
461
|
+
activeConversationId = created.conversationId;
|
|
462
|
+
}
|
|
463
|
+
if (currentTools.length > 0) {
|
|
464
|
+
sections.push({ type: "tools", content: currentTools });
|
|
465
|
+
}
|
|
466
|
+
if (currentText.length > 0) {
|
|
467
|
+
sections.push({ type: "text", content: currentText });
|
|
468
|
+
}
|
|
469
|
+
messages.push({ role: "user", content: trimmed });
|
|
470
|
+
messages.push({
|
|
471
|
+
role: "assistant",
|
|
472
|
+
content: responseText,
|
|
473
|
+
metadata: toolTimeline.length > 0 || sections.length > 0 ? {
|
|
474
|
+
toolActivity: toolTimeline,
|
|
475
|
+
sections: sections.length > 0 ? sections : void 0
|
|
476
|
+
} : void 0
|
|
477
|
+
});
|
|
478
|
+
turn = computeTurn(messages);
|
|
479
|
+
const conversation = await conversationStore.get(activeConversationId);
|
|
480
|
+
if (conversation) {
|
|
481
|
+
const maybeTitle = conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0) ? inferConversationTitle(trimmed) : conversation.title;
|
|
482
|
+
await conversationStore.update({
|
|
483
|
+
...conversation,
|
|
484
|
+
title: maybeTitle,
|
|
485
|
+
messages: [...messages],
|
|
486
|
+
runtimeRunId: latestRunId || conversation.runtimeRunId
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
rl.close();
|
|
491
|
+
};
|
|
492
|
+
export {
|
|
493
|
+
runInteractiveInk
|
|
494
|
+
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CLI for building and deploying AI agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/
|
|
7
|
+
"url": "https://github.com/cesr/poncho-ai.git",
|
|
8
8
|
"directory": "packages/cli"
|
|
9
9
|
},
|
|
10
10
|
"publishConfig": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"react": "^19.2.4",
|
|
27
27
|
"react-devtools-core": "^6.1.5",
|
|
28
28
|
"yaml": "^2.8.1",
|
|
29
|
-
"@poncho-ai/harness": "0.
|
|
30
|
-
"@poncho-ai/sdk": "0.
|
|
29
|
+
"@poncho-ai/harness": "0.8.0",
|
|
30
|
+
"@poncho-ai/sdk": "0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/react": "^19.2.14",
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
LocalMcpBridge,
|
|
16
16
|
TelemetryEmitter,
|
|
17
17
|
createConversationStore,
|
|
18
|
+
ensureAgentIdentity,
|
|
19
|
+
generateAgentId,
|
|
18
20
|
loadPonchoConfig,
|
|
19
21
|
resolveStateConfig,
|
|
20
22
|
type PonchoConfig,
|
|
@@ -201,9 +203,11 @@ const parseParams = (values: string[]): Record<string, string> => {
|
|
|
201
203
|
|
|
202
204
|
const AGENT_TEMPLATE = (
|
|
203
205
|
name: string,
|
|
206
|
+
id: string,
|
|
204
207
|
options: { modelProvider: "anthropic" | "openai"; modelName: string },
|
|
205
208
|
): string => `---
|
|
206
209
|
name: ${name}
|
|
210
|
+
id: ${id}
|
|
207
211
|
description: A helpful Poncho assistant
|
|
208
212
|
model:
|
|
209
213
|
provider: ${options.modelProvider}
|
|
@@ -727,6 +731,7 @@ export const initProject = async (
|
|
|
727
731
|
interactive: false,
|
|
728
732
|
};
|
|
729
733
|
const onboarding = await runInitOnboarding(onboardingOptions);
|
|
734
|
+
const agentId = generateAgentId();
|
|
730
735
|
|
|
731
736
|
const G = "\x1b[32m";
|
|
732
737
|
const D = "\x1b[2m";
|
|
@@ -738,7 +743,13 @@ export const initProject = async (
|
|
|
738
743
|
process.stdout.write("\n");
|
|
739
744
|
|
|
740
745
|
const scaffoldFiles: Array<{ path: string; content: string }> = [
|
|
741
|
-
{
|
|
746
|
+
{
|
|
747
|
+
path: "AGENT.md",
|
|
748
|
+
content: AGENT_TEMPLATE(projectName, agentId, {
|
|
749
|
+
modelProvider: onboarding.agentModel.provider,
|
|
750
|
+
modelName: onboarding.agentModel.name,
|
|
751
|
+
}),
|
|
752
|
+
},
|
|
742
753
|
{ path: "poncho.config.js", content: renderConfigFile(onboarding.config) },
|
|
743
754
|
{ path: "package.json", content: PACKAGE_TEMPLATE(projectName, projectDir) },
|
|
744
755
|
{ path: "README.md", content: README_TEMPLATE(projectName) },
|
|
@@ -935,7 +946,11 @@ export const createRequestHandler = async (options?: {
|
|
|
935
946
|
});
|
|
936
947
|
await harness.initialize();
|
|
937
948
|
const telemetry = new TelemetryEmitter(config?.telemetry);
|
|
938
|
-
const
|
|
949
|
+
const identity = await ensureAgentIdentity(workingDir);
|
|
950
|
+
const conversationStore = createConversationStore(resolveStateConfig(config), {
|
|
951
|
+
workingDir,
|
|
952
|
+
agentId: identity.id,
|
|
953
|
+
});
|
|
939
954
|
const sessionStore = new SessionStore();
|
|
940
955
|
const loginRateLimiter = new LoginRateLimiter();
|
|
941
956
|
|
|
@@ -1688,6 +1703,7 @@ export const runInteractive = async (
|
|
|
1688
1703
|
approvalHandler,
|
|
1689
1704
|
});
|
|
1690
1705
|
await harness.initialize();
|
|
1706
|
+
const identity = await ensureAgentIdentity(workingDir);
|
|
1691
1707
|
try {
|
|
1692
1708
|
const { runInteractiveInk } = await import("./run-interactive-ink.js");
|
|
1693
1709
|
await (
|
|
@@ -1704,7 +1720,10 @@ export const runInteractive = async (
|
|
|
1704
1720
|
params,
|
|
1705
1721
|
workingDir,
|
|
1706
1722
|
config,
|
|
1707
|
-
conversationStore: createConversationStore(resolveStateConfig(config), {
|
|
1723
|
+
conversationStore: createConversationStore(resolveStateConfig(config), {
|
|
1724
|
+
workingDir,
|
|
1725
|
+
agentId: identity.id,
|
|
1726
|
+
}),
|
|
1708
1727
|
onSetApprovalCallback: (cb: (req: ApprovalRequest) => void) => {
|
|
1709
1728
|
onApprovalRequest = cb;
|
|
1710
1729
|
// If there's already a pending request, fire it immediately
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
ensureAgentIdentity,
|
|
5
|
+
getAgentStoreDirectory,
|
|
6
|
+
type PonchoConfig,
|
|
7
|
+
} from "@poncho-ai/harness";
|
|
6
8
|
import { resolveHarnessEnvironment } from "./index.js";
|
|
7
9
|
|
|
8
10
|
type IntroInput = {
|
|
@@ -22,26 +24,6 @@ type OnboardingMarkerState = {
|
|
|
22
24
|
|
|
23
25
|
const ONBOARDING_VERSION = 1;
|
|
24
26
|
|
|
25
|
-
const getStateDirectory = (): string => {
|
|
26
|
-
const cwd = process.cwd();
|
|
27
|
-
const home = homedir();
|
|
28
|
-
const isServerless =
|
|
29
|
-
process.env.VERCEL === "1" ||
|
|
30
|
-
process.env.VERCEL_ENV !== undefined ||
|
|
31
|
-
process.env.VERCEL_URL !== undefined ||
|
|
32
|
-
process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined ||
|
|
33
|
-
process.env.AWS_EXECUTION_ENV?.includes("AWS_Lambda") === true ||
|
|
34
|
-
process.env.LAMBDA_TASK_ROOT !== undefined ||
|
|
35
|
-
process.env.NOW_REGION !== undefined ||
|
|
36
|
-
cwd.startsWith("/var/task") ||
|
|
37
|
-
home.startsWith("/var/task") ||
|
|
38
|
-
process.env.SERVERLESS === "1";
|
|
39
|
-
if (isServerless) {
|
|
40
|
-
return "/tmp/.poncho/state";
|
|
41
|
-
}
|
|
42
|
-
return resolve(homedir(), ".poncho", "state");
|
|
43
|
-
};
|
|
44
|
-
|
|
45
27
|
const summarizeConfig = (config: PonchoConfig | undefined): string[] => {
|
|
46
28
|
const provider = config?.storage?.provider ?? config?.state?.provider ?? "local";
|
|
47
29
|
const memoryEnabled = config?.storage?.memory?.enabled ?? config?.memory?.enabled ?? false;
|
|
@@ -55,19 +37,15 @@ const summarizeConfig = (config: PonchoConfig | undefined): string[] => {
|
|
|
55
37
|
];
|
|
56
38
|
};
|
|
57
39
|
|
|
58
|
-
const getOnboardingMarkerPath = (workingDir: string): string =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.update(workingDir)
|
|
63
|
-
.digest("hex")
|
|
64
|
-
.slice(0, 12)}-onboarding.json`,
|
|
65
|
-
);
|
|
40
|
+
const getOnboardingMarkerPath = async (workingDir: string): Promise<string> => {
|
|
41
|
+
const identity = await ensureAgentIdentity(workingDir);
|
|
42
|
+
return resolve(getAgentStoreDirectory(identity), "onboarding-state.json");
|
|
43
|
+
};
|
|
66
44
|
|
|
67
45
|
const readMarker = async (
|
|
68
46
|
workingDir: string,
|
|
69
47
|
): Promise<OnboardingMarkerState | undefined> => {
|
|
70
|
-
const markerPath = getOnboardingMarkerPath(workingDir);
|
|
48
|
+
const markerPath = await getOnboardingMarkerPath(workingDir);
|
|
71
49
|
try {
|
|
72
50
|
await access(markerPath);
|
|
73
51
|
} catch {
|
|
@@ -85,7 +63,7 @@ const writeMarker = async (
|
|
|
85
63
|
workingDir: string,
|
|
86
64
|
state: OnboardingMarkerState,
|
|
87
65
|
): Promise<void> => {
|
|
88
|
-
const markerPath = getOnboardingMarkerPath(workingDir);
|
|
66
|
+
const markerPath = await getOnboardingMarkerPath(workingDir);
|
|
89
67
|
await mkdir(dirname(markerPath), { recursive: true });
|
|
90
68
|
await writeFile(markerPath, JSON.stringify(state, null, 2), "utf8");
|
|
91
69
|
};
|