@phi-code-admin/phi-code 0.63.0 → 0.63.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/extensions/phi/memory.ts +27 -27
- package/extensions/phi/orchestrator.ts +104 -15
- package/package.json +2 -2
- package/scripts/postinstall.cjs +12 -1
package/extensions/phi/memory.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Now powered by sigma-memory package which provides:
|
|
5
5
|
* - NotesManager: Markdown files management
|
|
6
6
|
* - OntologyManager: Knowledge graph with entities and relations
|
|
7
|
-
* -
|
|
7
|
+
* - VectorStore: Embedded vector search (sql.js + local embeddings)
|
|
8
8
|
*
|
|
9
9
|
* Features:
|
|
10
|
-
* - memory_search: Unified search across notes, ontology, and
|
|
10
|
+
* - memory_search: Unified search across notes, ontology, and vector store
|
|
11
11
|
* - memory_write: Write content to memory files
|
|
12
12
|
* - memory_read: Read specific memory files or list available ones
|
|
13
13
|
* - memory_status: Get status of all memory subsystems
|
|
@@ -26,16 +26,12 @@ import { readFileSync } from "node:fs";
|
|
|
26
26
|
import { SigmaMemory } from "sigma-memory";
|
|
27
27
|
|
|
28
28
|
export default function memoryExtension(pi: ExtensionAPI) {
|
|
29
|
-
// Initialize sigma-memory
|
|
30
|
-
const sigmaMemory = new SigmaMemory(
|
|
31
|
-
// Default configuration, can be overridden
|
|
32
|
-
qmdEnabled: true,
|
|
33
|
-
qmdCommand: 'qmd'
|
|
34
|
-
});
|
|
29
|
+
// Initialize sigma-memory with embedded vector store
|
|
30
|
+
const sigmaMemory = new SigmaMemory();
|
|
35
31
|
|
|
36
|
-
// Initialize memory
|
|
37
|
-
sigmaMemory.init().catch(
|
|
38
|
-
|
|
32
|
+
// Initialize memory + vector store (lazy model download on first search)
|
|
33
|
+
sigmaMemory.init().catch(() => {
|
|
34
|
+
// Non-critical — memory works without vectors
|
|
39
35
|
});
|
|
40
36
|
|
|
41
37
|
/**
|
|
@@ -44,12 +40,15 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
44
40
|
pi.registerTool({
|
|
45
41
|
name: "memory_search",
|
|
46
42
|
label: "Memory Search",
|
|
47
|
-
description: "Search for content in memory using unified search (notes + ontology +
|
|
43
|
+
description: "Search for content in memory using unified search (notes + ontology + vector search)",
|
|
48
44
|
promptSnippet: "Search project memory (notes, ontology, vector search). ALWAYS call before answering questions about prior work, decisions, or project context.",
|
|
49
45
|
promptGuidelines: [
|
|
50
46
|
"Before answering questions about prior work, architecture, decisions, or project context: call memory_search first.",
|
|
51
47
|
"When starting work on a topic, search memory for existing notes and learnings.",
|
|
52
48
|
"After completing important work or learning something new, use memory_write to save it.",
|
|
49
|
+
"When a command fails or produces an unexpected error, document the error and fix in memory_write (self-improvement).",
|
|
50
|
+
"When the user corrects you, save the correction in memory_write so you never repeat the mistake.",
|
|
51
|
+
"After a significant debugging session, write a summary of root cause and solution to memory.",
|
|
53
52
|
],
|
|
54
53
|
parameters: Type.Object({
|
|
55
54
|
query: Type.String({ description: "Search query to find in memory" }),
|
|
@@ -96,7 +95,7 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
96
95
|
resultText += `Relation: ${data.type} (${data.from} → ${data.to})\n`;
|
|
97
96
|
resultText += `Properties: ${JSON.stringify(data.properties)}\n\n`;
|
|
98
97
|
}
|
|
99
|
-
} else if (result.source === '
|
|
98
|
+
} else if (result.source === 'vectors') {
|
|
100
99
|
const data = result.data;
|
|
101
100
|
resultText += `File: ${data.file} (line ${data.line})\n`;
|
|
102
101
|
resultText += `> ${data.content}\n\n`;
|
|
@@ -136,14 +135,18 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
136
135
|
const { content, file } = params as { content: string; file?: string };
|
|
137
136
|
|
|
138
137
|
try {
|
|
139
|
-
//
|
|
138
|
+
// Write to notes
|
|
140
139
|
sigmaMemory.notes.write(content, file);
|
|
141
|
-
|
|
142
140
|
const filename = file || new Date().toISOString().split('T')[0] + '.md';
|
|
141
|
+
|
|
142
|
+
// Auto-index in vector store (non-blocking)
|
|
143
|
+
sigmaMemory.vectors.addDocument(filename, content).catch(() => {
|
|
144
|
+
// Vector indexing failed silently — notes still saved
|
|
145
|
+
});
|
|
143
146
|
|
|
144
147
|
return {
|
|
145
|
-
content: [{ type: "text", text: `Content written to ${filename}` }],
|
|
146
|
-
details: { filename, contentLength: content.length }
|
|
148
|
+
content: [{ type: "text", text: `Content written to ${filename} (indexed for vector search)` }],
|
|
149
|
+
details: { filename, contentLength: content.length, vectorIndexed: true }
|
|
147
150
|
};
|
|
148
151
|
|
|
149
152
|
} catch (error) {
|
|
@@ -214,7 +217,7 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
214
217
|
pi.registerTool({
|
|
215
218
|
name: "memory_status",
|
|
216
219
|
label: "Memory Status",
|
|
217
|
-
description: "Get status of all memory subsystems (notes, ontology,
|
|
220
|
+
description: "Get status of all memory subsystems (notes, ontology, vector search)",
|
|
218
221
|
parameters: Type.Object({}),
|
|
219
222
|
|
|
220
223
|
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
@@ -236,14 +239,11 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
236
239
|
statusText += `- Entities by type: ${JSON.stringify(status.ontology.entitiesByType)}\n`;
|
|
237
240
|
statusText += `- Relations by type: ${JSON.stringify(status.ontology.relationsByType)}\n\n`;
|
|
238
241
|
|
|
239
|
-
//
|
|
240
|
-
statusText += `##
|
|
241
|
-
statusText += `-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
statusText += `- Chunks: ${status.qmd.status.chunks}\n`;
|
|
245
|
-
statusText += `- Last update: ${status.qmd.status.lastUpdate || 'Never'}\n`;
|
|
246
|
-
}
|
|
242
|
+
// Vector store status
|
|
243
|
+
statusText += `## Vector Search (embedded)\n`;
|
|
244
|
+
statusText += `- Documents: ${status.vectors.documentCount}\n`;
|
|
245
|
+
statusText += `- Chunks: ${status.vectors.chunkCount}\n`;
|
|
246
|
+
statusText += `- Last update: ${status.vectors.lastUpdate || 'Never'}\n`;
|
|
247
247
|
|
|
248
248
|
return {
|
|
249
249
|
content: [{ type: "text", text: statusText }],
|
|
@@ -292,7 +292,7 @@ export default function memoryExtension(pi: ExtensionAPI) {
|
|
|
292
292
|
const parts: string[] = [];
|
|
293
293
|
if (status.notes.count > 0) parts.push(`${status.notes.count} notes`);
|
|
294
294
|
if (status.ontology.entities > 0) parts.push(`${status.ontology.entities} entities`);
|
|
295
|
-
if (status.
|
|
295
|
+
if (status.vectors.chunkCount > 0) parts.push(`${status.vectors.chunkCount} vectors`);
|
|
296
296
|
if (parts.length > 0) {
|
|
297
297
|
ctx.ui.notify(`🧠 Memory: ${parts.join(", ")}`, "info");
|
|
298
298
|
}
|
|
@@ -450,23 +450,61 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
450
450
|
|
|
451
451
|
// ─── Orchestration State ─────────────────────────────────────────
|
|
452
452
|
|
|
453
|
+
interface AgentDef {
|
|
454
|
+
name: string;
|
|
455
|
+
tools: string[];
|
|
456
|
+
systemPrompt: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
453
459
|
interface OrchestratorPhase {
|
|
454
460
|
key: string;
|
|
455
461
|
label: string;
|
|
456
462
|
model: string;
|
|
457
463
|
fallback: string;
|
|
464
|
+
agent: AgentDef | null;
|
|
458
465
|
instruction: string;
|
|
459
466
|
}
|
|
460
467
|
|
|
461
468
|
let phaseQueue: OrchestratorPhase[] = [];
|
|
462
469
|
let orchestrationActive = false;
|
|
463
470
|
let idlePollTimer: ReturnType<typeof setInterval> | null = null;
|
|
471
|
+
let activeAgentPrompt: string | null = null;
|
|
472
|
+
let activeAgentTools: string[] | null = null;
|
|
473
|
+
let savedTools: string[] | null = null;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Parse agent .md file with YAML frontmatter
|
|
477
|
+
*/
|
|
478
|
+
function loadAgentDef(name: string): AgentDef | null {
|
|
479
|
+
const dirs = [
|
|
480
|
+
join(process.cwd(), ".phi", "agents"),
|
|
481
|
+
join(homedir(), ".phi", "agent", "agents"),
|
|
482
|
+
];
|
|
483
|
+
for (const dir of dirs) {
|
|
484
|
+
const filePath = join(dir, `${name}.md`);
|
|
485
|
+
try {
|
|
486
|
+
const content = readFileSync(filePath, "utf-8");
|
|
487
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
488
|
+
if (!fmMatch) continue;
|
|
489
|
+
const fields: Record<string, string> = {};
|
|
490
|
+
for (const line of fmMatch[1].split("\n")) {
|
|
491
|
+
const m = line.match(/^(\w+):\s*(.*)$/);
|
|
492
|
+
if (m) fields[m[1]] = m[2].trim();
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
name: fields.name || name,
|
|
496
|
+
tools: (fields.tools || "").split(",").map(t => t.trim()).filter(Boolean),
|
|
497
|
+
systemPrompt: fmMatch[2].trim(),
|
|
498
|
+
};
|
|
499
|
+
} catch { continue; }
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
464
503
|
|
|
465
504
|
/**
|
|
466
|
-
* Load routing config and build phase queue with model assignments.
|
|
505
|
+
* Load routing config and build phase queue with model assignments + agent definitions.
|
|
467
506
|
*/
|
|
468
507
|
function buildPhases(description: string): OrchestratorPhase[] {
|
|
469
|
-
// Read routing.json for model assignments
|
|
470
508
|
const routingPath = join(homedir(), ".phi", "agent", "routing.json");
|
|
471
509
|
let routing: any = { routes: {}, default: { model: "default" } };
|
|
472
510
|
try {
|
|
@@ -490,22 +528,27 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
490
528
|
return [
|
|
491
529
|
{
|
|
492
530
|
key: "explore", label: "🔍 Phase 1 — EXPLORE", model: explore.preferred, fallback: explore.fallback,
|
|
531
|
+
agent: loadAgentDef("explore"),
|
|
493
532
|
instruction: `Analyze the project requirements and existing codebase. Identify what exists, what's needed, and any constraints.\n\n**Project:** ${description}\n\nList files, read key ones, check dependencies. Return a structured summary.`,
|
|
494
533
|
},
|
|
495
534
|
{
|
|
496
535
|
key: "plan", label: "📐 Phase 2 — PLAN", model: plan.preferred, fallback: plan.fallback,
|
|
536
|
+
agent: loadAgentDef("plan"),
|
|
497
537
|
instruction: `Design the architecture for this project. Define file structure, tech choices, and implementation approach.\n\n**Project:** ${description}\n\nBe specific: list every file to create with its purpose.`,
|
|
498
538
|
},
|
|
499
539
|
{
|
|
500
540
|
key: "code", label: "💻 Phase 3 — CODE", model: code.preferred, fallback: code.fallback,
|
|
541
|
+
agent: loadAgentDef("code"),
|
|
501
542
|
instruction: `Implement the COMPLETE project. Create ALL files with production-quality code.\n\n**Project:** ${description}\n\n**Rules:**\n- Create every file needed\n- No placeholders, no TODOs, no stubs\n- Every function must be fully implemented\n- Follow the architecture from the previous planning phase`,
|
|
502
543
|
},
|
|
503
544
|
{
|
|
504
545
|
key: "test", label: "🧪 Phase 4 — TEST", model: test.preferred, fallback: test.fallback,
|
|
546
|
+
agent: loadAgentDef("test"),
|
|
505
547
|
instruction: `Test the implementation. Run the code, check for errors, verify it works.\n\n**Project:** ${description}\n\nFix any errors you find. Ensure the project runs correctly.`,
|
|
506
548
|
},
|
|
507
549
|
{
|
|
508
550
|
key: "review", label: "🔍 Phase 5 — REVIEW", model: review.preferred, fallback: review.fallback,
|
|
551
|
+
agent: loadAgentDef("review"),
|
|
509
552
|
instruction: `Review the code quality, security, and performance. Fix any issues.\n\n**Project:** ${description}\n\nCheck: error handling, edge cases, code style, documentation.`,
|
|
510
553
|
},
|
|
511
554
|
];
|
|
@@ -513,7 +556,6 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
513
556
|
|
|
514
557
|
/**
|
|
515
558
|
* Switch model for the current phase.
|
|
516
|
-
* Tries preferred model first, then fallback.
|
|
517
559
|
*/
|
|
518
560
|
async function switchModelForPhase(phase: OrchestratorPhase, ctx: any): Promise<string> {
|
|
519
561
|
const available = ctx.modelRegistry?.getAvailable?.() || [];
|
|
@@ -528,47 +570,90 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
528
570
|
return ctx.model?.id || phase.model;
|
|
529
571
|
}
|
|
530
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Activate agent for a phase: set system prompt + restrict tools.
|
|
575
|
+
*/
|
|
576
|
+
function activateAgent(phase: OrchestratorPhase, ctx: any) {
|
|
577
|
+
if (phase.agent) {
|
|
578
|
+
// Save current tools for restoration
|
|
579
|
+
if (!savedTools) {
|
|
580
|
+
savedTools = pi.getActiveTools();
|
|
581
|
+
}
|
|
582
|
+
// Set agent's system prompt (will be injected via before_agent_start)
|
|
583
|
+
activeAgentPrompt = phase.agent.systemPrompt;
|
|
584
|
+
// Restrict tools to agent's allowed tools
|
|
585
|
+
if (phase.agent.tools.length > 0) {
|
|
586
|
+
activeAgentTools = phase.agent.tools;
|
|
587
|
+
pi.setActiveTools(phase.agent.tools);
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
activeAgentPrompt = null;
|
|
591
|
+
activeAgentTools = null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Deactivate agent: restore tools, clear prompt override.
|
|
597
|
+
*/
|
|
598
|
+
function deactivateAgent() {
|
|
599
|
+
activeAgentPrompt = null;
|
|
600
|
+
activeAgentTools = null;
|
|
601
|
+
if (savedTools) {
|
|
602
|
+
pi.setActiveTools(savedTools);
|
|
603
|
+
savedTools = null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
531
607
|
/**
|
|
532
608
|
* Send the next phase in the queue.
|
|
533
|
-
* Called after the agent goes idle (previous phase complete).
|
|
534
609
|
*/
|
|
535
610
|
function sendNextPhase(ctx: any) {
|
|
536
611
|
if (phaseQueue.length === 0) {
|
|
537
|
-
// All phases done
|
|
538
612
|
orchestrationActive = false;
|
|
613
|
+
deactivateAgent();
|
|
539
614
|
ctx.ui.notify(`\n✅ **All 5 phases complete!**`, "info");
|
|
540
615
|
return;
|
|
541
616
|
}
|
|
542
617
|
|
|
543
618
|
const phase = phaseQueue.shift()!;
|
|
544
619
|
|
|
545
|
-
// Switch model and send
|
|
546
620
|
switchModelForPhase(phase, ctx).then((modelId) => {
|
|
547
|
-
|
|
621
|
+
activateAgent(phase, ctx);
|
|
622
|
+
const agentName = phase.agent?.name || phase.key;
|
|
623
|
+
ctx.ui.notify(`\n${phase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
|
|
548
624
|
setTimeout(() => pi.sendUserMessage(phase.instruction), 200);
|
|
549
625
|
});
|
|
550
626
|
}
|
|
551
627
|
|
|
628
|
+
// ─── System Prompt Injection — Agent personas ────────────────────
|
|
629
|
+
|
|
630
|
+
pi.on("before_agent_start", async (event, _ctx) => {
|
|
631
|
+
if (!orchestrationActive || !activeAgentPrompt) {
|
|
632
|
+
return { };
|
|
633
|
+
}
|
|
634
|
+
// Replace system prompt with the active agent's prompt
|
|
635
|
+
return { systemPrompt: activeAgentPrompt };
|
|
636
|
+
});
|
|
637
|
+
|
|
552
638
|
// ─── Output Event — Phase Chaining ───────────────────────────────
|
|
553
639
|
|
|
554
640
|
pi.on("output", async (_event, ctx) => {
|
|
555
641
|
if (!orchestrationActive || phaseQueue.length === 0) return;
|
|
556
642
|
|
|
557
|
-
// Debounce: clear any existing poll timer
|
|
558
643
|
if (idlePollTimer) {
|
|
559
644
|
clearInterval(idlePollTimer);
|
|
560
645
|
idlePollTimer = null;
|
|
561
646
|
}
|
|
562
647
|
|
|
563
|
-
// Poll for idle state (agent finishes tool calls + response)
|
|
564
648
|
let attempts = 0;
|
|
565
649
|
idlePollTimer = setInterval(() => {
|
|
566
650
|
attempts++;
|
|
567
|
-
if (attempts > 120) {
|
|
651
|
+
if (attempts > 120) {
|
|
568
652
|
clearInterval(idlePollTimer!);
|
|
569
653
|
idlePollTimer = null;
|
|
570
654
|
ctx.ui.notify("⚠️ Orchestrator timeout — phase took too long.", "warning");
|
|
571
655
|
orchestrationActive = false;
|
|
656
|
+
deactivateAgent();
|
|
572
657
|
return;
|
|
573
658
|
}
|
|
574
659
|
if (ctx.isIdle()) {
|
|
@@ -605,23 +690,27 @@ export default function orchestratorExtension(pi: ExtensionAPI) {
|
|
|
605
690
|
const specFile = `spec-${ts}.md`;
|
|
606
691
|
await writeFile(join(plansDir, specFile), `# ${description}\n\n**Created:** ${new Date().toLocaleString()}\n`, "utf-8");
|
|
607
692
|
|
|
608
|
-
// Build phases with model assignments
|
|
693
|
+
// Build phases with model assignments + agent definitions
|
|
609
694
|
const phases = buildPhases(description);
|
|
610
695
|
phaseQueue = phases.slice(1); // Queue phases 2-5
|
|
611
696
|
orchestrationActive = true;
|
|
612
697
|
const firstPhase = phases[0];
|
|
613
698
|
|
|
614
|
-
ctx.ui.notify(`📋 **Orchestrator started** — 5 phases with model routing\n`, "info");
|
|
699
|
+
ctx.ui.notify(`📋 **Orchestrator started** — 5 phases with model routing + agent roles\n`, "info");
|
|
615
700
|
|
|
616
701
|
// Show the plan
|
|
617
702
|
for (const p of phases) {
|
|
618
|
-
|
|
703
|
+
const agentName = p.agent?.name || p.key;
|
|
704
|
+
const toolCount = p.agent?.tools.length || 0;
|
|
705
|
+
ctx.ui.notify(` ${p.label} → \`${p.model}\` (agent: ${agentName}, ${toolCount} tools)`, "info");
|
|
619
706
|
}
|
|
620
707
|
ctx.ui.notify("", "info");
|
|
621
708
|
|
|
622
|
-
// Switch model and
|
|
709
|
+
// Switch model and activate agent for first phase
|
|
623
710
|
const modelId = await switchModelForPhase(firstPhase, ctx);
|
|
624
|
-
|
|
711
|
+
activateAgent(firstPhase, ctx);
|
|
712
|
+
const agentName = firstPhase.agent?.name || firstPhase.key;
|
|
713
|
+
ctx.ui.notify(`${firstPhase.label} → \`${modelId}\` (agent: ${agentName})`, "info");
|
|
625
714
|
setTimeout(() => pi.sendUserMessage(firstPhase.instruction), 200);
|
|
626
715
|
},
|
|
627
716
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phi-code-admin/phi-code",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.2",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"phi-code-tui": "^0.56.3",
|
|
62
62
|
"proper-lockfile": "^4.1.2",
|
|
63
63
|
"sigma-agents": "^0.1.6",
|
|
64
|
-
"sigma-memory": "
|
|
64
|
+
"sigma-memory": "0.2.0",
|
|
65
65
|
"sigma-skills": "^0.1.2",
|
|
66
66
|
"strip-ansi": "^7.1.0",
|
|
67
67
|
"undici": "^7.19.1",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -58,7 +58,18 @@ for (const pkg of sigmaPackages) {
|
|
|
58
58
|
createLink(srcPkg, destLink, pkg);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// 3. Ensure
|
|
61
|
+
// 3. Ensure memory directories exist (vector store DB created on first use)
|
|
62
|
+
const memoryDir = join(homedir(), ".phi", "memory");
|
|
63
|
+
const memoryNotesDir = join(memoryDir, "notes");
|
|
64
|
+
const memoryOntologyDir = join(memoryDir, "ontology");
|
|
65
|
+
for (const dir of [memoryDir, memoryNotesDir, memoryOntologyDir]) {
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
console.log(` Φ Created ${dir}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 4. Ensure settings.json has quietStartup: true
|
|
62
73
|
const settingsPath = join(agentDir, "settings.json");
|
|
63
74
|
try {
|
|
64
75
|
let settings = {};
|