@poncho-ai/cli 0.3.0 → 0.3.2
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 +8 -9
- package/CHANGELOG.md +19 -0
- package/dist/chunk-3OBDF3LI.js +1893 -0
- package/dist/chunk-AS3CEZHY.js +2145 -0
- package/dist/chunk-ESPC4MPN.js +4043 -0
- package/dist/chunk-XABZUC4W.js +4043 -0
- package/dist/chunk-YJZ3MQIA.js +2145 -0
- package/dist/cli.js +1 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +3 -2
- package/dist/run-interactive-ink-BOD2F5JM.js +463 -0
- package/dist/run-interactive-ink-EHJVWEDC.js +462 -0
- package/dist/run-interactive-ink-M5E55KCC.js +463 -0
- package/package.json +2 -2
- package/src/index.ts +57 -15
- package/src/init-feature-context.ts +2 -1
- package/src/run-interactive-ink.ts +2 -1
- package/src/web-ui.ts +1 -1
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeFirstRunIntro,
|
|
3
|
+
inferConversationTitle,
|
|
4
|
+
resolveHarnessEnvironment
|
|
5
|
+
} from "./chunk-XABZUC4W.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
|
+
let runFailed = false;
|
|
322
|
+
let usage;
|
|
323
|
+
let latestRunId = "";
|
|
324
|
+
const startedAt = Date.now();
|
|
325
|
+
try {
|
|
326
|
+
for await (const event of harness.run({
|
|
327
|
+
task: trimmed,
|
|
328
|
+
parameters: params,
|
|
329
|
+
messages
|
|
330
|
+
})) {
|
|
331
|
+
if (event.type === "run:started") {
|
|
332
|
+
latestRunId = event.runId;
|
|
333
|
+
}
|
|
334
|
+
if (event.type === "model:chunk") {
|
|
335
|
+
sawChunk = true;
|
|
336
|
+
responseText += event.content;
|
|
337
|
+
streamedText += event.content;
|
|
338
|
+
if (!thinkingCleared) {
|
|
339
|
+
clearThinking();
|
|
340
|
+
process.stdout.write(`${C.green}assistant> ${C.reset}`);
|
|
341
|
+
}
|
|
342
|
+
process.stdout.write(event.content);
|
|
343
|
+
} 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") {
|
|
344
|
+
if (streamedText.length > 0) {
|
|
345
|
+
committedText = true;
|
|
346
|
+
streamedText = "";
|
|
347
|
+
process.stdout.write("\n");
|
|
348
|
+
}
|
|
349
|
+
clearThinking();
|
|
350
|
+
if (event.type === "tool:started") {
|
|
351
|
+
const preview = showToolPayloads ? compactPreview(event.input, 400) : compactPreview(event.input, 100);
|
|
352
|
+
console.log(yellow(`tools> start ${event.tool} input=${preview}`));
|
|
353
|
+
toolTimeline.push(`- start \`${event.tool}\``);
|
|
354
|
+
toolEvents += 1;
|
|
355
|
+
} else if (event.type === "tool:completed") {
|
|
356
|
+
const preview = showToolPayloads ? compactPreview(event.output, 400) : compactPreview(event.output, 100);
|
|
357
|
+
console.log(
|
|
358
|
+
yellow(
|
|
359
|
+
`tools> done ${event.tool} in ${formatDuration(event.duration)}`
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
if (showToolPayloads) {
|
|
363
|
+
console.log(yellow(`tools> output ${preview}`));
|
|
364
|
+
}
|
|
365
|
+
toolTimeline.push(
|
|
366
|
+
`- done \`${event.tool}\` in ${formatDuration(event.duration)}`
|
|
367
|
+
);
|
|
368
|
+
} else if (event.type === "tool:error") {
|
|
369
|
+
console.log(
|
|
370
|
+
red(`tools> error ${event.tool}: ${event.error}`)
|
|
371
|
+
);
|
|
372
|
+
toolTimeline.push(`- error \`${event.tool}\`: ${event.error}`);
|
|
373
|
+
} else if (event.type === "tool:approval:required") {
|
|
374
|
+
console.log(
|
|
375
|
+
magenta(`tools> approval required for ${event.tool}`)
|
|
376
|
+
);
|
|
377
|
+
toolTimeline.push(`- approval required \`${event.tool}\``);
|
|
378
|
+
} else if (event.type === "tool:approval:granted") {
|
|
379
|
+
console.log(
|
|
380
|
+
gray(`tools> approval granted (${event.approvalId})`)
|
|
381
|
+
);
|
|
382
|
+
toolTimeline.push(`- approval granted (${event.approvalId})`);
|
|
383
|
+
} else if (event.type === "tool:approval:denied") {
|
|
384
|
+
console.log(
|
|
385
|
+
magenta(`tools> approval denied (${event.approvalId})`)
|
|
386
|
+
);
|
|
387
|
+
toolTimeline.push(`- approval denied (${event.approvalId})`);
|
|
388
|
+
}
|
|
389
|
+
} else if (event.type === "run:error") {
|
|
390
|
+
clearThinking();
|
|
391
|
+
runFailed = true;
|
|
392
|
+
console.log(red(`error> ${event.error.message}`));
|
|
393
|
+
} else if (event.type === "model:response") {
|
|
394
|
+
usage = event.usage;
|
|
395
|
+
} else if (event.type === "run:completed" && !sawChunk) {
|
|
396
|
+
clearThinking();
|
|
397
|
+
responseText = event.result.response ?? "";
|
|
398
|
+
if (responseText.length > 0) {
|
|
399
|
+
process.stdout.write(
|
|
400
|
+
`${C.green}assistant> ${C.reset}${responseText}
|
|
401
|
+
`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
clearThinking();
|
|
408
|
+
runFailed = true;
|
|
409
|
+
console.log(
|
|
410
|
+
red(
|
|
411
|
+
`error> ${error instanceof Error ? error.message : "Unknown error"}`
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
if (sawChunk && streamedText.length > 0) {
|
|
416
|
+
process.stdout.write("\n");
|
|
417
|
+
} else if (!sawChunk && !runFailed && responseText.length === 0) {
|
|
418
|
+
clearThinking();
|
|
419
|
+
console.log(green("assistant> (no response)"));
|
|
420
|
+
}
|
|
421
|
+
const fullResponse = responseText || streamedText;
|
|
422
|
+
if (!runFailed && toolEvents === 0 && FAUX_TOOL_LOG_PATTERN.test(fullResponse)) {
|
|
423
|
+
console.log(
|
|
424
|
+
magenta(
|
|
425
|
+
"warning> assistant described tool execution but no real tool events occurred."
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
const durationMs = Date.now() - startedAt;
|
|
430
|
+
console.log(
|
|
431
|
+
gray(`meta> ${formatDuration(durationMs)} | tools: ${toolEvents}
|
|
432
|
+
`)
|
|
433
|
+
);
|
|
434
|
+
if (!activeConversationId) {
|
|
435
|
+
const created = await conversationStore.create(
|
|
436
|
+
OWNER_ID,
|
|
437
|
+
inferConversationTitle(trimmed)
|
|
438
|
+
);
|
|
439
|
+
activeConversationId = created.conversationId;
|
|
440
|
+
}
|
|
441
|
+
messages.push({ role: "user", content: trimmed });
|
|
442
|
+
messages.push({
|
|
443
|
+
role: "assistant",
|
|
444
|
+
content: responseText,
|
|
445
|
+
metadata: toolTimeline.length > 0 ? { toolActivity: toolTimeline } : void 0
|
|
446
|
+
});
|
|
447
|
+
turn = computeTurn(messages);
|
|
448
|
+
const conversation = await conversationStore.get(activeConversationId);
|
|
449
|
+
if (conversation) {
|
|
450
|
+
const maybeTitle = conversation.messages.length === 0 && (conversation.title === "New conversation" || conversation.title.trim().length === 0) ? inferConversationTitle(trimmed) : conversation.title;
|
|
451
|
+
await conversationStore.update({
|
|
452
|
+
...conversation,
|
|
453
|
+
title: maybeTitle,
|
|
454
|
+
messages: [...messages],
|
|
455
|
+
runtimeRunId: latestRunId || conversation.runtimeRunId
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
rl.close();
|
|
460
|
+
};
|
|
461
|
+
export {
|
|
462
|
+
runInteractiveInk
|
|
463
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "CLI for building and deploying AI agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"react": "^19.2.4",
|
|
26
26
|
"react-devtools-core": "^6.1.5",
|
|
27
27
|
"yaml": "^2.8.1",
|
|
28
|
-
"@poncho-ai/harness": "0.3.
|
|
28
|
+
"@poncho-ai/harness": "0.3.1",
|
|
29
29
|
"@poncho-ai/sdk": "0.2.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -69,11 +69,63 @@ const readRequestBody = async (request: IncomingMessage): Promise<unknown> => {
|
|
|
69
69
|
return body.length > 0 ? (JSON.parse(body) as unknown) : {};
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Detects the runtime environment from platform-specific or standard environment variables.
|
|
74
|
+
* Priority: PONCHO_ENV > platform detection (Vercel, Railway, etc.) > NODE_ENV > "development"
|
|
75
|
+
*/
|
|
76
|
+
export const resolveHarnessEnvironment = (): "development" | "staging" | "production" => {
|
|
77
|
+
// Check explicit Poncho environment variable first
|
|
78
|
+
if (process.env.PONCHO_ENV) {
|
|
79
|
+
const value = process.env.PONCHO_ENV.toLowerCase();
|
|
80
|
+
if (value === "production" || value === "staging") {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
return "development";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Detect platform-specific environment variables
|
|
87
|
+
// Vercel
|
|
88
|
+
if (process.env.VERCEL_ENV) {
|
|
89
|
+
const vercelEnv = process.env.VERCEL_ENV.toLowerCase();
|
|
90
|
+
if (vercelEnv === "production") return "production";
|
|
91
|
+
if (vercelEnv === "preview") return "staging";
|
|
92
|
+
return "development";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Railway
|
|
96
|
+
if (process.env.RAILWAY_ENVIRONMENT) {
|
|
97
|
+
const railwayEnv = process.env.RAILWAY_ENVIRONMENT.toLowerCase();
|
|
98
|
+
if (railwayEnv === "production") return "production";
|
|
99
|
+
return "staging";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Render
|
|
103
|
+
if (process.env.RENDER) {
|
|
104
|
+
// Render sets IS_PULL_REQUEST for preview deploys
|
|
105
|
+
if (process.env.IS_PULL_REQUEST === "true") return "staging";
|
|
106
|
+
return "production";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// AWS Lambda
|
|
110
|
+
if (process.env.AWS_EXECUTION_ENV || process.env.AWS_LAMBDA_FUNCTION_NAME) {
|
|
111
|
+
return "production";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fly.io
|
|
115
|
+
if (process.env.FLY_APP_NAME) {
|
|
116
|
+
return "production";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Fall back to NODE_ENV
|
|
120
|
+
if (process.env.NODE_ENV) {
|
|
121
|
+
const value = process.env.NODE_ENV.toLowerCase();
|
|
122
|
+
if (value === "production" || value === "staging") {
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
return "development";
|
|
76
126
|
}
|
|
127
|
+
|
|
128
|
+
// Default to development
|
|
77
129
|
return "development";
|
|
78
130
|
};
|
|
79
131
|
|
|
@@ -174,17 +226,7 @@ Environment: {{runtime.environment}}
|
|
|
174
226
|
- Use tools when needed
|
|
175
227
|
- Explain your reasoning clearly
|
|
176
228
|
- Ask clarifying questions when requirements are ambiguous
|
|
177
|
-
- For setup/configuration/skills/MCP questions, proactively read \`README.md\` with \`read_file\` before answering.
|
|
178
|
-
- Prefer concrete commands and examples from \`README.md\` over assumptions.
|
|
179
229
|
- Never claim a file/tool change unless the corresponding tool call actually succeeded
|
|
180
|
-
|
|
181
|
-
## Default Capabilities in a Fresh Project
|
|
182
|
-
|
|
183
|
-
- Built-in tools: \`list_directory\` and \`read_file\`
|
|
184
|
-
- \`write_file\` is available in development, and disabled by default in production
|
|
185
|
-
- A starter local skill is included (\`starter-echo\`)
|
|
186
|
-
- Bash/shell commands are **not** available unless you install and enable a shell tool/skill
|
|
187
|
-
- Git operations are only available if a git-capable tool/skill is configured
|
|
188
230
|
`;
|
|
189
231
|
|
|
190
232
|
/**
|
|
@@ -782,7 +824,7 @@ export const createRequestHandler = async (options?: {
|
|
|
782
824
|
agentModelName = modelMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
783
825
|
}
|
|
784
826
|
} catch {}
|
|
785
|
-
const harness = new AgentHarness({ workingDir });
|
|
827
|
+
const harness = new AgentHarness({ workingDir, environment: resolveHarnessEnvironment() });
|
|
786
828
|
await harness.initialize();
|
|
787
829
|
const telemetry = new TelemetryEmitter(config?.telemetry);
|
|
788
830
|
const conversationStore = createConversationStore(resolveStateConfig(config), { workingDir });
|
|
@@ -3,6 +3,7 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { basename, dirname, resolve } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import type { PonchoConfig } from "@poncho-ai/harness";
|
|
6
|
+
import { resolveHarnessEnvironment } from "./index.js";
|
|
6
7
|
|
|
7
8
|
type IntroInput = {
|
|
8
9
|
agentName: string;
|
|
@@ -110,7 +111,7 @@ export const consumeFirstRunIntro = async (
|
|
|
110
111
|
workingDir: string,
|
|
111
112
|
input: IntroInput,
|
|
112
113
|
): Promise<string | undefined> => {
|
|
113
|
-
const runtimeEnv = (
|
|
114
|
+
const runtimeEnv = resolveHarnessEnvironment();
|
|
114
115
|
if (runtimeEnv === "production") {
|
|
115
116
|
return undefined;
|
|
116
117
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import type { AgentEvent, Message, TokenUsage } from "@poncho-ai/sdk";
|
|
18
18
|
import { inferConversationTitle } from "./web-ui.js";
|
|
19
19
|
import { consumeFirstRunIntro } from "./init-feature-context.js";
|
|
20
|
+
import { resolveHarnessEnvironment } from "./index.js";
|
|
20
21
|
|
|
21
22
|
// Re-export types that index.ts references
|
|
22
23
|
export type ApprovalRequest = {
|
|
@@ -103,7 +104,7 @@ const loadMetadata = async (workingDir: string): Promise<UiMetadata> => {
|
|
|
103
104
|
model,
|
|
104
105
|
provider,
|
|
105
106
|
workingDir,
|
|
106
|
-
environment:
|
|
107
|
+
environment: resolveHarnessEnvironment(),
|
|
107
108
|
};
|
|
108
109
|
};
|
|
109
110
|
|
package/src/web-ui.ts
CHANGED
|
@@ -1813,7 +1813,7 @@ export const renderWebUiHtml = (options?: { agentName?: string }): string => {
|
|
|
1813
1813
|
if (!sidebar || !backdrop || !shell) return;
|
|
1814
1814
|
|
|
1815
1815
|
var sidebarWidth = 260;
|
|
1816
|
-
var edgeThreshold =
|
|
1816
|
+
var edgeThreshold = 50; // px from left edge to start drag
|
|
1817
1817
|
var velocityThreshold = 0.3; // px/ms to trigger open/close
|
|
1818
1818
|
|
|
1819
1819
|
var dragging = false;
|