@aman_asmuei/aman-agent 0.24.3 → 0.25.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/README.md +292 -26
- package/dist/index.js +748 -171
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1387,18 +1387,18 @@ var McpManager = class {
|
|
|
1387
1387
|
|
|
1388
1388
|
// src/agent.ts
|
|
1389
1389
|
import * as readline from "readline";
|
|
1390
|
-
import
|
|
1391
|
-
import
|
|
1392
|
-
import
|
|
1390
|
+
import fs18 from "fs";
|
|
1391
|
+
import path18 from "path";
|
|
1392
|
+
import os17 from "os";
|
|
1393
1393
|
import pc7 from "picocolors";
|
|
1394
1394
|
import { marked } from "marked";
|
|
1395
1395
|
import { markedTerminal } from "marked-terminal";
|
|
1396
1396
|
import logUpdate from "log-update";
|
|
1397
1397
|
|
|
1398
1398
|
// src/commands.ts
|
|
1399
|
-
import
|
|
1400
|
-
import
|
|
1401
|
-
import
|
|
1399
|
+
import fs15 from "fs";
|
|
1400
|
+
import path15 from "path";
|
|
1401
|
+
import os14 from "os";
|
|
1402
1402
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1403
1403
|
import pc5 from "picocolors";
|
|
1404
1404
|
|
|
@@ -2361,8 +2361,8 @@ import pc3 from "picocolors";
|
|
|
2361
2361
|
// src/hooks.ts
|
|
2362
2362
|
import pc2 from "picocolors";
|
|
2363
2363
|
import * as p2 from "@clack/prompts";
|
|
2364
|
-
import
|
|
2365
|
-
import
|
|
2364
|
+
import fs12 from "fs";
|
|
2365
|
+
import path12 from "path";
|
|
2366
2366
|
|
|
2367
2367
|
// src/personality.ts
|
|
2368
2368
|
var FRUSTRATION_SIGNALS = [
|
|
@@ -2535,6 +2535,382 @@ async function syncPersonalityToCore(state, mcpManager) {
|
|
|
2535
2535
|
}
|
|
2536
2536
|
}
|
|
2537
2537
|
|
|
2538
|
+
// src/postmortem.ts
|
|
2539
|
+
import fs11 from "fs/promises";
|
|
2540
|
+
import path11 from "path";
|
|
2541
|
+
import os11 from "os";
|
|
2542
|
+
|
|
2543
|
+
// src/observation.ts
|
|
2544
|
+
import fs10 from "fs/promises";
|
|
2545
|
+
import path10 from "path";
|
|
2546
|
+
import os10 from "os";
|
|
2547
|
+
var STAT_MAP = {
|
|
2548
|
+
tool_call: "toolCalls",
|
|
2549
|
+
tool_error: "toolErrors",
|
|
2550
|
+
topic_shift: "topicShifts",
|
|
2551
|
+
blocker: "blockers",
|
|
2552
|
+
milestone: "milestones",
|
|
2553
|
+
file_change: "fileChanges"
|
|
2554
|
+
};
|
|
2555
|
+
function defaultObservationsDir() {
|
|
2556
|
+
return path10.join(os10.homedir(), ".acore", "observations");
|
|
2557
|
+
}
|
|
2558
|
+
function createObservationSession(sessionId) {
|
|
2559
|
+
return {
|
|
2560
|
+
sessionId,
|
|
2561
|
+
startedAt: Date.now(),
|
|
2562
|
+
events: [],
|
|
2563
|
+
paused: false,
|
|
2564
|
+
stats: {
|
|
2565
|
+
toolCalls: 0,
|
|
2566
|
+
toolErrors: 0,
|
|
2567
|
+
topicShifts: 0,
|
|
2568
|
+
blockers: 0,
|
|
2569
|
+
milestones: 0,
|
|
2570
|
+
fileChanges: 0
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
function recordEvent(session, event) {
|
|
2575
|
+
if (session.paused) return;
|
|
2576
|
+
const full = { ...event, timestamp: Date.now() };
|
|
2577
|
+
session.events.push(full);
|
|
2578
|
+
const statKey = STAT_MAP[event.type];
|
|
2579
|
+
if (statKey) {
|
|
2580
|
+
session.stats[statKey]++;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function pauseObservation(session) {
|
|
2584
|
+
session.paused = true;
|
|
2585
|
+
}
|
|
2586
|
+
function resumeObservation(session) {
|
|
2587
|
+
session.paused = false;
|
|
2588
|
+
}
|
|
2589
|
+
async function flushEvents(session, dir) {
|
|
2590
|
+
if (session.events.length === 0) return;
|
|
2591
|
+
const obsDir = dir ?? defaultObservationsDir();
|
|
2592
|
+
await fs10.mkdir(obsDir, { recursive: true });
|
|
2593
|
+
const filePath = path10.join(obsDir, `${session.sessionId}.jsonl`);
|
|
2594
|
+
const lines = session.events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2595
|
+
await fs10.appendFile(filePath, lines, "utf-8");
|
|
2596
|
+
session.events.length = 0;
|
|
2597
|
+
}
|
|
2598
|
+
async function readObservationEvents(sessionId, dir) {
|
|
2599
|
+
const obsDir = dir ?? defaultObservationsDir();
|
|
2600
|
+
const filePath = path10.join(obsDir, `${sessionId}.jsonl`);
|
|
2601
|
+
try {
|
|
2602
|
+
const content = await fs10.readFile(filePath, "utf-8");
|
|
2603
|
+
return content.trim().split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
2604
|
+
} catch {
|
|
2605
|
+
return [];
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
function getSessionStats(session) {
|
|
2609
|
+
const elapsed = Math.round((Date.now() - session.startedAt) / 6e4);
|
|
2610
|
+
const s = session.stats;
|
|
2611
|
+
const parts = [
|
|
2612
|
+
`Session: ${elapsed} min`,
|
|
2613
|
+
`Tools: ${s.toolCalls} calls (${s.toolErrors} error${s.toolErrors !== 1 ? "s" : ""})`,
|
|
2614
|
+
`Files: ${s.fileChanges} changed`,
|
|
2615
|
+
`Blockers: ${s.blockers}`,
|
|
2616
|
+
`Milestones: ${s.milestones}`
|
|
2617
|
+
];
|
|
2618
|
+
if (s.topicShifts > 0) parts.push(`Topic shifts: ${s.topicShifts}`);
|
|
2619
|
+
if (session.paused) parts.push("(paused)");
|
|
2620
|
+
return parts.join(" | ");
|
|
2621
|
+
}
|
|
2622
|
+
async function cleanupOldObservations(dir, maxAgeDays = 30) {
|
|
2623
|
+
const obsDir = dir ?? defaultObservationsDir();
|
|
2624
|
+
try {
|
|
2625
|
+
const files = await fs10.readdir(obsDir);
|
|
2626
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
2627
|
+
for (const file of files) {
|
|
2628
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
2629
|
+
const filePath = path10.join(obsDir, file);
|
|
2630
|
+
const stat = await fs10.stat(filePath);
|
|
2631
|
+
if (stat.mtimeMs < cutoff) {
|
|
2632
|
+
await fs10.unlink(filePath);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
} catch {
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
function detectTopicShift(recentMessages, previousMessages) {
|
|
2639
|
+
const extractKeywords = (msgs) => {
|
|
2640
|
+
const words = msgs.join(" ").toLowerCase().split(/\W+/).filter((w) => w.length > 3);
|
|
2641
|
+
return new Set(words);
|
|
2642
|
+
};
|
|
2643
|
+
const recent = extractKeywords(recentMessages);
|
|
2644
|
+
const previous = extractKeywords(previousMessages);
|
|
2645
|
+
if (previous.size === 0) return { shifted: false, newTopics: [] };
|
|
2646
|
+
let overlap = 0;
|
|
2647
|
+
for (const word of recent) {
|
|
2648
|
+
if (previous.has(word)) overlap++;
|
|
2649
|
+
}
|
|
2650
|
+
const overlapRatio = previous.size > 0 ? overlap / previous.size : 1;
|
|
2651
|
+
const shifted = overlapRatio < 0.3;
|
|
2652
|
+
const newTopics = shifted ? [...recent].filter((w) => !previous.has(w)).slice(0, 5) : [];
|
|
2653
|
+
return { shifted, newTopics };
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
// src/postmortem.ts
|
|
2657
|
+
function defaultPostmortemsDir() {
|
|
2658
|
+
return path11.join(os11.homedir(), ".acore", "postmortems");
|
|
2659
|
+
}
|
|
2660
|
+
function defaultObservationsDir2() {
|
|
2661
|
+
return path11.join(os11.homedir(), ".acore", "observations");
|
|
2662
|
+
}
|
|
2663
|
+
function shouldAutoPostmortem(session, messages) {
|
|
2664
|
+
if (messages.length < 6) return false;
|
|
2665
|
+
const durationMs = Date.now() - session.startedAt;
|
|
2666
|
+
return session.stats.toolErrors >= 3 || session.stats.blockers >= 2 || durationMs > 60 * 6e4 || hasAbandonedPlanSteps(messages) || hasSustainedFrustration(session, 5);
|
|
2667
|
+
}
|
|
2668
|
+
function hasAbandonedPlanSteps(messages) {
|
|
2669
|
+
const text3 = messages.map((m) => typeof m.content === "string" ? m.content : "").join("\n");
|
|
2670
|
+
const unchecked = (text3.match(/- \[ \]/g) ?? []).length;
|
|
2671
|
+
const checked = (text3.match(/- \[x\]/g) ?? []).length;
|
|
2672
|
+
return checked > 0 && unchecked > 0 && unchecked >= checked;
|
|
2673
|
+
}
|
|
2674
|
+
function hasSustainedFrustration(session, threshold) {
|
|
2675
|
+
return session.stats.blockers >= threshold;
|
|
2676
|
+
}
|
|
2677
|
+
function messageContentToText(content) {
|
|
2678
|
+
if (typeof content === "string") return content;
|
|
2679
|
+
return content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
2680
|
+
}
|
|
2681
|
+
var POSTMORTEM_PROMPT = `Analyze this session and generate a structured post-mortem report.
|
|
2682
|
+
Return ONLY valid JSON matching this schema (no markdown, no explanation):
|
|
2683
|
+
|
|
2684
|
+
{
|
|
2685
|
+
"summary": "2-3 sentence overview",
|
|
2686
|
+
"goals": ["what the user tried to accomplish"],
|
|
2687
|
+
"completed": ["what actually got done"],
|
|
2688
|
+
"blockers": ["what caused friction"],
|
|
2689
|
+
"decisions": ["key choices made with rationale"],
|
|
2690
|
+
"sentimentArc": "how mood evolved during session",
|
|
2691
|
+
"patterns": ["recurring behaviors worth remembering for future sessions"],
|
|
2692
|
+
"recommendations": ["actionable suggestions for next session"]
|
|
2693
|
+
}`;
|
|
2694
|
+
async function generatePostmortemReport(sessionId, messages, session, client, obsDir) {
|
|
2695
|
+
try {
|
|
2696
|
+
const events = await readObservationEvents(sessionId, obsDir ?? defaultObservationsDir2());
|
|
2697
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
2698
|
+
const fileChanges = [];
|
|
2699
|
+
const topicProgression = [];
|
|
2700
|
+
for (const event of events) {
|
|
2701
|
+
if (event.type === "tool_call") {
|
|
2702
|
+
const name = event.data.tool ?? "unknown";
|
|
2703
|
+
const entry = toolMap.get(name) ?? { calls: 0, errors: 0 };
|
|
2704
|
+
entry.calls++;
|
|
2705
|
+
toolMap.set(name, entry);
|
|
2706
|
+
} else if (event.type === "tool_error") {
|
|
2707
|
+
const name = event.data.tool ?? "unknown";
|
|
2708
|
+
const entry = toolMap.get(name) ?? { calls: 0, errors: 0 };
|
|
2709
|
+
entry.errors++;
|
|
2710
|
+
toolMap.set(name, entry);
|
|
2711
|
+
} else if (event.type === "file_change") {
|
|
2712
|
+
const p4 = event.data.path ?? "unknown";
|
|
2713
|
+
if (!fileChanges.includes(p4)) fileChanges.push(p4);
|
|
2714
|
+
} else if (event.type === "topic_shift") {
|
|
2715
|
+
const topics = event.data.newTopics ?? [];
|
|
2716
|
+
topicProgression.push(...topics);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
const toolUsage = [...toolMap.entries()].map(([name, { calls, errors }]) => ({
|
|
2720
|
+
name,
|
|
2721
|
+
count: calls,
|
|
2722
|
+
errorRate: calls > 0 ? Math.round(errors / calls * 100) / 100 : 0
|
|
2723
|
+
}));
|
|
2724
|
+
const recentMessages = messages.slice(-20).map((m) => {
|
|
2725
|
+
const text4 = messageContentToText(m.content);
|
|
2726
|
+
return `${m.role}: ${text4.slice(0, 200)}`;
|
|
2727
|
+
});
|
|
2728
|
+
const obsSnapshot = events.slice(-30).map((e) => `[${e.type}] ${e.summary}`);
|
|
2729
|
+
const durationMin = Math.round((Date.now() - session.startedAt) / 6e4);
|
|
2730
|
+
const prompt = `${POSTMORTEM_PROMPT}
|
|
2731
|
+
|
|
2732
|
+
Session ID: ${sessionId}
|
|
2733
|
+
Duration: ${durationMin} minutes
|
|
2734
|
+
Turns: ${messages.length}
|
|
2735
|
+
Tool calls: ${session.stats.toolCalls} (${session.stats.toolErrors} errors)
|
|
2736
|
+
Blockers: ${session.stats.blockers}
|
|
2737
|
+
Milestones: ${session.stats.milestones}
|
|
2738
|
+
|
|
2739
|
+
Recent messages:
|
|
2740
|
+
${recentMessages.join("\n")}
|
|
2741
|
+
|
|
2742
|
+
Observations:
|
|
2743
|
+
${obsSnapshot.join("\n")}`;
|
|
2744
|
+
const response = await client.chat(
|
|
2745
|
+
"You are a session analyst. Output only valid JSON.",
|
|
2746
|
+
[{ role: "user", content: prompt }],
|
|
2747
|
+
() => {
|
|
2748
|
+
}
|
|
2749
|
+
// no-op onChunk — postmortem runs silently
|
|
2750
|
+
);
|
|
2751
|
+
const text3 = messageContentToText(response.message.content);
|
|
2752
|
+
const jsonMatch = text3.match(/\{[\s\S]*\}/);
|
|
2753
|
+
if (!jsonMatch) {
|
|
2754
|
+
log.debug("postmortem", "LLM returned non-JSON response");
|
|
2755
|
+
return null;
|
|
2756
|
+
}
|
|
2757
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
2758
|
+
return {
|
|
2759
|
+
sessionId,
|
|
2760
|
+
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
2761
|
+
duration: durationMin,
|
|
2762
|
+
turnCount: messages.length,
|
|
2763
|
+
summary: parsed.summary ?? "",
|
|
2764
|
+
goals: parsed.goals ?? [],
|
|
2765
|
+
completed: parsed.completed ?? [],
|
|
2766
|
+
blockers: parsed.blockers ?? [],
|
|
2767
|
+
decisions: parsed.decisions ?? [],
|
|
2768
|
+
toolUsage,
|
|
2769
|
+
fileChanges,
|
|
2770
|
+
topicProgression: [...new Set(topicProgression)],
|
|
2771
|
+
sentimentArc: parsed.sentimentArc ?? "",
|
|
2772
|
+
patterns: parsed.patterns ?? [],
|
|
2773
|
+
recommendations: parsed.recommendations ?? []
|
|
2774
|
+
};
|
|
2775
|
+
} catch (err) {
|
|
2776
|
+
log.debug("postmortem", "Failed to generate post-mortem", err);
|
|
2777
|
+
return null;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
function formatPostmortemMarkdown(report) {
|
|
2781
|
+
const lines = [
|
|
2782
|
+
`# Post-Mortem: ${report.date}`,
|
|
2783
|
+
"",
|
|
2784
|
+
`**Session:** ${report.sessionId} | **Duration:** ${report.duration} min | **Turns:** ${report.turnCount}`,
|
|
2785
|
+
"",
|
|
2786
|
+
"## Summary",
|
|
2787
|
+
report.summary,
|
|
2788
|
+
""
|
|
2789
|
+
];
|
|
2790
|
+
if (report.goals.length > 0) {
|
|
2791
|
+
lines.push("## Goals");
|
|
2792
|
+
report.goals.forEach((g) => lines.push(`- ${g}`));
|
|
2793
|
+
lines.push("");
|
|
2794
|
+
}
|
|
2795
|
+
if (report.completed.length > 0) {
|
|
2796
|
+
lines.push("## Completed");
|
|
2797
|
+
report.completed.forEach((c) => lines.push(`- [x] ${c}`));
|
|
2798
|
+
lines.push("");
|
|
2799
|
+
}
|
|
2800
|
+
if (report.blockers.length > 0) {
|
|
2801
|
+
lines.push("## Blockers");
|
|
2802
|
+
report.blockers.forEach((b) => lines.push(`- ${b}`));
|
|
2803
|
+
lines.push("");
|
|
2804
|
+
}
|
|
2805
|
+
if (report.decisions.length > 0) {
|
|
2806
|
+
lines.push("## Decisions");
|
|
2807
|
+
report.decisions.forEach((d) => lines.push(`- ${d}`));
|
|
2808
|
+
lines.push("");
|
|
2809
|
+
}
|
|
2810
|
+
if (report.toolUsage.length > 0) {
|
|
2811
|
+
lines.push("## Tool Usage");
|
|
2812
|
+
lines.push("| Tool | Calls | Error Rate |");
|
|
2813
|
+
lines.push("|------|-------|------------|");
|
|
2814
|
+
report.toolUsage.forEach(
|
|
2815
|
+
(t) => lines.push(`| ${t.name} | ${t.count} | ${Math.round(t.errorRate * 100)}% |`)
|
|
2816
|
+
);
|
|
2817
|
+
lines.push("");
|
|
2818
|
+
}
|
|
2819
|
+
if (report.fileChanges.length > 0) {
|
|
2820
|
+
lines.push("## Files Changed");
|
|
2821
|
+
report.fileChanges.forEach((f) => lines.push(`- \`${f}\``));
|
|
2822
|
+
lines.push("");
|
|
2823
|
+
}
|
|
2824
|
+
if (report.topicProgression.length > 0) {
|
|
2825
|
+
lines.push(`## Topics`);
|
|
2826
|
+
lines.push(report.topicProgression.join(" \u2192 "));
|
|
2827
|
+
lines.push("");
|
|
2828
|
+
}
|
|
2829
|
+
if (report.sentimentArc) {
|
|
2830
|
+
lines.push("## Sentiment Arc");
|
|
2831
|
+
lines.push(report.sentimentArc);
|
|
2832
|
+
lines.push("");
|
|
2833
|
+
}
|
|
2834
|
+
if (report.patterns.length > 0) {
|
|
2835
|
+
lines.push("## Patterns");
|
|
2836
|
+
report.patterns.forEach((p4) => lines.push(`- ${p4}`));
|
|
2837
|
+
lines.push("");
|
|
2838
|
+
}
|
|
2839
|
+
if (report.recommendations.length > 0) {
|
|
2840
|
+
lines.push("## Recommendations");
|
|
2841
|
+
report.recommendations.forEach((r) => lines.push(`- ${r}`));
|
|
2842
|
+
lines.push("");
|
|
2843
|
+
}
|
|
2844
|
+
return lines.join("\n");
|
|
2845
|
+
}
|
|
2846
|
+
async function savePostmortem(report, dir) {
|
|
2847
|
+
const pmDir = dir ?? defaultPostmortemsDir();
|
|
2848
|
+
await fs11.mkdir(pmDir, { recursive: true });
|
|
2849
|
+
const shortId = report.sessionId.slice(0, 4);
|
|
2850
|
+
const fileName = `${report.date}-${shortId}.md`;
|
|
2851
|
+
const filePath = path11.join(pmDir, fileName);
|
|
2852
|
+
const markdown = formatPostmortemMarkdown(report);
|
|
2853
|
+
await fs11.writeFile(filePath, markdown, "utf-8");
|
|
2854
|
+
return filePath;
|
|
2855
|
+
}
|
|
2856
|
+
async function listPostmortems(dir) {
|
|
2857
|
+
const pmDir = dir ?? defaultPostmortemsDir();
|
|
2858
|
+
try {
|
|
2859
|
+
const files = await fs11.readdir(pmDir);
|
|
2860
|
+
return files.filter((f) => f.endsWith(".md")).sort().reverse();
|
|
2861
|
+
} catch {
|
|
2862
|
+
return [];
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
async function readPostmortem(name, dir) {
|
|
2866
|
+
const pmDir = dir ?? defaultPostmortemsDir();
|
|
2867
|
+
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
2868
|
+
try {
|
|
2869
|
+
return await fs11.readFile(path11.join(pmDir, fileName), "utf-8");
|
|
2870
|
+
} catch {
|
|
2871
|
+
return null;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
async function analyzePostmortemRange(sinceDays, client, dir) {
|
|
2875
|
+
const pmDir = dir ?? defaultPostmortemsDir();
|
|
2876
|
+
try {
|
|
2877
|
+
const files = await listPostmortems(pmDir);
|
|
2878
|
+
const cutoffDate = new Date(Date.now() - sinceDays * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
|
|
2879
|
+
const recentFiles = files.filter((f) => f >= cutoffDate);
|
|
2880
|
+
if (recentFiles.length === 0) return "No post-mortems found in the specified range.";
|
|
2881
|
+
const contents = [];
|
|
2882
|
+
for (const f of recentFiles.slice(0, 10)) {
|
|
2883
|
+
const content = await readPostmortem(f, pmDir);
|
|
2884
|
+
if (content) contents.push(content);
|
|
2885
|
+
}
|
|
2886
|
+
const response = await client.chat(
|
|
2887
|
+
"You are a session analyst. Analyze these post-mortems and identify trends.",
|
|
2888
|
+
[
|
|
2889
|
+
{
|
|
2890
|
+
role: "user",
|
|
2891
|
+
content: `Analyze these ${contents.length} post-mortem reports from the last ${sinceDays} days. Identify:
|
|
2892
|
+
1. Recurring blockers
|
|
2893
|
+
2. Productivity patterns
|
|
2894
|
+
3. Tool reliability issues
|
|
2895
|
+
4. Topic continuity across sessions
|
|
2896
|
+
5. Actionable recommendations
|
|
2897
|
+
|
|
2898
|
+
Reports:
|
|
2899
|
+
${contents.join("\n\n---\n\n")}`
|
|
2900
|
+
}
|
|
2901
|
+
],
|
|
2902
|
+
() => {
|
|
2903
|
+
}
|
|
2904
|
+
// no-op onChunk
|
|
2905
|
+
);
|
|
2906
|
+
const text3 = messageContentToText(response.message.content);
|
|
2907
|
+
return text3 || null;
|
|
2908
|
+
} catch (err) {
|
|
2909
|
+
log.debug("postmortem", "Failed to analyze range", err);
|
|
2910
|
+
return null;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2538
2914
|
// src/hooks.ts
|
|
2539
2915
|
function getTimeContext() {
|
|
2540
2916
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2767,7 +3143,7 @@ async function onWorkflowMatch(userInput, ctx) {
|
|
|
2767
3143
|
isHookCall = false;
|
|
2768
3144
|
}
|
|
2769
3145
|
}
|
|
2770
|
-
async function onSessionEnd(ctx, messages, sessionId) {
|
|
3146
|
+
async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
2771
3147
|
try {
|
|
2772
3148
|
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
2773
3149
|
console.log(pc2.dim("\n Saving conversation to memory..."));
|
|
@@ -2803,10 +3179,10 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2803
3179
|
}
|
|
2804
3180
|
console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
2805
3181
|
}
|
|
2806
|
-
const projectContextPath =
|
|
2807
|
-
if (
|
|
3182
|
+
const projectContextPath = path12.join(process.cwd(), ".acore", "context.md");
|
|
3183
|
+
if (fs12.existsSync(projectContextPath) && messages.length > 2) {
|
|
2808
3184
|
try {
|
|
2809
|
-
let contextContent =
|
|
3185
|
+
let contextContent = fs12.readFileSync(projectContextPath, "utf-8");
|
|
2810
3186
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2811
3187
|
let lastUserMsg = "";
|
|
2812
3188
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -2824,7 +3200,7 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2824
3200
|
- Recent decisions: [see memory]
|
|
2825
3201
|
- Temp notes: [cleared]`;
|
|
2826
3202
|
contextContent = contextContent.replace(sessionPattern, newSession);
|
|
2827
|
-
|
|
3203
|
+
fs12.writeFileSync(projectContextPath, contextContent, "utf-8");
|
|
2828
3204
|
log.debug("hooks", `Updated project context: ${projectContextPath}`);
|
|
2829
3205
|
}
|
|
2830
3206
|
} catch (err) {
|
|
@@ -2877,6 +3253,37 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2877
3253
|
}
|
|
2878
3254
|
}
|
|
2879
3255
|
}
|
|
3256
|
+
if (ctx.config.autoPostmortem !== false && observationSession && shouldAutoPostmortem(observationSession, messages)) {
|
|
3257
|
+
try {
|
|
3258
|
+
const client = ctx.llmClient;
|
|
3259
|
+
if (client) {
|
|
3260
|
+
const report = await generatePostmortemReport(
|
|
3261
|
+
sessionId,
|
|
3262
|
+
messages,
|
|
3263
|
+
observationSession,
|
|
3264
|
+
client
|
|
3265
|
+
);
|
|
3266
|
+
if (report) {
|
|
3267
|
+
const filePath = await savePostmortem(report);
|
|
3268
|
+
console.log(pc2.dim(`
|
|
3269
|
+
Post-mortem saved \u2192 ${filePath}`));
|
|
3270
|
+
for (const pattern of report.patterns) {
|
|
3271
|
+
try {
|
|
3272
|
+
await memoryStore({
|
|
3273
|
+
content: pattern,
|
|
3274
|
+
type: "pattern",
|
|
3275
|
+
tags: ["postmortem", "auto"],
|
|
3276
|
+
confidence: 0.7
|
|
3277
|
+
});
|
|
3278
|
+
} catch {
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
} catch (err) {
|
|
3284
|
+
log.debug("hooks", "auto post-mortem failed", err);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
2880
3287
|
} catch (err) {
|
|
2881
3288
|
log.warn("hooks", "session end hook failed", err);
|
|
2882
3289
|
}
|
|
@@ -3030,43 +3437,43 @@ async function delegatePipeline(steps, initialInput, client, mcpManager, options
|
|
|
3030
3437
|
}
|
|
3031
3438
|
|
|
3032
3439
|
// src/teams.ts
|
|
3033
|
-
import
|
|
3034
|
-
import
|
|
3035
|
-
import
|
|
3440
|
+
import fs13 from "fs";
|
|
3441
|
+
import path13 from "path";
|
|
3442
|
+
import os12 from "os";
|
|
3036
3443
|
import pc4 from "picocolors";
|
|
3037
3444
|
function getTeamsDir() {
|
|
3038
|
-
return
|
|
3445
|
+
return path13.join(os12.homedir(), ".acore", "teams");
|
|
3039
3446
|
}
|
|
3040
3447
|
function ensureTeamsDir() {
|
|
3041
3448
|
const dir = getTeamsDir();
|
|
3042
|
-
if (!
|
|
3449
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
3043
3450
|
return dir;
|
|
3044
3451
|
}
|
|
3045
3452
|
function teamPath(name) {
|
|
3046
3453
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
3047
|
-
return
|
|
3454
|
+
return path13.join(ensureTeamsDir(), `${slug}.json`);
|
|
3048
3455
|
}
|
|
3049
3456
|
function createTeam(team) {
|
|
3050
3457
|
const fp = teamPath(team.name);
|
|
3051
|
-
|
|
3458
|
+
fs13.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
|
|
3052
3459
|
}
|
|
3053
3460
|
function loadTeam(name) {
|
|
3054
3461
|
const fp = teamPath(name);
|
|
3055
|
-
if (!
|
|
3462
|
+
if (!fs13.existsSync(fp)) return null;
|
|
3056
3463
|
try {
|
|
3057
|
-
return JSON.parse(
|
|
3464
|
+
return JSON.parse(fs13.readFileSync(fp, "utf-8"));
|
|
3058
3465
|
} catch {
|
|
3059
3466
|
return null;
|
|
3060
3467
|
}
|
|
3061
3468
|
}
|
|
3062
3469
|
function listTeams() {
|
|
3063
3470
|
const dir = getTeamsDir();
|
|
3064
|
-
if (!
|
|
3471
|
+
if (!fs13.existsSync(dir)) return [];
|
|
3065
3472
|
const teams = [];
|
|
3066
|
-
for (const file of
|
|
3473
|
+
for (const file of fs13.readdirSync(dir)) {
|
|
3067
3474
|
if (!file.endsWith(".json")) continue;
|
|
3068
3475
|
try {
|
|
3069
|
-
const content =
|
|
3476
|
+
const content = fs13.readFileSync(path13.join(dir, file), "utf-8");
|
|
3070
3477
|
teams.push(JSON.parse(content));
|
|
3071
3478
|
} catch {
|
|
3072
3479
|
}
|
|
@@ -3075,8 +3482,8 @@ function listTeams() {
|
|
|
3075
3482
|
}
|
|
3076
3483
|
function deleteTeam(name) {
|
|
3077
3484
|
const fp = teamPath(name);
|
|
3078
|
-
if (!
|
|
3079
|
-
|
|
3485
|
+
if (!fs13.existsSync(fp)) return false;
|
|
3486
|
+
fs13.unlinkSync(fp);
|
|
3080
3487
|
return true;
|
|
3081
3488
|
}
|
|
3082
3489
|
async function runTeam(team, task, client, mcpManager, tools) {
|
|
@@ -3302,23 +3709,23 @@ var BUILT_IN_TEAMS = [
|
|
|
3302
3709
|
];
|
|
3303
3710
|
|
|
3304
3711
|
// src/plans.ts
|
|
3305
|
-
import
|
|
3306
|
-
import
|
|
3307
|
-
import
|
|
3712
|
+
import fs14 from "fs";
|
|
3713
|
+
import path14 from "path";
|
|
3714
|
+
import os13 from "os";
|
|
3308
3715
|
function getPlansDir() {
|
|
3309
|
-
const localDir =
|
|
3310
|
-
const localAcore =
|
|
3311
|
-
if (
|
|
3312
|
-
return
|
|
3716
|
+
const localDir = path14.join(process.cwd(), ".acore", "plans");
|
|
3717
|
+
const localAcore = path14.join(process.cwd(), ".acore");
|
|
3718
|
+
if (fs14.existsSync(localAcore)) return localDir;
|
|
3719
|
+
return path14.join(os13.homedir(), ".acore", "plans");
|
|
3313
3720
|
}
|
|
3314
3721
|
function ensurePlansDir() {
|
|
3315
3722
|
const dir = getPlansDir();
|
|
3316
|
-
if (!
|
|
3723
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
3317
3724
|
return dir;
|
|
3318
3725
|
}
|
|
3319
3726
|
function planPath(name) {
|
|
3320
3727
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3321
|
-
return
|
|
3728
|
+
return path14.join(ensurePlansDir(), `${slug}.md`);
|
|
3322
3729
|
}
|
|
3323
3730
|
function serializePlan(plan) {
|
|
3324
3731
|
const lines = [];
|
|
@@ -3344,7 +3751,7 @@ function parsePlan(content, filePath) {
|
|
|
3344
3751
|
const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
|
|
3345
3752
|
const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
|
|
3346
3753
|
const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
|
|
3347
|
-
const name = nameMatch?.[1]?.trim() ||
|
|
3754
|
+
const name = nameMatch?.[1]?.trim() || path14.basename(filePath, ".md");
|
|
3348
3755
|
const goal = goalMatch?.[1]?.trim() || "";
|
|
3349
3756
|
const createdAt = createdMatch?.[1]?.trim() || "";
|
|
3350
3757
|
const updatedAt = updatedMatch?.[1]?.trim() || "";
|
|
@@ -3386,22 +3793,22 @@ function createPlan(name, goal, steps) {
|
|
|
3386
3793
|
}
|
|
3387
3794
|
function savePlan(plan) {
|
|
3388
3795
|
const fp = planPath(plan.name);
|
|
3389
|
-
|
|
3796
|
+
fs14.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
3390
3797
|
}
|
|
3391
3798
|
function loadPlan(name) {
|
|
3392
3799
|
const fp = planPath(name);
|
|
3393
|
-
if (!
|
|
3394
|
-
const content =
|
|
3800
|
+
if (!fs14.existsSync(fp)) return null;
|
|
3801
|
+
const content = fs14.readFileSync(fp, "utf-8");
|
|
3395
3802
|
return parsePlan(content, fp);
|
|
3396
3803
|
}
|
|
3397
3804
|
function listPlans() {
|
|
3398
3805
|
const dir = getPlansDir();
|
|
3399
|
-
if (!
|
|
3806
|
+
if (!fs14.existsSync(dir)) return [];
|
|
3400
3807
|
const plans = [];
|
|
3401
|
-
for (const file of
|
|
3808
|
+
for (const file of fs14.readdirSync(dir)) {
|
|
3402
3809
|
if (!file.endsWith(".md")) continue;
|
|
3403
|
-
const fp =
|
|
3404
|
-
const content =
|
|
3810
|
+
const fp = path14.join(dir, file);
|
|
3811
|
+
const content = fs14.readFileSync(fp, "utf-8");
|
|
3405
3812
|
const plan = parsePlan(content, fp);
|
|
3406
3813
|
if (plan) plans.push(plan);
|
|
3407
3814
|
}
|
|
@@ -3510,10 +3917,10 @@ import {
|
|
|
3510
3917
|
} from "@aman_asmuei/arules-core";
|
|
3511
3918
|
var AGENT_SCOPE = process.env.AMAN_AGENT_SCOPE ?? "dev:agent";
|
|
3512
3919
|
function readEcosystemFile(filePath, label) {
|
|
3513
|
-
if (!
|
|
3920
|
+
if (!fs15.existsSync(filePath)) {
|
|
3514
3921
|
return pc5.dim(`No ${label} file found at ${filePath}`);
|
|
3515
3922
|
}
|
|
3516
|
-
return
|
|
3923
|
+
return fs15.readFileSync(filePath, "utf-8").trim();
|
|
3517
3924
|
}
|
|
3518
3925
|
function parseCommand(input) {
|
|
3519
3926
|
const trimmed = input.trim();
|
|
@@ -3788,9 +4195,9 @@ ${result.violations.map((v) => ` - ${v}`).join("\n")}`)
|
|
|
3788
4195
|
};
|
|
3789
4196
|
}
|
|
3790
4197
|
async function handleWorkflowsCommand(action, args, ctx) {
|
|
3791
|
-
const home2 =
|
|
4198
|
+
const home2 = os14.homedir();
|
|
3792
4199
|
if (!action) {
|
|
3793
|
-
const content = readEcosystemFile(
|
|
4200
|
+
const content = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
3794
4201
|
return { handled: true, output: content };
|
|
3795
4202
|
}
|
|
3796
4203
|
if (action === "add") {
|
|
@@ -3812,7 +4219,7 @@ async function handleWorkflowsCommand(action, args, ctx) {
|
|
|
3812
4219
|
return { handled: true, output: pc5.yellow("Usage: /workflows get <name>") };
|
|
3813
4220
|
}
|
|
3814
4221
|
const name = args.join(" ").toLowerCase();
|
|
3815
|
-
const raw = readEcosystemFile(
|
|
4222
|
+
const raw = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
3816
4223
|
if (raw.startsWith("No ")) {
|
|
3817
4224
|
return { handled: true, output: raw };
|
|
3818
4225
|
}
|
|
@@ -3871,12 +4278,12 @@ async function handleToolsCommand(action, args, _ctx) {
|
|
|
3871
4278
|
return { handled: true, output: pc5.yellow("Usage: /tools search <query...>") };
|
|
3872
4279
|
}
|
|
3873
4280
|
const query = args.join(" ").toLowerCase();
|
|
3874
|
-
const home2 =
|
|
3875
|
-
const toolsFile =
|
|
3876
|
-
if (!
|
|
4281
|
+
const home2 = os14.homedir();
|
|
4282
|
+
const toolsFile = path15.join(home2, ".akit", "tools.md");
|
|
4283
|
+
if (!fs15.existsSync(toolsFile)) {
|
|
3877
4284
|
return { handled: true, output: pc5.dim(`No tools file found. Use 'npx @aman_asmuei/akit search ${args.join(" ")}' to search the registry.`) };
|
|
3878
4285
|
}
|
|
3879
|
-
const raw =
|
|
4286
|
+
const raw = fs15.readFileSync(toolsFile, "utf-8").trim();
|
|
3880
4287
|
const lines = raw.split("\n");
|
|
3881
4288
|
const matches = lines.filter((l) => l.toLowerCase().includes(query));
|
|
3882
4289
|
if (matches.length === 0) {
|
|
@@ -3887,9 +4294,9 @@ async function handleToolsCommand(action, args, _ctx) {
|
|
|
3887
4294
|
return handleAkitCommand(action, args);
|
|
3888
4295
|
}
|
|
3889
4296
|
async function handleSkillsCommand(action, args, ctx) {
|
|
3890
|
-
const home2 =
|
|
4297
|
+
const home2 = os14.homedir();
|
|
3891
4298
|
if (!action) {
|
|
3892
|
-
const content = readEcosystemFile(
|
|
4299
|
+
const content = readEcosystemFile(path15.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
3893
4300
|
return { handled: true, output: content };
|
|
3894
4301
|
}
|
|
3895
4302
|
if (action === "install") {
|
|
@@ -3911,8 +4318,8 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
3911
4318
|
return { handled: true, output: pc5.yellow("Usage: /skills search <query...>") };
|
|
3912
4319
|
}
|
|
3913
4320
|
const query = args.join(" ").toLowerCase();
|
|
3914
|
-
const home3 =
|
|
3915
|
-
const raw = readEcosystemFile(
|
|
4321
|
+
const home3 = os14.homedir();
|
|
4322
|
+
const raw = readEcosystemFile(path15.join(home3, ".askill", "skills.md"), "skills (askill)");
|
|
3916
4323
|
if (raw.startsWith("No ")) {
|
|
3917
4324
|
return { handled: true, output: raw };
|
|
3918
4325
|
}
|
|
@@ -3935,9 +4342,9 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
3935
4342
|
return { handled: true, output: pc5.yellow(`Unknown action: /skills ${action}. Try /skills --help`) };
|
|
3936
4343
|
}
|
|
3937
4344
|
async function handleEvalCommand(action, args, ctx) {
|
|
3938
|
-
const home2 =
|
|
4345
|
+
const home2 = os14.homedir();
|
|
3939
4346
|
if (!action) {
|
|
3940
|
-
const content = readEcosystemFile(
|
|
4347
|
+
const content = readEcosystemFile(path15.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
3941
4348
|
return { handled: true, output: content };
|
|
3942
4349
|
}
|
|
3943
4350
|
if (action === "milestone") {
|
|
@@ -3949,11 +4356,11 @@ async function handleEvalCommand(action, args, ctx) {
|
|
|
3949
4356
|
return { handled: true, output };
|
|
3950
4357
|
}
|
|
3951
4358
|
if (action === "report") {
|
|
3952
|
-
const evalFile =
|
|
3953
|
-
if (!
|
|
4359
|
+
const evalFile = path15.join(home2, ".aeval", "eval.md");
|
|
4360
|
+
if (!fs15.existsSync(evalFile)) {
|
|
3954
4361
|
return { handled: true, output: pc5.dim("No eval report found. Log milestones with /eval milestone <text>.") };
|
|
3955
4362
|
}
|
|
3956
|
-
const content =
|
|
4363
|
+
const content = fs15.readFileSync(evalFile, "utf-8").trim();
|
|
3957
4364
|
return { handled: true, output: [pc5.bold("Eval Report"), "", content].join("\n") };
|
|
3958
4365
|
}
|
|
3959
4366
|
return { handled: true, output: pc5.yellow(`Unknown action: /eval ${action}. Use /eval, /eval report, or /eval milestone <text>.`) };
|
|
@@ -4463,6 +4870,8 @@ function handleHelp() {
|
|
|
4463
4870
|
` ${pc5.cyan("/showcase")} Browse & switch companion templates`,
|
|
4464
4871
|
` ${pc5.cyan("/delegate")} Delegate tasks to sub-agents`,
|
|
4465
4872
|
` ${pc5.cyan("/team")} Manage agent teams`,
|
|
4873
|
+
` ${pc5.cyan("/observe")} Session observation dashboard [pause|resume]`,
|
|
4874
|
+
` ${pc5.cyan("/postmortem")} Generate post-mortem [last|list|--since 7d]`,
|
|
4466
4875
|
` ${pc5.cyan("/update")} Check for updates`,
|
|
4467
4876
|
` ${pc5.cyan("/reset")} Full reset [all|memory|config|identity|rules]`,
|
|
4468
4877
|
` ${pc5.cyan("/clear")} Clear conversation history`,
|
|
@@ -4475,10 +4884,10 @@ function handleSave() {
|
|
|
4475
4884
|
}
|
|
4476
4885
|
function handleReset(action) {
|
|
4477
4886
|
const dirs = {
|
|
4478
|
-
config:
|
|
4479
|
-
memory:
|
|
4480
|
-
identity:
|
|
4481
|
-
rules:
|
|
4887
|
+
config: path15.join(os14.homedir(), ".aman-agent"),
|
|
4888
|
+
memory: path15.join(os14.homedir(), ".amem"),
|
|
4889
|
+
identity: path15.join(os14.homedir(), ".acore"),
|
|
4890
|
+
rules: path15.join(os14.homedir(), ".arules")
|
|
4482
4891
|
};
|
|
4483
4892
|
if (action === "help" || !action) {
|
|
4484
4893
|
return {
|
|
@@ -4503,15 +4912,15 @@ function handleReset(action) {
|
|
|
4503
4912
|
const removed = [];
|
|
4504
4913
|
for (const target of targets) {
|
|
4505
4914
|
const dir = dirs[target];
|
|
4506
|
-
if (
|
|
4507
|
-
|
|
4915
|
+
if (fs15.existsSync(dir)) {
|
|
4916
|
+
fs15.rmSync(dir, { recursive: true, force: true });
|
|
4508
4917
|
removed.push(target);
|
|
4509
4918
|
}
|
|
4510
4919
|
}
|
|
4511
4920
|
if (targets.includes("config")) {
|
|
4512
4921
|
const configDir = dirs.config;
|
|
4513
|
-
|
|
4514
|
-
|
|
4922
|
+
fs15.mkdirSync(configDir, { recursive: true });
|
|
4923
|
+
fs15.writeFileSync(path15.join(configDir, ".reconfig"), "", "utf-8");
|
|
4515
4924
|
}
|
|
4516
4925
|
if (removed.length === 0) {
|
|
4517
4926
|
return { handled: true, output: pc5.dim("Nothing to reset \u2014 directories don't exist.") };
|
|
@@ -4528,7 +4937,7 @@ function handleReset(action) {
|
|
|
4528
4937
|
function handleUpdate() {
|
|
4529
4938
|
try {
|
|
4530
4939
|
const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
4531
|
-
const local = true ? "0.
|
|
4940
|
+
const local = true ? "0.25.0" : "unknown";
|
|
4532
4941
|
if (current === local) {
|
|
4533
4942
|
return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
|
|
4534
4943
|
}
|
|
@@ -4572,11 +4981,11 @@ function handleExportCommand() {
|
|
|
4572
4981
|
return { handled: true, exportConversation: true };
|
|
4573
4982
|
}
|
|
4574
4983
|
function handleDebugCommand() {
|
|
4575
|
-
const logPath =
|
|
4576
|
-
if (!
|
|
4984
|
+
const logPath = path15.join(os14.homedir(), ".aman-agent", "debug.log");
|
|
4985
|
+
if (!fs15.existsSync(logPath)) {
|
|
4577
4986
|
return { handled: true, output: pc5.dim("No debug log found.") };
|
|
4578
4987
|
}
|
|
4579
|
-
const content =
|
|
4988
|
+
const content = fs15.readFileSync(logPath, "utf-8");
|
|
4580
4989
|
const lines = content.trim().split("\n");
|
|
4581
4990
|
const last20 = lines.slice(-20).join("\n");
|
|
4582
4991
|
return { handled: true, output: pc5.bold("Debug Log (last 20 entries):\n") + pc5.dim(last20) };
|
|
@@ -4774,7 +5183,7 @@ ${result.response}`
|
|
|
4774
5183
|
};
|
|
4775
5184
|
}
|
|
4776
5185
|
function handleProfileCommand(action, args) {
|
|
4777
|
-
const profilesDir =
|
|
5186
|
+
const profilesDir = path15.join(os14.homedir(), ".acore", "profiles");
|
|
4778
5187
|
if (action === "me") {
|
|
4779
5188
|
const user = loadUserIdentity();
|
|
4780
5189
|
if (!user) {
|
|
@@ -4842,8 +5251,8 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4842
5251
|
};
|
|
4843
5252
|
}
|
|
4844
5253
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
4845
|
-
const profileDir =
|
|
4846
|
-
if (
|
|
5254
|
+
const profileDir = path15.join(profilesDir, slug);
|
|
5255
|
+
if (fs15.existsSync(profileDir)) {
|
|
4847
5256
|
return { handled: true, output: pc5.yellow(`Profile already exists: ${slug}`) };
|
|
4848
5257
|
}
|
|
4849
5258
|
const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
|
|
@@ -4859,16 +5268,16 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4859
5268
|
Use: aman-agent --profile ${slug}`
|
|
4860
5269
|
};
|
|
4861
5270
|
}
|
|
4862
|
-
|
|
4863
|
-
const globalCore =
|
|
4864
|
-
if (
|
|
4865
|
-
let content =
|
|
5271
|
+
fs15.mkdirSync(profileDir, { recursive: true });
|
|
5272
|
+
const globalCore = path15.join(os14.homedir(), ".acore", "core.md");
|
|
5273
|
+
if (fs15.existsSync(globalCore)) {
|
|
5274
|
+
let content = fs15.readFileSync(globalCore, "utf-8");
|
|
4866
5275
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4867
5276
|
content = content.replace(/^# .+$/m, `# ${aiName}`);
|
|
4868
|
-
|
|
5277
|
+
fs15.writeFileSync(path15.join(profileDir, "core.md"), content, "utf-8");
|
|
4869
5278
|
} else {
|
|
4870
5279
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4871
|
-
|
|
5280
|
+
fs15.writeFileSync(path15.join(profileDir, "core.md"), `# ${aiName}
|
|
4872
5281
|
|
|
4873
5282
|
## Identity
|
|
4874
5283
|
- Role: ${aiName} is your AI companion
|
|
@@ -4881,7 +5290,7 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4881
5290
|
return {
|
|
4882
5291
|
handled: true,
|
|
4883
5292
|
output: pc5.green(`Profile created: ${slug}`) + `
|
|
4884
|
-
Edit: ${
|
|
5293
|
+
Edit: ${path15.join(profileDir, "core.md")}
|
|
4885
5294
|
Use: aman-agent --profile ${slug}
|
|
4886
5295
|
|
|
4887
5296
|
${pc5.dim("Add rules.md or skills.md for profile-specific overrides.")}`
|
|
@@ -4890,9 +5299,9 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4890
5299
|
case "show": {
|
|
4891
5300
|
const name = args[0];
|
|
4892
5301
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile show <name>") };
|
|
4893
|
-
const profileDir =
|
|
4894
|
-
if (!
|
|
4895
|
-
const files =
|
|
5302
|
+
const profileDir = path15.join(profilesDir, name);
|
|
5303
|
+
if (!fs15.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
|
|
5304
|
+
const files = fs15.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
|
|
4896
5305
|
const lines = files.map((f) => ` ${f}`);
|
|
4897
5306
|
return { handled: true, output: `Profile: ${pc5.bold(name)}
|
|
4898
5307
|
Files:
|
|
@@ -4901,9 +5310,9 @@ ${lines.join("\n")}` };
|
|
|
4901
5310
|
case "delete": {
|
|
4902
5311
|
const name = args[0];
|
|
4903
5312
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile delete <name>") };
|
|
4904
|
-
const profileDir =
|
|
4905
|
-
if (!
|
|
4906
|
-
|
|
5313
|
+
const profileDir = path15.join(profilesDir, name);
|
|
5314
|
+
if (!fs15.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
|
|
5315
|
+
fs15.rmSync(profileDir, { recursive: true });
|
|
4907
5316
|
return { handled: true, output: pc5.dim(`Profile deleted: ${name}`) };
|
|
4908
5317
|
}
|
|
4909
5318
|
case "help":
|
|
@@ -4927,7 +5336,7 @@ ${lines.join("\n")}` };
|
|
|
4927
5336
|
return { handled: true, output: pc5.yellow(`Unknown profile action: ${action}. Try /profile help`) };
|
|
4928
5337
|
}
|
|
4929
5338
|
}
|
|
4930
|
-
function handlePlanCommand(action, args) {
|
|
5339
|
+
function handlePlanCommand(action, args, ctx) {
|
|
4931
5340
|
if (!action) {
|
|
4932
5341
|
const active = getActivePlan();
|
|
4933
5342
|
if (!active) {
|
|
@@ -4956,17 +5365,29 @@ function handlePlanCommand(action, args) {
|
|
|
4956
5365
|
case "done": {
|
|
4957
5366
|
const active = getActivePlan();
|
|
4958
5367
|
if (!active) return { handled: true, output: pc5.yellow("No active plan.") };
|
|
5368
|
+
const recordPlanMilestone = (stepIndex) => {
|
|
5369
|
+
if (ctx?.observationSession) {
|
|
5370
|
+
const step = active.steps[stepIndex];
|
|
5371
|
+
recordEvent(ctx.observationSession, {
|
|
5372
|
+
type: "milestone",
|
|
5373
|
+
summary: `Plan step done: ${step.text}`,
|
|
5374
|
+
data: { plan: active.name, stepIndex, stepText: step.text }
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
};
|
|
4959
5378
|
if (args.length > 0) {
|
|
4960
5379
|
const stepNum = parseInt(args[0], 10);
|
|
4961
5380
|
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
4962
5381
|
return { handled: true, output: pc5.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
4963
5382
|
}
|
|
4964
5383
|
markStepDone(active, stepNum - 1);
|
|
5384
|
+
recordPlanMilestone(stepNum - 1);
|
|
4965
5385
|
return { handled: true, output: pc5.green(`Step ${stepNum} done!`) + "\n\n" + formatPlan(active) };
|
|
4966
5386
|
}
|
|
4967
5387
|
const next = active.steps.findIndex((s) => !s.done);
|
|
4968
5388
|
if (next < 0) return { handled: true, output: pc5.green("All steps already complete!") };
|
|
4969
5389
|
markStepDone(active, next);
|
|
5390
|
+
recordPlanMilestone(next);
|
|
4970
5391
|
return { handled: true, output: pc5.green(`Step ${next + 1} done!`) + "\n\n" + formatPlan(active) };
|
|
4971
5392
|
}
|
|
4972
5393
|
case "undo": {
|
|
@@ -5109,10 +5530,10 @@ function handleShowcaseCommand(action, args) {
|
|
|
5109
5530
|
Or place it as a sibling directory to aman-agent.`
|
|
5110
5531
|
};
|
|
5111
5532
|
}
|
|
5112
|
-
const corePath =
|
|
5533
|
+
const corePath = path15.join(os14.homedir(), ".acore", "core.md");
|
|
5113
5534
|
let currentShowcase = null;
|
|
5114
|
-
if (
|
|
5115
|
-
const content =
|
|
5535
|
+
if (fs15.existsSync(corePath)) {
|
|
5536
|
+
const content = fs15.readFileSync(corePath, "utf-8");
|
|
5116
5537
|
const nameMatch = content.match(/^# (.+)/m);
|
|
5117
5538
|
if (nameMatch) {
|
|
5118
5539
|
const coreName = nameMatch[1].trim().toLowerCase();
|
|
@@ -5283,8 +5704,76 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5283
5704
|
"delegate",
|
|
5284
5705
|
"team",
|
|
5285
5706
|
"showcase",
|
|
5286
|
-
"file"
|
|
5707
|
+
"file",
|
|
5708
|
+
"observe",
|
|
5709
|
+
"postmortem"
|
|
5287
5710
|
]);
|
|
5711
|
+
async function handleObserveCommand(action, ctx) {
|
|
5712
|
+
if (!ctx.observationSession) {
|
|
5713
|
+
return {
|
|
5714
|
+
handled: true,
|
|
5715
|
+
output: pc5.dim("Observation is disabled. Enable with recordObservations: true in config.")
|
|
5716
|
+
};
|
|
5717
|
+
}
|
|
5718
|
+
switch (action) {
|
|
5719
|
+
case "pause":
|
|
5720
|
+
pauseObservation(ctx.observationSession);
|
|
5721
|
+
return { handled: true, output: pc5.dim("Observation paused. Use /observe resume to continue.") };
|
|
5722
|
+
case "resume":
|
|
5723
|
+
resumeObservation(ctx.observationSession);
|
|
5724
|
+
return { handled: true, output: pc5.dim("Observation resumed.") };
|
|
5725
|
+
default:
|
|
5726
|
+
return { handled: true, output: getSessionStats(ctx.observationSession) };
|
|
5727
|
+
}
|
|
5728
|
+
}
|
|
5729
|
+
async function handlePostmortemCommand(action, args, ctx) {
|
|
5730
|
+
switch (action) {
|
|
5731
|
+
case "last": {
|
|
5732
|
+
const files = await listPostmortems();
|
|
5733
|
+
if (files.length === 0) return { handled: true, output: pc5.dim("No post-mortems found.") };
|
|
5734
|
+
const content = await readPostmortem(files[0]);
|
|
5735
|
+
return { handled: true, output: content ?? pc5.red("Could not read post-mortem.") };
|
|
5736
|
+
}
|
|
5737
|
+
case "list": {
|
|
5738
|
+
const files = await listPostmortems();
|
|
5739
|
+
if (files.length === 0) return { handled: true, output: pc5.dim("No post-mortems found.") };
|
|
5740
|
+
return { handled: true, output: "Post-mortems:\n" + files.map((f) => ` ${f}`).join("\n") };
|
|
5741
|
+
}
|
|
5742
|
+
default: {
|
|
5743
|
+
const allArgs = action ? [action, ...args] : args;
|
|
5744
|
+
const sinceIdx = allArgs.indexOf("--since");
|
|
5745
|
+
if (sinceIdx !== -1 && allArgs[sinceIdx + 1]) {
|
|
5746
|
+
const daysStr = allArgs[sinceIdx + 1];
|
|
5747
|
+
const days = parseInt(daysStr.replace("d", ""), 10) || 7;
|
|
5748
|
+
if (!ctx.llmClient) {
|
|
5749
|
+
return { handled: true, output: pc5.red("LLM client not available for analysis.") };
|
|
5750
|
+
}
|
|
5751
|
+
const analysis = await analyzePostmortemRange(days, ctx.llmClient);
|
|
5752
|
+
return { handled: true, output: analysis ?? pc5.red("Could not analyze post-mortems.") };
|
|
5753
|
+
}
|
|
5754
|
+
if (!ctx.observationSession || !ctx.llmClient || !ctx.messages) {
|
|
5755
|
+
return {
|
|
5756
|
+
handled: true,
|
|
5757
|
+
output: pc5.dim("Cannot generate post-mortem: missing session context.")
|
|
5758
|
+
};
|
|
5759
|
+
}
|
|
5760
|
+
const report = await generatePostmortemReport(
|
|
5761
|
+
ctx.observationSession.sessionId,
|
|
5762
|
+
ctx.messages,
|
|
5763
|
+
ctx.observationSession,
|
|
5764
|
+
ctx.llmClient
|
|
5765
|
+
);
|
|
5766
|
+
if (!report) return { handled: true, output: pc5.red("Could not generate post-mortem.") };
|
|
5767
|
+
const filePath = await savePostmortem(report);
|
|
5768
|
+
return {
|
|
5769
|
+
handled: true,
|
|
5770
|
+
output: formatPostmortemMarkdown(report) + `
|
|
5771
|
+
|
|
5772
|
+
${pc5.dim(`Saved \u2192 ${filePath}`)}`
|
|
5773
|
+
};
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5288
5777
|
async function handleCommand(input, ctx) {
|
|
5289
5778
|
const trimmed = input.trim();
|
|
5290
5779
|
if (!trimmed.startsWith("/")) return { handled: false };
|
|
@@ -5332,7 +5821,7 @@ async function handleCommand(input, ctx) {
|
|
|
5332
5821
|
case "reset":
|
|
5333
5822
|
return handleReset(action);
|
|
5334
5823
|
case "plan":
|
|
5335
|
-
return handlePlanCommand(action, args);
|
|
5824
|
+
return handlePlanCommand(action, args, ctx);
|
|
5336
5825
|
case "profile":
|
|
5337
5826
|
return handleProfileCommand(action, args);
|
|
5338
5827
|
case "delegate":
|
|
@@ -5348,6 +5837,10 @@ async function handleCommand(input, ctx) {
|
|
|
5348
5837
|
case "update":
|
|
5349
5838
|
case "upgrade":
|
|
5350
5839
|
return handleUpdate();
|
|
5840
|
+
case "observe":
|
|
5841
|
+
return handleObserveCommand(action, ctx);
|
|
5842
|
+
case "postmortem":
|
|
5843
|
+
return handlePostmortemCommand(action, args, ctx);
|
|
5351
5844
|
default:
|
|
5352
5845
|
return { handled: false };
|
|
5353
5846
|
}
|
|
@@ -5427,9 +5920,9 @@ ${summaryParts.slice(0, 20).join("\n")}
|
|
|
5427
5920
|
}
|
|
5428
5921
|
|
|
5429
5922
|
// src/skill-engine.ts
|
|
5430
|
-
import
|
|
5431
|
-
import
|
|
5432
|
-
import
|
|
5923
|
+
import fs16 from "fs";
|
|
5924
|
+
import path16 from "path";
|
|
5925
|
+
import os15 from "os";
|
|
5433
5926
|
var SKILL_TRIGGERS = {
|
|
5434
5927
|
testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
|
|
5435
5928
|
"api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
|
|
@@ -5444,20 +5937,20 @@ var SKILL_TRIGGERS = {
|
|
|
5444
5937
|
typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
|
|
5445
5938
|
accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
|
|
5446
5939
|
};
|
|
5447
|
-
var LEVEL_FILE =
|
|
5940
|
+
var LEVEL_FILE = path16.join(os15.homedir(), ".aman-agent", "skill-levels.json");
|
|
5448
5941
|
function loadSkillLevels() {
|
|
5449
5942
|
try {
|
|
5450
|
-
if (
|
|
5451
|
-
return JSON.parse(
|
|
5943
|
+
if (fs16.existsSync(LEVEL_FILE)) {
|
|
5944
|
+
return JSON.parse(fs16.readFileSync(LEVEL_FILE, "utf-8"));
|
|
5452
5945
|
}
|
|
5453
5946
|
} catch {
|
|
5454
5947
|
}
|
|
5455
5948
|
return {};
|
|
5456
5949
|
}
|
|
5457
5950
|
function saveSkillLevels(levels) {
|
|
5458
|
-
const dir =
|
|
5459
|
-
if (!
|
|
5460
|
-
|
|
5951
|
+
const dir = path16.dirname(LEVEL_FILE);
|
|
5952
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
5953
|
+
fs16.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
|
|
5461
5954
|
}
|
|
5462
5955
|
function computeLevel(activations) {
|
|
5463
5956
|
if (activations >= 50) return { level: 5, label: "Expert" };
|
|
@@ -6070,9 +6563,9 @@ function humanizeError(message) {
|
|
|
6070
6563
|
}
|
|
6071
6564
|
|
|
6072
6565
|
// src/hints.ts
|
|
6073
|
-
import
|
|
6074
|
-
import
|
|
6075
|
-
import
|
|
6566
|
+
import fs17 from "fs";
|
|
6567
|
+
import path17 from "path";
|
|
6568
|
+
import os16 from "os";
|
|
6076
6569
|
var HINTS = [
|
|
6077
6570
|
{
|
|
6078
6571
|
id: "eval",
|
|
@@ -6110,11 +6603,11 @@ function getHint(state, ctx) {
|
|
|
6110
6603
|
}
|
|
6111
6604
|
return null;
|
|
6112
6605
|
}
|
|
6113
|
-
var HINTS_FILE =
|
|
6606
|
+
var HINTS_FILE = path17.join(os16.homedir(), ".aman-agent", "hints-seen.json");
|
|
6114
6607
|
function loadShownHints() {
|
|
6115
6608
|
try {
|
|
6116
|
-
if (
|
|
6117
|
-
const data = JSON.parse(
|
|
6609
|
+
if (fs17.existsSync(HINTS_FILE)) {
|
|
6610
|
+
const data = JSON.parse(fs17.readFileSync(HINTS_FILE, "utf-8"));
|
|
6118
6611
|
return new Set(Array.isArray(data) ? data : []);
|
|
6119
6612
|
}
|
|
6120
6613
|
} catch {
|
|
@@ -6123,9 +6616,9 @@ function loadShownHints() {
|
|
|
6123
6616
|
}
|
|
6124
6617
|
function saveShownHints(shown) {
|
|
6125
6618
|
try {
|
|
6126
|
-
const dir =
|
|
6127
|
-
|
|
6128
|
-
|
|
6619
|
+
const dir = path17.dirname(HINTS_FILE);
|
|
6620
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
6621
|
+
fs17.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
|
|
6129
6622
|
} catch {
|
|
6130
6623
|
}
|
|
6131
6624
|
}
|
|
@@ -6262,10 +6755,14 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
6262
6755
|
await bgTasks.waitAll();
|
|
6263
6756
|
bgTasks.displayCompleted();
|
|
6264
6757
|
}
|
|
6758
|
+
if (observationSession) {
|
|
6759
|
+
await flushEvents(observationSession).catch(() => {
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
6265
6762
|
if (mcpManager && hooksConfig) {
|
|
6266
6763
|
try {
|
|
6267
|
-
const hookCtx = { mcpManager, config: hooksConfig };
|
|
6268
|
-
await onSessionEnd(hookCtx, messages, sessionId);
|
|
6764
|
+
const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
|
|
6765
|
+
await onSessionEnd(hookCtx, messages, sessionId, observationSession);
|
|
6269
6766
|
} catch (err) {
|
|
6270
6767
|
log.debug("agent", "session end hook failed on SIGINT", err);
|
|
6271
6768
|
}
|
|
@@ -6314,6 +6811,13 @@ Type a message, ${pc7.dim("/help")} for commands, or ${pc7.dim("/quit")} to exit
|
|
|
6314
6811
|
log.warn("agent", "session start hook failed", err);
|
|
6315
6812
|
}
|
|
6316
6813
|
}
|
|
6814
|
+
let observationSession;
|
|
6815
|
+
let prevSentiment;
|
|
6816
|
+
if (hooksConfig?.recordObservations !== false) {
|
|
6817
|
+
observationSession = createObservationSession(sessionId);
|
|
6818
|
+
cleanupOldObservations().catch(() => {
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
6317
6821
|
while (true) {
|
|
6318
6822
|
if (bgTasks.hasCompleted) {
|
|
6319
6823
|
const completed = bgTasks.collectCompleted();
|
|
@@ -6353,13 +6857,24 @@ ${task.result}`
|
|
|
6353
6857
|
}
|
|
6354
6858
|
const input = await prompt();
|
|
6355
6859
|
if (!input.trim()) continue;
|
|
6356
|
-
const cmdResult = await handleCommand(input, {
|
|
6860
|
+
const cmdResult = await handleCommand(input, {
|
|
6861
|
+
model,
|
|
6862
|
+
mcpManager,
|
|
6863
|
+
llmClient: client,
|
|
6864
|
+
tools,
|
|
6865
|
+
observationSession,
|
|
6866
|
+
messages
|
|
6867
|
+
});
|
|
6357
6868
|
if (cmdResult.handled) {
|
|
6358
6869
|
if (cmdResult.quit) {
|
|
6870
|
+
if (observationSession) {
|
|
6871
|
+
await flushEvents(observationSession).catch(() => {
|
|
6872
|
+
});
|
|
6873
|
+
}
|
|
6359
6874
|
if (mcpManager && hooksConfig) {
|
|
6360
6875
|
try {
|
|
6361
|
-
const hookCtx = { mcpManager, config: hooksConfig };
|
|
6362
|
-
await onSessionEnd(hookCtx, messages, sessionId);
|
|
6876
|
+
const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
|
|
6877
|
+
await onSessionEnd(hookCtx, messages, sessionId, observationSession);
|
|
6363
6878
|
} catch (err) {
|
|
6364
6879
|
log.debug("agent", "session end hook failed on quit", err);
|
|
6365
6880
|
}
|
|
@@ -6370,9 +6885,9 @@ ${task.result}`
|
|
|
6370
6885
|
}
|
|
6371
6886
|
if (cmdResult.exportConversation) {
|
|
6372
6887
|
try {
|
|
6373
|
-
const exportDir =
|
|
6374
|
-
|
|
6375
|
-
const exportPath =
|
|
6888
|
+
const exportDir = path18.join(os17.homedir(), ".aman-agent", "exports");
|
|
6889
|
+
fs18.mkdirSync(exportDir, { recursive: true });
|
|
6890
|
+
const exportPath = path18.join(exportDir, `${sessionId}.md`);
|
|
6376
6891
|
const lines = [
|
|
6377
6892
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
6378
6893
|
`**Model:** ${model}`,
|
|
@@ -6386,7 +6901,7 @@ ${task.result}`
|
|
|
6386
6901
|
lines.push(`${label} ${msg.content}`, "");
|
|
6387
6902
|
}
|
|
6388
6903
|
}
|
|
6389
|
-
|
|
6904
|
+
fs18.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
6390
6905
|
console.log(pc7.green(`Exported to ${exportPath}`));
|
|
6391
6906
|
} catch {
|
|
6392
6907
|
console.log(pc7.red("Failed to export conversation."));
|
|
@@ -6512,25 +7027,25 @@ ${knowledgeItem.content}
|
|
|
6512
7027
|
for (const match of filePathMatches) {
|
|
6513
7028
|
let filePath = match[1];
|
|
6514
7029
|
if (filePath.startsWith("~/")) {
|
|
6515
|
-
filePath =
|
|
7030
|
+
filePath = path18.join(os17.homedir(), filePath.slice(2));
|
|
6516
7031
|
}
|
|
6517
|
-
if (!
|
|
6518
|
-
const ext =
|
|
7032
|
+
if (!fs18.existsSync(filePath) || !fs18.statSync(filePath).isFile()) continue;
|
|
7033
|
+
const ext = path18.extname(filePath).toLowerCase();
|
|
6519
7034
|
if (imageExts.has(ext)) {
|
|
6520
7035
|
try {
|
|
6521
|
-
const stat =
|
|
7036
|
+
const stat = fs18.statSync(filePath);
|
|
6522
7037
|
if (stat.size > maxImageBytes) {
|
|
6523
|
-
process.stdout.write(pc7.yellow(` [skipped: ${
|
|
7038
|
+
process.stdout.write(pc7.yellow(` [skipped: ${path18.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
6524
7039
|
`));
|
|
6525
7040
|
continue;
|
|
6526
7041
|
}
|
|
6527
|
-
const data =
|
|
7042
|
+
const data = fs18.readFileSync(filePath).toString("base64");
|
|
6528
7043
|
const mediaType = mimeMap[ext] || "image/png";
|
|
6529
7044
|
imageBlocks.push({
|
|
6530
7045
|
type: "image",
|
|
6531
7046
|
source: { type: "base64", media_type: mediaType, data }
|
|
6532
7047
|
});
|
|
6533
|
-
process.stdout.write(pc7.dim(` [attached image: ${
|
|
7048
|
+
process.stdout.write(pc7.dim(` [attached image: ${path18.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
|
|
6534
7049
|
`));
|
|
6535
7050
|
} catch {
|
|
6536
7051
|
process.stdout.write(pc7.dim(` [could not read image: ${filePath}]
|
|
@@ -6538,7 +7053,7 @@ ${knowledgeItem.content}
|
|
|
6538
7053
|
}
|
|
6539
7054
|
} else if (textExts.has(ext) || ext === "") {
|
|
6540
7055
|
try {
|
|
6541
|
-
const content =
|
|
7056
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
6542
7057
|
const maxChars = 5e4;
|
|
6543
7058
|
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
6544
7059
|
|
|
@@ -6548,7 +7063,7 @@ ${knowledgeItem.content}
|
|
|
6548
7063
|
<file path="${filePath}" size="${content.length} chars">
|
|
6549
7064
|
${trimmed}
|
|
6550
7065
|
</file>`;
|
|
6551
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
7066
|
+
process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
|
|
6552
7067
|
`));
|
|
6553
7068
|
} catch {
|
|
6554
7069
|
process.stdout.write(pc7.dim(` [could not read: ${filePath}]
|
|
@@ -6557,7 +7072,7 @@ ${trimmed}
|
|
|
6557
7072
|
} else if (docExts.has(ext)) {
|
|
6558
7073
|
if (mcpManager) {
|
|
6559
7074
|
try {
|
|
6560
|
-
process.stdout.write(pc7.dim(` [converting: ${
|
|
7075
|
+
process.stdout.write(pc7.dim(` [converting: ${path18.basename(filePath)}...]
|
|
6561
7076
|
`));
|
|
6562
7077
|
const converted = await mcpManager.callTool("doc_convert", { path: filePath });
|
|
6563
7078
|
if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
|
|
@@ -6566,7 +7081,7 @@ ${trimmed}
|
|
|
6566
7081
|
<file path="${filePath}" format="${ext}">
|
|
6567
7082
|
${converted.slice(0, 5e4)}
|
|
6568
7083
|
</file>`;
|
|
6569
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
7084
|
+
process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (converted from ${ext})]
|
|
6570
7085
|
`));
|
|
6571
7086
|
} else {
|
|
6572
7087
|
textContent += `
|
|
@@ -6578,7 +7093,7 @@ ${converted}
|
|
|
6578
7093
|
`));
|
|
6579
7094
|
}
|
|
6580
7095
|
} catch {
|
|
6581
|
-
process.stdout.write(pc7.dim(` [could not convert: ${
|
|
7096
|
+
process.stdout.write(pc7.dim(` [could not convert: ${path18.basename(filePath)}]
|
|
6582
7097
|
`));
|
|
6583
7098
|
}
|
|
6584
7099
|
} else {
|
|
@@ -6659,6 +7174,33 @@ ${converted}
|
|
|
6659
7174
|
});
|
|
6660
7175
|
syncPersonalityToCore(state, mcpManager).catch(() => {
|
|
6661
7176
|
});
|
|
7177
|
+
if (observationSession && prevSentiment !== state.sentiment.dominant) {
|
|
7178
|
+
recordEvent(observationSession, {
|
|
7179
|
+
type: "sentiment_shift",
|
|
7180
|
+
summary: `${prevSentiment ?? "neutral"} \u2192 ${state.sentiment.dominant}`,
|
|
7181
|
+
data: { from: prevSentiment ?? "neutral", to: state.sentiment.dominant }
|
|
7182
|
+
});
|
|
7183
|
+
prevSentiment = state.sentiment.dominant;
|
|
7184
|
+
}
|
|
7185
|
+
if (observationSession && state.sentiment.frustration > 0.6) {
|
|
7186
|
+
recordEvent(observationSession, {
|
|
7187
|
+
type: "blocker",
|
|
7188
|
+
summary: "User expressing frustration",
|
|
7189
|
+
data: { frustrationLevel: state.sentiment.frustration }
|
|
7190
|
+
});
|
|
7191
|
+
}
|
|
7192
|
+
if (observationSession && recentUserMsgs.length >= 6) {
|
|
7193
|
+
const recent = recentUserMsgs.slice(-3);
|
|
7194
|
+
const previous = recentUserMsgs.slice(-6, -3);
|
|
7195
|
+
const shift = detectTopicShift(recent, previous);
|
|
7196
|
+
if (shift.shifted) {
|
|
7197
|
+
recordEvent(observationSession, {
|
|
7198
|
+
type: "topic_shift",
|
|
7199
|
+
summary: `Topics: ${shift.newTopics.join(", ")}`,
|
|
7200
|
+
data: { newTopics: shift.newTopics }
|
|
7201
|
+
});
|
|
7202
|
+
}
|
|
7203
|
+
}
|
|
6662
7204
|
const nudge = formatWellbeingNudge(state);
|
|
6663
7205
|
if (nudge) {
|
|
6664
7206
|
augmentedSystemPrompt += "\n" + nudge;
|
|
@@ -6757,7 +7299,38 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6757
7299
|
}
|
|
6758
7300
|
process.stdout.write(pc7.dim(` [using ${toolUse.name}...]
|
|
6759
7301
|
`));
|
|
6760
|
-
const
|
|
7302
|
+
const toolStartMs = Date.now();
|
|
7303
|
+
let result;
|
|
7304
|
+
try {
|
|
7305
|
+
result = await mcpManager.callTool(toolUse.name, toolUse.input);
|
|
7306
|
+
} catch (toolErr) {
|
|
7307
|
+
const errMsg = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
7308
|
+
if (observationSession) {
|
|
7309
|
+
recordEvent(observationSession, {
|
|
7310
|
+
type: "tool_error",
|
|
7311
|
+
summary: `${toolUse.name}: ${errMsg}`,
|
|
7312
|
+
data: { tool: toolUse.name, error: errMsg }
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
throw toolErr;
|
|
7316
|
+
}
|
|
7317
|
+
if (observationSession) {
|
|
7318
|
+
const durationMs = Date.now() - toolStartMs;
|
|
7319
|
+
recordEvent(observationSession, {
|
|
7320
|
+
type: "tool_call",
|
|
7321
|
+
summary: `${toolUse.name} (${durationMs}ms)`,
|
|
7322
|
+
data: { tool: toolUse.name, durationMs, success: true }
|
|
7323
|
+
});
|
|
7324
|
+
const FILE_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "file_create", "file_delete"]);
|
|
7325
|
+
if (FILE_TOOLS.has(toolUse.name)) {
|
|
7326
|
+
const filePath = toolUse.input?.path ?? "unknown";
|
|
7327
|
+
recordEvent(observationSession, {
|
|
7328
|
+
type: "file_change",
|
|
7329
|
+
summary: `${toolUse.name}: ${String(filePath)}`,
|
|
7330
|
+
data: { tool: toolUse.name, path: filePath }
|
|
7331
|
+
});
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
6761
7334
|
const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
|
|
6762
7335
|
if (!skipLogging) {
|
|
6763
7336
|
try {
|
|
@@ -6799,6 +7372,10 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6799
7372
|
});
|
|
6800
7373
|
log.debug("agent", `checkpoint saved at turn ${currentTurn}`);
|
|
6801
7374
|
}
|
|
7375
|
+
if (observationSession && observationSession.events.length >= 5) {
|
|
7376
|
+
flushEvents(observationSession).catch(() => {
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
6802
7379
|
if (hooksConfig?.extractMemories) {
|
|
6803
7380
|
const assistantText = typeof response.message.content === "string" ? response.message.content : response.message.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
6804
7381
|
if (assistantText) {
|
|
@@ -6820,7 +7397,7 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6820
7397
|
}
|
|
6821
7398
|
if (hooksConfig?.featureHints) {
|
|
6822
7399
|
hintState.turnCount++;
|
|
6823
|
-
const hasWorkflows =
|
|
7400
|
+
const hasWorkflows = fs18.existsSync(path18.join(os17.homedir(), ".aflow", "flow.md"));
|
|
6824
7401
|
const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
|
|
6825
7402
|
const hint = getHint(hintState, { hasWorkflows, memoryCount });
|
|
6826
7403
|
if (hint) {
|
|
@@ -6866,9 +7443,9 @@ async function saveConversationToMemory(messages, sessionId) {
|
|
|
6866
7443
|
}
|
|
6867
7444
|
|
|
6868
7445
|
// src/index.ts
|
|
6869
|
-
import
|
|
6870
|
-
import
|
|
6871
|
-
import
|
|
7446
|
+
import fs19 from "fs";
|
|
7447
|
+
import path19 from "path";
|
|
7448
|
+
import os18 from "os";
|
|
6872
7449
|
|
|
6873
7450
|
// src/presets.ts
|
|
6874
7451
|
var PRESETS = {
|
|
@@ -6977,9 +7554,9 @@ ${wfSections}`;
|
|
|
6977
7554
|
|
|
6978
7555
|
// src/index.ts
|
|
6979
7556
|
async function autoDetectConfig() {
|
|
6980
|
-
const reconfigMarker =
|
|
6981
|
-
if (
|
|
6982
|
-
|
|
7557
|
+
const reconfigMarker = path19.join(os18.homedir(), ".aman-agent", ".reconfig");
|
|
7558
|
+
if (fs19.existsSync(reconfigMarker)) {
|
|
7559
|
+
fs19.unlinkSync(reconfigMarker);
|
|
6983
7560
|
return null;
|
|
6984
7561
|
}
|
|
6985
7562
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -7008,11 +7585,11 @@ async function autoDetectConfig() {
|
|
|
7008
7585
|
return null;
|
|
7009
7586
|
}
|
|
7010
7587
|
function bootstrapEcosystem() {
|
|
7011
|
-
const home2 =
|
|
7012
|
-
const corePath =
|
|
7013
|
-
if (
|
|
7014
|
-
|
|
7015
|
-
|
|
7588
|
+
const home2 = os18.homedir();
|
|
7589
|
+
const corePath = path19.join(home2, ".acore", "core.md");
|
|
7590
|
+
if (fs19.existsSync(corePath)) return false;
|
|
7591
|
+
fs19.mkdirSync(path19.join(home2, ".acore"), { recursive: true });
|
|
7592
|
+
fs19.writeFileSync(corePath, [
|
|
7016
7593
|
"# Aman",
|
|
7017
7594
|
"",
|
|
7018
7595
|
"## Personality",
|
|
@@ -7024,11 +7601,11 @@ function bootstrapEcosystem() {
|
|
|
7024
7601
|
"## Session",
|
|
7025
7602
|
"_New companion \u2014 no prior sessions._"
|
|
7026
7603
|
].join("\n"), "utf-8");
|
|
7027
|
-
const rulesDir =
|
|
7028
|
-
const rulesPath =
|
|
7029
|
-
if (!
|
|
7030
|
-
|
|
7031
|
-
|
|
7604
|
+
const rulesDir = path19.join(home2, ".arules");
|
|
7605
|
+
const rulesPath = path19.join(rulesDir, "rules.md");
|
|
7606
|
+
if (!fs19.existsSync(rulesPath)) {
|
|
7607
|
+
fs19.mkdirSync(rulesDir, { recursive: true });
|
|
7608
|
+
fs19.writeFileSync(rulesPath, [
|
|
7032
7609
|
"# Guardrails",
|
|
7033
7610
|
"",
|
|
7034
7611
|
"## safety",
|
|
@@ -7040,22 +7617,22 @@ function bootstrapEcosystem() {
|
|
|
7040
7617
|
"- Respect the user's preferences stored in memory"
|
|
7041
7618
|
].join("\n"), "utf-8");
|
|
7042
7619
|
}
|
|
7043
|
-
const flowDir =
|
|
7044
|
-
const flowPath =
|
|
7045
|
-
if (!
|
|
7046
|
-
|
|
7047
|
-
|
|
7620
|
+
const flowDir = path19.join(home2, ".aflow");
|
|
7621
|
+
const flowPath = path19.join(flowDir, "flow.md");
|
|
7622
|
+
if (!fs19.existsSync(flowPath)) {
|
|
7623
|
+
fs19.mkdirSync(flowDir, { recursive: true });
|
|
7624
|
+
fs19.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
|
|
7048
7625
|
}
|
|
7049
|
-
const skillDir =
|
|
7050
|
-
const skillPath =
|
|
7051
|
-
if (!
|
|
7052
|
-
|
|
7053
|
-
|
|
7626
|
+
const skillDir = path19.join(home2, ".askill");
|
|
7627
|
+
const skillPath = path19.join(skillDir, "skills.md");
|
|
7628
|
+
if (!fs19.existsSync(skillPath)) {
|
|
7629
|
+
fs19.mkdirSync(skillDir, { recursive: true });
|
|
7630
|
+
fs19.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
|
|
7054
7631
|
}
|
|
7055
7632
|
return true;
|
|
7056
7633
|
}
|
|
7057
7634
|
var program = new Command();
|
|
7058
|
-
program.name("aman-agent").description("Your AI companion, running locally").version("0.
|
|
7635
|
+
program.name("aman-agent").description("Your AI companion, running locally").version("0.25.0").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).option("--profile <name>", "Use a specific agent profile (e.g., coder, writer, researcher)").action(async (options) => {
|
|
7059
7636
|
p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
|
|
7060
7637
|
let config = loadConfig();
|
|
7061
7638
|
if (!config) {
|
|
@@ -7407,19 +7984,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
7407
7984
|
});
|
|
7408
7985
|
if (p3.isCancel(preset)) process.exit(0);
|
|
7409
7986
|
const result = applyPreset(preset, name || "Aman");
|
|
7410
|
-
const home2 =
|
|
7411
|
-
|
|
7412
|
-
|
|
7987
|
+
const home2 = os18.homedir();
|
|
7988
|
+
fs19.mkdirSync(path19.join(home2, ".acore"), { recursive: true });
|
|
7989
|
+
fs19.writeFileSync(path19.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
|
|
7413
7990
|
p3.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
|
|
7414
7991
|
if (result.rulesMd) {
|
|
7415
|
-
|
|
7416
|
-
|
|
7992
|
+
fs19.mkdirSync(path19.join(home2, ".arules"), { recursive: true });
|
|
7993
|
+
fs19.writeFileSync(path19.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
|
|
7417
7994
|
const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
|
|
7418
7995
|
p3.log.success(`${ruleCount} rules set`);
|
|
7419
7996
|
}
|
|
7420
7997
|
if (result.flowMd) {
|
|
7421
|
-
|
|
7422
|
-
|
|
7998
|
+
fs19.mkdirSync(path19.join(home2, ".aflow"), { recursive: true });
|
|
7999
|
+
fs19.writeFileSync(path19.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
|
|
7423
8000
|
const wfCount = (result.flowMd.match(/^## /gm) || []).length;
|
|
7424
8001
|
p3.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
|
|
7425
8002
|
}
|