@phi-code-admin/phi-code 0.61.3 → 0.61.5
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.
|
@@ -351,9 +351,10 @@ function getProviderConfigs(): ProviderConfig[] {
|
|
|
351
351
|
];
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
function getAvailableModels(): Array<{ id: string; provider: string; baseUrl: string; apiKey: string }
|
|
354
|
+
async function getAvailableModels(): Promise<Array<{ id: string; provider: string; baseUrl: string; apiKey: string }>> {
|
|
355
355
|
const models: Array<{ id: string; provider: string; baseUrl: string; apiKey: string }> = [];
|
|
356
356
|
|
|
357
|
+
// 1. Cloud providers via env vars
|
|
357
358
|
for (const provider of getProviderConfigs()) {
|
|
358
359
|
const apiKey = process.env[provider.envVar];
|
|
359
360
|
if (!apiKey) continue;
|
|
@@ -368,6 +369,60 @@ function getAvailableModels(): Array<{ id: string; provider: string; baseUrl: st
|
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
371
|
|
|
372
|
+
// 2. Local providers (LM Studio, Ollama) — auto-detect via models.json
|
|
373
|
+
const { join } = await import("node:path");
|
|
374
|
+
const { homedir } = await import("node:os");
|
|
375
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
376
|
+
|
|
377
|
+
const modelsJsonPath = join(homedir(), ".phi", "agent", "models.json");
|
|
378
|
+
if (existsSync(modelsJsonPath)) {
|
|
379
|
+
try {
|
|
380
|
+
const config = JSON.parse(readFileSync(modelsJsonPath, "utf-8"));
|
|
381
|
+
if (config.providers) {
|
|
382
|
+
for (const [id, providerConfig] of Object.entries<any>(config.providers)) {
|
|
383
|
+
const baseUrl = providerConfig.baseUrl || "";
|
|
384
|
+
const apiKey = providerConfig.apiKey || "local";
|
|
385
|
+
if (providerConfig.models?.length > 0) {
|
|
386
|
+
for (const m of providerConfig.models) {
|
|
387
|
+
const modelId = typeof m === "string" ? m : m.id;
|
|
388
|
+
// Skip if already added from env vars
|
|
389
|
+
if (!models.some(existing => existing.id === modelId && existing.baseUrl === baseUrl)) {
|
|
390
|
+
models.push({ id: modelId, provider: id, baseUrl, apiKey });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch { /* ignore parse errors */ }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 3. Try to detect LM Studio (port 1234) and Ollama (port 11434) directly
|
|
400
|
+
for (const local of [
|
|
401
|
+
{ name: "lm-studio", port: 1234, baseUrl: "http://localhost:1234/v1" },
|
|
402
|
+
{ name: "ollama", port: 11434, baseUrl: "http://localhost:11434/v1" },
|
|
403
|
+
]) {
|
|
404
|
+
// Skip if already discovered via models.json
|
|
405
|
+
if (models.some(m => m.baseUrl === local.baseUrl)) continue;
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const controller = new AbortController();
|
|
409
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
410
|
+
const resp = await fetch(`${local.baseUrl}/models`, { signal: controller.signal });
|
|
411
|
+
clearTimeout(timeout);
|
|
412
|
+
|
|
413
|
+
if (resp.ok) {
|
|
414
|
+
const data = await resp.json() as any;
|
|
415
|
+
const modelList = data?.data || [];
|
|
416
|
+
for (const m of modelList) {
|
|
417
|
+
const modelId = m.id || m.name;
|
|
418
|
+
if (modelId && !models.some(existing => existing.id === modelId)) {
|
|
419
|
+
models.push({ id: modelId, provider: local.name, baseUrl: local.baseUrl, apiKey: "local" });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} catch { /* not running */ }
|
|
424
|
+
}
|
|
425
|
+
|
|
371
426
|
return models;
|
|
372
427
|
}
|
|
373
428
|
|
|
@@ -626,7 +681,7 @@ Scoring: S (80+), A (65+), B (50+), C (35+), D (<35)`, "info");
|
|
|
626
681
|
}
|
|
627
682
|
|
|
628
683
|
// Get available models (validates API keys are non-empty and reasonable length)
|
|
629
|
-
const available = getAvailableModels();
|
|
684
|
+
const available = await getAvailableModels();
|
|
630
685
|
if (available.length === 0) {
|
|
631
686
|
const providers = getProviderConfigs();
|
|
632
687
|
const hint = providers.map(p => ` ${p.envVar}: ${process.env[p.envVar] ? "set but no models configured" : "not set"}`).join("\n");
|
|
@@ -226,12 +226,8 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
226
226
|
): Promise<{ results: TaskResult[]; progressFile: string }> {
|
|
227
227
|
const progressFile = todoFile.replace("todo-", "progress-");
|
|
228
228
|
const progressPath = join(plansDir, progressFile);
|
|
229
|
-
|
|
230
|
-
progress += `**Started:** ${new Date().toLocaleString()}\n`;
|
|
231
|
-
progress += `**Tasks:** ${tasks.length}\n**Mode:** in-session (single turn)\n\n`;
|
|
232
|
-
await writeFile(progressPath, progress, "utf-8");
|
|
229
|
+
const totalTasks = tasks.length;
|
|
233
230
|
|
|
234
|
-
// Shared context for sub-agents
|
|
235
231
|
const sharedContext = {
|
|
236
232
|
projectTitle: projectContext?.title || "Project",
|
|
237
233
|
projectDescription: projectContext?.description || "",
|
|
@@ -239,100 +235,49 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
239
235
|
completedTasks: [] as Array<{ index: number; title: string; agent: string; output: string }>,
|
|
240
236
|
};
|
|
241
237
|
|
|
242
|
-
|
|
243
|
-
const completed = new Set<number>();
|
|
244
|
-
const failed = new Set<number>();
|
|
245
|
-
const results: TaskResult[] = [];
|
|
246
|
-
|
|
247
|
-
// Check which tasks can run (all dependencies completed successfully)
|
|
248
|
-
function getReadyTasks(): number[] {
|
|
249
|
-
const ready: number[] = [];
|
|
250
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
251
|
-
const taskNum = i + 1;
|
|
252
|
-
if (completed.has(taskNum) || failed.has(taskNum)) continue;
|
|
253
|
-
|
|
254
|
-
const deps = tasks[i].dependencies || [];
|
|
255
|
-
const allDepsMet = deps.every(d => completed.has(d));
|
|
256
|
-
const anyDepFailed = deps.some(d => failed.has(d));
|
|
257
|
-
|
|
258
|
-
if (anyDepFailed) {
|
|
259
|
-
// Skip tasks whose dependencies failed
|
|
260
|
-
failed.add(taskNum);
|
|
261
|
-
results.push({
|
|
262
|
-
taskIndex: taskNum,
|
|
263
|
-
title: tasks[i].title,
|
|
264
|
-
agent: tasks[i].agent || "code",
|
|
265
|
-
status: "skipped",
|
|
266
|
-
output: `Skipped: dependency #${deps.find(d => failed.has(d))} failed`,
|
|
267
|
-
durationMs: 0,
|
|
268
|
-
});
|
|
269
|
-
notify(`⏭️ Task ${taskNum}: **${tasks[i].title}** — skipped (dependency failed)`, "warning");
|
|
270
|
-
} else if (allDepsMet) {
|
|
271
|
-
ready.push(i);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return ready;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const totalTasks = tasks.length;
|
|
278
|
-
|
|
279
|
-
notify(`🚀 Executing ${totalTasks} tasks in-session (no subprocess overhead)...`, "info");
|
|
238
|
+
notify(`🚀 Executing ${totalTasks} tasks in-session...`, "info");
|
|
280
239
|
|
|
281
240
|
// Build a single comprehensive prompt with ALL tasks
|
|
282
|
-
|
|
283
|
-
let megaPrompt = `# 📋 Project Plan: ${sharedContext.projectTitle}\n\n`;
|
|
241
|
+
let megaPrompt = `# 📋 Project: ${sharedContext.projectTitle}\n\n`;
|
|
284
242
|
megaPrompt += `${sharedContext.projectDescription}\n\n`;
|
|
285
243
|
if (sharedContext.specSummary) {
|
|
286
244
|
megaPrompt += `## Spec\n${sharedContext.specSummary}\n\n`;
|
|
287
245
|
}
|
|
288
246
|
megaPrompt += `## Tasks (execute ALL in order)\n\n`;
|
|
289
247
|
|
|
248
|
+
const results: TaskResult[] = [];
|
|
249
|
+
|
|
290
250
|
for (let i = 0; i < tasks.length; i++) {
|
|
291
251
|
const task = tasks[i];
|
|
292
252
|
const { taskPrompt } = executeTaskInSession(task, sharedContext);
|
|
293
253
|
megaPrompt += `---\n\n${taskPrompt}\n\n`;
|
|
294
|
-
|
|
295
|
-
// Mark all tasks as completed for the progress file
|
|
296
254
|
results.push({
|
|
297
|
-
taskIndex: i + 1,
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
status: "success",
|
|
301
|
-
output: "(executed in-session)",
|
|
302
|
-
durationMs: 0,
|
|
255
|
+
taskIndex: i + 1, title: task.title,
|
|
256
|
+
agent: task.agent || "code", status: "success",
|
|
257
|
+
output: "(in-session)", durationMs: 0,
|
|
303
258
|
});
|
|
304
259
|
}
|
|
305
260
|
|
|
306
261
|
megaPrompt += `---\n\n## ⚠️ Instructions\n\n`;
|
|
307
262
|
megaPrompt += `Execute ALL ${totalTasks} tasks above **sequentially**. For each task:\n`;
|
|
308
263
|
megaPrompt += `1. Create/edit the required files using your tools\n`;
|
|
309
|
-
megaPrompt += `2. Report what you did
|
|
264
|
+
megaPrompt += `2. Report what you did briefly\n`;
|
|
310
265
|
megaPrompt += `3. Move to the next task\n\n`;
|
|
311
|
-
megaPrompt += `Do NOT skip any task. Complete the entire project
|
|
266
|
+
megaPrompt += `Do NOT skip any task. Complete the entire project.\n`;
|
|
312
267
|
|
|
313
|
-
// Write progress
|
|
314
|
-
progress
|
|
315
|
-
progress +=
|
|
268
|
+
// Write progress file
|
|
269
|
+
let progress = `# Progress: ${todoFile}\n\n`;
|
|
270
|
+
progress += `**Started:** ${new Date().toLocaleString()}\n`;
|
|
271
|
+
progress += `**Tasks:** ${totalTasks} | **Mode:** in-session\n\n`;
|
|
316
272
|
for (const r of results) {
|
|
317
273
|
progress += `- Task ${r.taskIndex}: ${r.title} [${r.agent}]\n`;
|
|
318
274
|
}
|
|
319
|
-
progress += `\n---\n\n## Summary\n\n`;
|
|
320
|
-
progress += `- **Mode:** in-session (single turn)\n`;
|
|
321
|
-
progress += `- **Tasks:** ${totalTasks}\n`;
|
|
322
|
-
progress += `- **Status:** sent to LLM\n`;
|
|
323
275
|
await writeFile(progressPath, progress, "utf-8");
|
|
324
276
|
|
|
325
|
-
// Send the mega-prompt
|
|
277
|
+
// Send the mega-prompt — LLM handles everything in current session
|
|
326
278
|
pi.sendUserMessage(megaPrompt);
|
|
327
279
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
notify(
|
|
331
|
-
`\n🏁 **Execution complete!** (${wave - 1} waves)\n` +
|
|
332
|
-
statusParts.join(" | ") + ` | ⏱️ ${(totalTime / 1000).toFixed(1)}s\n` +
|
|
333
|
-
`Progress: \`${progressFile}\``,
|
|
334
|
-
failedCount === 0 ? "info" : "warning"
|
|
335
|
-
);
|
|
280
|
+
notify(`📋 ${totalTasks} tasks sent to LLM. Progress: \`${progressFile}\``, "info");
|
|
336
281
|
|
|
337
282
|
return { results, progressFile };
|
|
338
283
|
}
|
|
@@ -472,34 +417,21 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
472
417
|
{ title: p.title, description: p.description, specSummary },
|
|
473
418
|
);
|
|
474
419
|
|
|
475
|
-
const
|
|
476
|
-
const failed = results.filter(r => r.status === "error").length;
|
|
477
|
-
const totalTime = results.reduce((sum, r) => sum + r.durationMs, 0);
|
|
420
|
+
const summary = `**🏁 Project "${p.title}" — ${p.tasks.length} tasks sent!**
|
|
478
421
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
**Plan files:** \`${specFile}\`, \`${todoFile}\`
|
|
422
|
+
**Plan:** \`${specFile}\`, \`${todoFile}\`
|
|
482
423
|
**Progress:** \`${progressFile}\`
|
|
483
424
|
|
|
484
|
-
**
|
|
485
|
-
|
|
486
|
-
- ❌ Failed: ${failed}
|
|
487
|
-
- ⏱️ Total time: ${(totalTime / 1000).toFixed(1)}s
|
|
488
|
-
|
|
489
|
-
**Task details:**
|
|
490
|
-
${results.map(r => {
|
|
491
|
-
const icon = r.status === "success" ? "✅" : "❌";
|
|
492
|
-
return `${icon} Task ${r.taskIndex}: ${r.title} [${r.agent}] (${(r.durationMs / 1000).toFixed(1)}s)`;
|
|
493
|
-
}).join("\n")}
|
|
425
|
+
**Tasks queued:**
|
|
426
|
+
${results.map(r => `📋 Task ${r.taskIndex}: ${r.title} [${r.agent}]`).join("\n")}
|
|
494
427
|
|
|
495
|
-
|
|
428
|
+
The LLM is now executing all tasks in-session.`;
|
|
496
429
|
|
|
497
430
|
return {
|
|
498
431
|
content: [{ type: "text", text: summary }],
|
|
499
432
|
details: {
|
|
500
433
|
specFile, todoFile, progressFile,
|
|
501
|
-
taskCount: p.tasks.length,
|
|
502
|
-
totalTimeMs: totalTime, title: p.title,
|
|
434
|
+
taskCount: p.tasks.length,
|
|
503
435
|
},
|
|
504
436
|
};
|
|
505
437
|
} catch (error) {
|