@aman_asmuei/aman-agent 0.23.0 → 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 +293 -27
- package/dist/index.js +1395 -186
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
|
|
@@ -1471,7 +1471,18 @@ import {
|
|
|
1471
1471
|
buildVectorIndex,
|
|
1472
1472
|
recallMemories,
|
|
1473
1473
|
generateEmbedding,
|
|
1474
|
-
getVectorIndex
|
|
1474
|
+
getVectorIndex,
|
|
1475
|
+
runDiagnostics,
|
|
1476
|
+
repairDatabase,
|
|
1477
|
+
loadConfig as loadConfig2,
|
|
1478
|
+
saveConfig as saveConfig2,
|
|
1479
|
+
multiStrategyRecall,
|
|
1480
|
+
reflect,
|
|
1481
|
+
isReflectionDue,
|
|
1482
|
+
syncFromClaude,
|
|
1483
|
+
exportForTeam,
|
|
1484
|
+
importFromTeam,
|
|
1485
|
+
syncToCopilot
|
|
1475
1486
|
} from "@aman_asmuei/amem-core";
|
|
1476
1487
|
import path6 from "path";
|
|
1477
1488
|
import os6 from "os";
|
|
@@ -1584,19 +1595,19 @@ async function memoryForget(opts) {
|
|
|
1584
1595
|
}
|
|
1585
1596
|
return { deleted: 0, message: "Provide an id, type, or query to forget." };
|
|
1586
1597
|
}
|
|
1587
|
-
var
|
|
1598
|
+
var _localMemoryConfig = {};
|
|
1588
1599
|
function setMemoryConfig(config) {
|
|
1589
|
-
|
|
1600
|
+
_localMemoryConfig = config;
|
|
1590
1601
|
}
|
|
1591
1602
|
function getMaxRecallTokens() {
|
|
1592
|
-
return
|
|
1603
|
+
return _localMemoryConfig.maxRecallTokens ?? 1500;
|
|
1593
1604
|
}
|
|
1594
1605
|
function memoryConsolidate(dryRun = false) {
|
|
1595
1606
|
return consolidateMemories(getDb(), cosineSimilarity, {
|
|
1596
1607
|
dryRun,
|
|
1597
|
-
maxStaleDays:
|
|
1598
|
-
minConfidence:
|
|
1599
|
-
minAccessCount:
|
|
1608
|
+
maxStaleDays: _localMemoryConfig.maxStaleDays ?? 90,
|
|
1609
|
+
minConfidence: _localMemoryConfig.minConfidence ?? 0.3,
|
|
1610
|
+
minAccessCount: _localMemoryConfig.minAccessCount ?? 0
|
|
1600
1611
|
});
|
|
1601
1612
|
}
|
|
1602
1613
|
function isMemoryInitialized() {
|
|
@@ -1626,6 +1637,114 @@ function reminderComplete(id) {
|
|
|
1626
1637
|
const fullId = getDb().resolveReminderId(id) ?? id;
|
|
1627
1638
|
return getDb().completeReminder(fullId);
|
|
1628
1639
|
}
|
|
1640
|
+
async function memoryDoctor() {
|
|
1641
|
+
return runDiagnostics(getDb());
|
|
1642
|
+
}
|
|
1643
|
+
async function memoryRepair(opts = {}) {
|
|
1644
|
+
const dryRun = opts.dryRun ?? true;
|
|
1645
|
+
if (dryRun) {
|
|
1646
|
+
const diag = runDiagnostics(getDb());
|
|
1647
|
+
return {
|
|
1648
|
+
dryRun: true,
|
|
1649
|
+
status: diag.status,
|
|
1650
|
+
issues: diag.issues.map((issue) => issue.message),
|
|
1651
|
+
actions: diag.issues.map((issue) => `Would fix: ${issue.suggestion}`)
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
const dbPath = process.env.AMEM_DB ?? path6.join(os6.homedir(), ".amem", "memory.db");
|
|
1655
|
+
const result = repairDatabase(dbPath);
|
|
1656
|
+
return {
|
|
1657
|
+
dryRun: false,
|
|
1658
|
+
status: result.status === "repaired" ? "warning" : result.status === "failed" ? "critical" : "healthy",
|
|
1659
|
+
issues: [],
|
|
1660
|
+
actions: result.memoriesRecovered > 0 ? [`Recovered ${result.memoriesRecovered} memories`] : []
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
async function memoryConfig(updates) {
|
|
1664
|
+
const current = loadConfig2();
|
|
1665
|
+
if (updates && Object.keys(updates).length > 0) {
|
|
1666
|
+
saveConfig2(updates);
|
|
1667
|
+
return loadConfig2();
|
|
1668
|
+
}
|
|
1669
|
+
return current;
|
|
1670
|
+
}
|
|
1671
|
+
async function memoryMultiRecall(query, opts = {}) {
|
|
1672
|
+
const queryEmbedding = await generateEmbedding(query);
|
|
1673
|
+
const memories = await multiStrategyRecall(getDb(), {
|
|
1674
|
+
query,
|
|
1675
|
+
queryEmbedding,
|
|
1676
|
+
limit: opts.limit ?? 10,
|
|
1677
|
+
scope: opts.scope ?? currentProject ?? void 0
|
|
1678
|
+
});
|
|
1679
|
+
return { memories, total: memories.length };
|
|
1680
|
+
}
|
|
1681
|
+
async function memoryReflect(config) {
|
|
1682
|
+
return reflect(getDb(), config);
|
|
1683
|
+
}
|
|
1684
|
+
function memoryTier(id, tier) {
|
|
1685
|
+
try {
|
|
1686
|
+
const db2 = getDb();
|
|
1687
|
+
const fullId = db2.resolveId(id) ?? id;
|
|
1688
|
+
db2.updateTier(fullId, tier);
|
|
1689
|
+
return { id: fullId, tier, ok: true };
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
function memoryDetail(id) {
|
|
1695
|
+
const db2 = getDb();
|
|
1696
|
+
const fullId = db2.resolveId(id) ?? id;
|
|
1697
|
+
return db2.getById(fullId);
|
|
1698
|
+
}
|
|
1699
|
+
function memoryRelate(fromId, toId, type, strength) {
|
|
1700
|
+
try {
|
|
1701
|
+
const relationId = getDb().addRelation(fromId, toId, type, strength);
|
|
1702
|
+
return { ok: true, relationId };
|
|
1703
|
+
} catch (err) {
|
|
1704
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function memoryExpire(id, reason) {
|
|
1708
|
+
try {
|
|
1709
|
+
const db2 = getDb();
|
|
1710
|
+
const fullId = db2.resolveId(id) ?? id;
|
|
1711
|
+
db2.expireMemory(fullId);
|
|
1712
|
+
return { ok: true, id: fullId, ...reason !== void 0 ? { reason } : {} };
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
function memoryVersions(id) {
|
|
1718
|
+
const db2 = getDb();
|
|
1719
|
+
const fullId = db2.resolveId(id) ?? id;
|
|
1720
|
+
return db2.getVersionHistory(fullId);
|
|
1721
|
+
}
|
|
1722
|
+
async function memorySync(action, opts = {}) {
|
|
1723
|
+
const db2 = getDb();
|
|
1724
|
+
try {
|
|
1725
|
+
switch (action) {
|
|
1726
|
+
case "import-claude":
|
|
1727
|
+
return await syncFromClaude(db2, opts.projectFilter, opts.dryRun ?? false);
|
|
1728
|
+
case "export-team": {
|
|
1729
|
+
const exportOptions = {
|
|
1730
|
+
userId: opts.userId ?? currentProject
|
|
1731
|
+
};
|
|
1732
|
+
return await exportForTeam(db2, opts.outputDir ?? process.cwd(), exportOptions);
|
|
1733
|
+
}
|
|
1734
|
+
case "import-team":
|
|
1735
|
+
if (!opts.filePath) {
|
|
1736
|
+
return { ok: false, error: "filePath is required for import-team" };
|
|
1737
|
+
}
|
|
1738
|
+
return await importFromTeam(db2, opts.filePath, opts.importOptions);
|
|
1739
|
+
case "sync-copilot":
|
|
1740
|
+
return syncToCopilot(db2, opts.copilotOptions);
|
|
1741
|
+
default:
|
|
1742
|
+
return { ok: false, error: `Unknown sync action: ${action}` };
|
|
1743
|
+
}
|
|
1744
|
+
} catch (err) {
|
|
1745
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1629
1748
|
|
|
1630
1749
|
// src/profile-templates.ts
|
|
1631
1750
|
import fs7 from "fs";
|
|
@@ -2152,14 +2271,98 @@ async function editProfile(current) {
|
|
|
2152
2271
|
return updated;
|
|
2153
2272
|
}
|
|
2154
2273
|
|
|
2274
|
+
// src/files.ts
|
|
2275
|
+
import fs9 from "fs";
|
|
2276
|
+
import path9 from "path";
|
|
2277
|
+
import os9 from "os";
|
|
2278
|
+
var MAX_READ_BYTES = 5e4;
|
|
2279
|
+
var HOME = fs9.realpathSync(os9.homedir());
|
|
2280
|
+
var TMPDIR = fs9.realpathSync(os9.tmpdir());
|
|
2281
|
+
var CWD = fs9.realpathSync(process.cwd());
|
|
2282
|
+
function realOrBest(p4) {
|
|
2283
|
+
const parts = p4.split(path9.sep);
|
|
2284
|
+
for (let i = parts.length; i > 0; i--) {
|
|
2285
|
+
const candidate = parts.slice(0, i).join(path9.sep) || path9.sep;
|
|
2286
|
+
try {
|
|
2287
|
+
const real = fs9.realpathSync(candidate);
|
|
2288
|
+
const remainder = parts.slice(i).join(path9.sep);
|
|
2289
|
+
return remainder ? `${real}${path9.sep}${remainder}` : real;
|
|
2290
|
+
} catch {
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
return p4;
|
|
2294
|
+
}
|
|
2295
|
+
function isUnderDir(real, dir) {
|
|
2296
|
+
return real === dir || real.startsWith(dir + path9.sep);
|
|
2297
|
+
}
|
|
2298
|
+
function assertSafePath(filePath) {
|
|
2299
|
+
const resolved = path9.resolve(filePath);
|
|
2300
|
+
const real = realOrBest(resolved);
|
|
2301
|
+
if (!isUnderDir(real, HOME) && !isUnderDir(real, CWD) && !isUnderDir(real, TMPDIR)) {
|
|
2302
|
+
throw new Error(`Path is outside allowed directories (home or cwd): ${real}`);
|
|
2303
|
+
}
|
|
2304
|
+
return resolved;
|
|
2305
|
+
}
|
|
2306
|
+
async function readFile(filePath) {
|
|
2307
|
+
const resolved = assertSafePath(filePath);
|
|
2308
|
+
if (!fs9.existsSync(resolved)) {
|
|
2309
|
+
throw new Error(`File not found: ${resolved}`);
|
|
2310
|
+
}
|
|
2311
|
+
const stat = fs9.statSync(resolved);
|
|
2312
|
+
if (stat.isDirectory()) {
|
|
2313
|
+
throw new Error(`Path is a directory, not a file: ${resolved}. Use /file list instead.`);
|
|
2314
|
+
}
|
|
2315
|
+
const size = stat.size;
|
|
2316
|
+
const buf = Buffer.alloc(Math.min(size, MAX_READ_BYTES));
|
|
2317
|
+
const fd = fs9.openSync(resolved, "r");
|
|
2318
|
+
try {
|
|
2319
|
+
fs9.readSync(fd, buf, 0, buf.length, 0);
|
|
2320
|
+
} finally {
|
|
2321
|
+
fs9.closeSync(fd);
|
|
2322
|
+
}
|
|
2323
|
+
return {
|
|
2324
|
+
path: resolved,
|
|
2325
|
+
content: buf.toString("utf-8"),
|
|
2326
|
+
size,
|
|
2327
|
+
truncated: size > MAX_READ_BYTES,
|
|
2328
|
+
encoding: "utf-8"
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
async function listFiles(dirPath, opts = {}) {
|
|
2332
|
+
const resolved = assertSafePath(dirPath);
|
|
2333
|
+
if (!fs9.existsSync(resolved)) {
|
|
2334
|
+
throw new Error(`Directory not found: ${resolved}`);
|
|
2335
|
+
}
|
|
2336
|
+
const stat = fs9.statSync(resolved);
|
|
2337
|
+
if (!stat.isDirectory()) {
|
|
2338
|
+
throw new Error(`Path is a file, not a directory: ${resolved}. Use /file read instead.`);
|
|
2339
|
+
}
|
|
2340
|
+
const entries = [];
|
|
2341
|
+
function walk(dir, prefix) {
|
|
2342
|
+
const items = fs9.readdirSync(dir, { withFileTypes: true });
|
|
2343
|
+
for (const item of items) {
|
|
2344
|
+
const rel = prefix ? `${prefix}/${item.name}` : item.name;
|
|
2345
|
+
if (item.isDirectory()) {
|
|
2346
|
+
entries.push({ name: rel, type: "dir", size: 0 });
|
|
2347
|
+
if (opts.recursive) walk(path9.join(dir, item.name), rel);
|
|
2348
|
+
} else {
|
|
2349
|
+
const s = fs9.statSync(path9.join(dir, item.name));
|
|
2350
|
+
entries.push({ name: rel, type: "file", size: s.size });
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
walk(resolved, "");
|
|
2355
|
+
return { path: resolved, entries, total: entries.length };
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2155
2358
|
// src/delegate.ts
|
|
2156
2359
|
import pc3 from "picocolors";
|
|
2157
2360
|
|
|
2158
2361
|
// src/hooks.ts
|
|
2159
2362
|
import pc2 from "picocolors";
|
|
2160
2363
|
import * as p2 from "@clack/prompts";
|
|
2161
|
-
import
|
|
2162
|
-
import
|
|
2364
|
+
import fs12 from "fs";
|
|
2365
|
+
import path12 from "path";
|
|
2163
2366
|
|
|
2164
2367
|
// src/personality.ts
|
|
2165
2368
|
var FRUSTRATION_SIGNALS = [
|
|
@@ -2332,6 +2535,382 @@ async function syncPersonalityToCore(state, mcpManager) {
|
|
|
2332
2535
|
}
|
|
2333
2536
|
}
|
|
2334
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
|
+
|
|
2335
2914
|
// src/hooks.ts
|
|
2336
2915
|
function getTimeContext() {
|
|
2337
2916
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2564,7 +3143,7 @@ async function onWorkflowMatch(userInput, ctx) {
|
|
|
2564
3143
|
isHookCall = false;
|
|
2565
3144
|
}
|
|
2566
3145
|
}
|
|
2567
|
-
async function onSessionEnd(ctx, messages, sessionId) {
|
|
3146
|
+
async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
2568
3147
|
try {
|
|
2569
3148
|
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
2570
3149
|
console.log(pc2.dim("\n Saving conversation to memory..."));
|
|
@@ -2600,10 +3179,10 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2600
3179
|
}
|
|
2601
3180
|
console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
2602
3181
|
}
|
|
2603
|
-
const projectContextPath =
|
|
2604
|
-
if (
|
|
3182
|
+
const projectContextPath = path12.join(process.cwd(), ".acore", "context.md");
|
|
3183
|
+
if (fs12.existsSync(projectContextPath) && messages.length > 2) {
|
|
2605
3184
|
try {
|
|
2606
|
-
let contextContent =
|
|
3185
|
+
let contextContent = fs12.readFileSync(projectContextPath, "utf-8");
|
|
2607
3186
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2608
3187
|
let lastUserMsg = "";
|
|
2609
3188
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -2621,7 +3200,7 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2621
3200
|
- Recent decisions: [see memory]
|
|
2622
3201
|
- Temp notes: [cleared]`;
|
|
2623
3202
|
contextContent = contextContent.replace(sessionPattern, newSession);
|
|
2624
|
-
|
|
3203
|
+
fs12.writeFileSync(projectContextPath, contextContent, "utf-8");
|
|
2625
3204
|
log.debug("hooks", `Updated project context: ${projectContextPath}`);
|
|
2626
3205
|
}
|
|
2627
3206
|
} catch (err) {
|
|
@@ -2674,6 +3253,37 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
2674
3253
|
}
|
|
2675
3254
|
}
|
|
2676
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
|
+
}
|
|
2677
3287
|
} catch (err) {
|
|
2678
3288
|
log.warn("hooks", "session end hook failed", err);
|
|
2679
3289
|
}
|
|
@@ -2827,43 +3437,43 @@ async function delegatePipeline(steps, initialInput, client, mcpManager, options
|
|
|
2827
3437
|
}
|
|
2828
3438
|
|
|
2829
3439
|
// src/teams.ts
|
|
2830
|
-
import
|
|
2831
|
-
import
|
|
2832
|
-
import
|
|
3440
|
+
import fs13 from "fs";
|
|
3441
|
+
import path13 from "path";
|
|
3442
|
+
import os12 from "os";
|
|
2833
3443
|
import pc4 from "picocolors";
|
|
2834
3444
|
function getTeamsDir() {
|
|
2835
|
-
return
|
|
3445
|
+
return path13.join(os12.homedir(), ".acore", "teams");
|
|
2836
3446
|
}
|
|
2837
3447
|
function ensureTeamsDir() {
|
|
2838
3448
|
const dir = getTeamsDir();
|
|
2839
|
-
if (!
|
|
3449
|
+
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
2840
3450
|
return dir;
|
|
2841
3451
|
}
|
|
2842
3452
|
function teamPath(name) {
|
|
2843
3453
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2844
|
-
return
|
|
3454
|
+
return path13.join(ensureTeamsDir(), `${slug}.json`);
|
|
2845
3455
|
}
|
|
2846
3456
|
function createTeam(team) {
|
|
2847
3457
|
const fp = teamPath(team.name);
|
|
2848
|
-
|
|
3458
|
+
fs13.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
|
|
2849
3459
|
}
|
|
2850
3460
|
function loadTeam(name) {
|
|
2851
3461
|
const fp = teamPath(name);
|
|
2852
|
-
if (!
|
|
3462
|
+
if (!fs13.existsSync(fp)) return null;
|
|
2853
3463
|
try {
|
|
2854
|
-
return JSON.parse(
|
|
3464
|
+
return JSON.parse(fs13.readFileSync(fp, "utf-8"));
|
|
2855
3465
|
} catch {
|
|
2856
3466
|
return null;
|
|
2857
3467
|
}
|
|
2858
3468
|
}
|
|
2859
3469
|
function listTeams() {
|
|
2860
3470
|
const dir = getTeamsDir();
|
|
2861
|
-
if (!
|
|
3471
|
+
if (!fs13.existsSync(dir)) return [];
|
|
2862
3472
|
const teams = [];
|
|
2863
|
-
for (const file of
|
|
3473
|
+
for (const file of fs13.readdirSync(dir)) {
|
|
2864
3474
|
if (!file.endsWith(".json")) continue;
|
|
2865
3475
|
try {
|
|
2866
|
-
const content =
|
|
3476
|
+
const content = fs13.readFileSync(path13.join(dir, file), "utf-8");
|
|
2867
3477
|
teams.push(JSON.parse(content));
|
|
2868
3478
|
} catch {
|
|
2869
3479
|
}
|
|
@@ -2872,8 +3482,8 @@ function listTeams() {
|
|
|
2872
3482
|
}
|
|
2873
3483
|
function deleteTeam(name) {
|
|
2874
3484
|
const fp = teamPath(name);
|
|
2875
|
-
if (!
|
|
2876
|
-
|
|
3485
|
+
if (!fs13.existsSync(fp)) return false;
|
|
3486
|
+
fs13.unlinkSync(fp);
|
|
2877
3487
|
return true;
|
|
2878
3488
|
}
|
|
2879
3489
|
async function runTeam(team, task, client, mcpManager, tools) {
|
|
@@ -3099,23 +3709,23 @@ var BUILT_IN_TEAMS = [
|
|
|
3099
3709
|
];
|
|
3100
3710
|
|
|
3101
3711
|
// src/plans.ts
|
|
3102
|
-
import
|
|
3103
|
-
import
|
|
3104
|
-
import
|
|
3712
|
+
import fs14 from "fs";
|
|
3713
|
+
import path14 from "path";
|
|
3714
|
+
import os13 from "os";
|
|
3105
3715
|
function getPlansDir() {
|
|
3106
|
-
const localDir =
|
|
3107
|
-
const localAcore =
|
|
3108
|
-
if (
|
|
3109
|
-
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");
|
|
3110
3720
|
}
|
|
3111
3721
|
function ensurePlansDir() {
|
|
3112
3722
|
const dir = getPlansDir();
|
|
3113
|
-
if (!
|
|
3723
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
3114
3724
|
return dir;
|
|
3115
3725
|
}
|
|
3116
3726
|
function planPath(name) {
|
|
3117
3727
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
3118
|
-
return
|
|
3728
|
+
return path14.join(ensurePlansDir(), `${slug}.md`);
|
|
3119
3729
|
}
|
|
3120
3730
|
function serializePlan(plan) {
|
|
3121
3731
|
const lines = [];
|
|
@@ -3141,7 +3751,7 @@ function parsePlan(content, filePath) {
|
|
|
3141
3751
|
const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
|
|
3142
3752
|
const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
|
|
3143
3753
|
const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
|
|
3144
|
-
const name = nameMatch?.[1]?.trim() ||
|
|
3754
|
+
const name = nameMatch?.[1]?.trim() || path14.basename(filePath, ".md");
|
|
3145
3755
|
const goal = goalMatch?.[1]?.trim() || "";
|
|
3146
3756
|
const createdAt = createdMatch?.[1]?.trim() || "";
|
|
3147
3757
|
const updatedAt = updatedMatch?.[1]?.trim() || "";
|
|
@@ -3183,22 +3793,22 @@ function createPlan(name, goal, steps) {
|
|
|
3183
3793
|
}
|
|
3184
3794
|
function savePlan(plan) {
|
|
3185
3795
|
const fp = planPath(plan.name);
|
|
3186
|
-
|
|
3796
|
+
fs14.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
3187
3797
|
}
|
|
3188
3798
|
function loadPlan(name) {
|
|
3189
3799
|
const fp = planPath(name);
|
|
3190
|
-
if (!
|
|
3191
|
-
const content =
|
|
3800
|
+
if (!fs14.existsSync(fp)) return null;
|
|
3801
|
+
const content = fs14.readFileSync(fp, "utf-8");
|
|
3192
3802
|
return parsePlan(content, fp);
|
|
3193
3803
|
}
|
|
3194
3804
|
function listPlans() {
|
|
3195
3805
|
const dir = getPlansDir();
|
|
3196
|
-
if (!
|
|
3806
|
+
if (!fs14.existsSync(dir)) return [];
|
|
3197
3807
|
const plans = [];
|
|
3198
|
-
for (const file of
|
|
3808
|
+
for (const file of fs14.readdirSync(dir)) {
|
|
3199
3809
|
if (!file.endsWith(".md")) continue;
|
|
3200
|
-
const fp =
|
|
3201
|
-
const content =
|
|
3810
|
+
const fp = path14.join(dir, file);
|
|
3811
|
+
const content = fs14.readFileSync(fp, "utf-8");
|
|
3202
3812
|
const plan = parsePlan(content, fp);
|
|
3203
3813
|
if (plan) plans.push(plan);
|
|
3204
3814
|
}
|
|
@@ -3295,20 +3905,22 @@ function progressBar(pct) {
|
|
|
3295
3905
|
// src/commands.ts
|
|
3296
3906
|
import {
|
|
3297
3907
|
getIdentity as acoreGetIdentity,
|
|
3298
|
-
updateSection as acoreUpdateSection
|
|
3908
|
+
updateSection as acoreUpdateSection,
|
|
3909
|
+
updateDynamics as acoreUpdateDynamics
|
|
3299
3910
|
} from "@aman_asmuei/acore-core";
|
|
3300
3911
|
import {
|
|
3301
3912
|
listRuleCategories as arulesListCategories,
|
|
3302
3913
|
addRule as arulesAddRule,
|
|
3303
3914
|
removeRule as arulesRemoveRule,
|
|
3304
|
-
toggleRuleAt as arulesToggleRule
|
|
3915
|
+
toggleRuleAt as arulesToggleRule,
|
|
3916
|
+
checkAction as arulesCheckAction
|
|
3305
3917
|
} from "@aman_asmuei/arules-core";
|
|
3306
3918
|
var AGENT_SCOPE = process.env.AMAN_AGENT_SCOPE ?? "dev:agent";
|
|
3307
3919
|
function readEcosystemFile(filePath, label) {
|
|
3308
|
-
if (!
|
|
3920
|
+
if (!fs15.existsSync(filePath)) {
|
|
3309
3921
|
return pc5.dim(`No ${label} file found at ${filePath}`);
|
|
3310
3922
|
}
|
|
3311
|
-
return
|
|
3923
|
+
return fs15.readFileSync(filePath, "utf-8").trim();
|
|
3312
3924
|
}
|
|
3313
3925
|
function parseCommand(input) {
|
|
3314
3926
|
const trimmed = input.trim();
|
|
@@ -3319,6 +3931,18 @@ function parseCommand(input) {
|
|
|
3319
3931
|
const args = parts.slice(2);
|
|
3320
3932
|
return { base, action, args };
|
|
3321
3933
|
}
|
|
3934
|
+
function buildNestedUpdate(key, val) {
|
|
3935
|
+
const parts = key.split(".");
|
|
3936
|
+
if (parts.length === 1) return { [key]: val };
|
|
3937
|
+
const result = {};
|
|
3938
|
+
let curr = result;
|
|
3939
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
3940
|
+
curr[parts[i]] = {};
|
|
3941
|
+
curr = curr[parts[i]];
|
|
3942
|
+
}
|
|
3943
|
+
curr[parts[parts.length - 1]] = val;
|
|
3944
|
+
return result;
|
|
3945
|
+
}
|
|
3322
3946
|
async function mcpWrite(ctx, layer, tool, args) {
|
|
3323
3947
|
if (!ctx.mcpManager) {
|
|
3324
3948
|
return pc5.red(`Cannot modify ${layer}: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp`);
|
|
@@ -3373,13 +3997,50 @@ async function handleIdentityCommand(action, args, _ctx) {
|
|
|
3373
3997
|
};
|
|
3374
3998
|
}
|
|
3375
3999
|
}
|
|
4000
|
+
if (action === "dynamics") {
|
|
4001
|
+
const updates = {};
|
|
4002
|
+
for (const arg of args) {
|
|
4003
|
+
const eq = arg.indexOf("=");
|
|
4004
|
+
if (eq > 0) updates[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
4005
|
+
}
|
|
4006
|
+
if (!Object.keys(updates).length) {
|
|
4007
|
+
return { handled: true, output: pc5.yellow("Usage: /identity dynamics energy=high mode=focused read='Book Title'") };
|
|
4008
|
+
}
|
|
4009
|
+
try {
|
|
4010
|
+
await acoreUpdateDynamics({
|
|
4011
|
+
energy: updates.energy,
|
|
4012
|
+
activeMode: updates.mode,
|
|
4013
|
+
currentRead: updates.read
|
|
4014
|
+
}, AGENT_SCOPE);
|
|
4015
|
+
return { handled: true, output: `Dynamics updated: ${Object.entries(updates).map(([k, v]) => `${k}=${v}`).join(", ")}` };
|
|
4016
|
+
} catch (err) {
|
|
4017
|
+
return { handled: true, output: pc5.red(`Dynamics error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
if (action === "summary") {
|
|
4021
|
+
try {
|
|
4022
|
+
const identity = await acoreGetIdentity(AGENT_SCOPE);
|
|
4023
|
+
if (!identity) return { handled: true, output: pc5.yellow("No identity configured.") };
|
|
4024
|
+
const nameMatch = identity.content.match(/\*\*Name:\*\*\s*(.+)/);
|
|
4025
|
+
const lines = [
|
|
4026
|
+
`**Identity Summary**`,
|
|
4027
|
+
nameMatch ? `Name: ${nameMatch[1].trim()}` : "",
|
|
4028
|
+
`Scope: ${AGENT_SCOPE}`
|
|
4029
|
+
].filter(Boolean);
|
|
4030
|
+
return { handled: true, output: lines.join("\n") };
|
|
4031
|
+
} catch (err) {
|
|
4032
|
+
return { handled: true, output: pc5.red(`Summary error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
3376
4035
|
if (action === "help") {
|
|
3377
4036
|
return {
|
|
3378
4037
|
handled: true,
|
|
3379
4038
|
output: [
|
|
3380
4039
|
pc5.bold("Identity commands:"),
|
|
3381
4040
|
` ${pc5.cyan("/identity")} View current identity`,
|
|
3382
|
-
` ${pc5.cyan("/identity update")} <section> Update a section
|
|
4041
|
+
` ${pc5.cyan("/identity update")} <section> Update a section`,
|
|
4042
|
+
` ${pc5.cyan("/identity dynamics")} key=val Update dynamic fields (energy, mode, read)`,
|
|
4043
|
+
` ${pc5.cyan("/identity summary")} Show structured identity summary`
|
|
3383
4044
|
].join("\n")
|
|
3384
4045
|
};
|
|
3385
4046
|
}
|
|
@@ -3495,6 +4156,26 @@ async function handleRulesCommand(action, args, _ctx) {
|
|
|
3495
4156
|
};
|
|
3496
4157
|
}
|
|
3497
4158
|
}
|
|
4159
|
+
if (action === "check") {
|
|
4160
|
+
if (args.length === 0) {
|
|
4161
|
+
return { handled: true, output: pc5.yellow("Usage: /rules check <action description...>") };
|
|
4162
|
+
}
|
|
4163
|
+
const description = args.join(" ");
|
|
4164
|
+
try {
|
|
4165
|
+
const result = await arulesCheckAction(description, AGENT_SCOPE);
|
|
4166
|
+
if (result.safe) {
|
|
4167
|
+
return { handled: true, output: pc5.green(`Action is allowed: "${description}"`) };
|
|
4168
|
+
}
|
|
4169
|
+
return {
|
|
4170
|
+
handled: true,
|
|
4171
|
+
output: pc5.red(`Action blocked: "${description}"
|
|
4172
|
+
Violations:
|
|
4173
|
+
${result.violations.map((v) => ` - ${v}`).join("\n")}`)
|
|
4174
|
+
};
|
|
4175
|
+
} catch (err) {
|
|
4176
|
+
return { handled: true, output: pc5.red(`Check error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
3498
4179
|
if (action === "help") {
|
|
3499
4180
|
return {
|
|
3500
4181
|
handled: true,
|
|
@@ -3503,7 +4184,8 @@ async function handleRulesCommand(action, args, _ctx) {
|
|
|
3503
4184
|
` ${pc5.cyan("/rules")} View current rules`,
|
|
3504
4185
|
` ${pc5.cyan("/rules add")} <category> <text> Add a rule`,
|
|
3505
4186
|
` ${pc5.cyan("/rules remove")} <category> <idx> Remove a rule`,
|
|
3506
|
-
` ${pc5.cyan("/rules toggle")} <category> <idx> Toggle a rule
|
|
4187
|
+
` ${pc5.cyan("/rules toggle")} <category> <idx> Toggle a rule`,
|
|
4188
|
+
` ${pc5.cyan("/rules check")} <action...> Check if an action is allowed`
|
|
3507
4189
|
].join("\n")
|
|
3508
4190
|
};
|
|
3509
4191
|
}
|
|
@@ -3513,9 +4195,9 @@ async function handleRulesCommand(action, args, _ctx) {
|
|
|
3513
4195
|
};
|
|
3514
4196
|
}
|
|
3515
4197
|
async function handleWorkflowsCommand(action, args, ctx) {
|
|
3516
|
-
const home2 =
|
|
4198
|
+
const home2 = os14.homedir();
|
|
3517
4199
|
if (!action) {
|
|
3518
|
-
const content = readEcosystemFile(
|
|
4200
|
+
const content = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
3519
4201
|
return { handled: true, output: content };
|
|
3520
4202
|
}
|
|
3521
4203
|
if (action === "add") {
|
|
@@ -3532,12 +4214,33 @@ async function handleWorkflowsCommand(action, args, ctx) {
|
|
|
3532
4214
|
const output = await mcpWrite(ctx, "workflows", "workflow_remove", { name: args.join(" ") });
|
|
3533
4215
|
return { handled: true, output };
|
|
3534
4216
|
}
|
|
4217
|
+
if (action === "get") {
|
|
4218
|
+
if (args.length === 0) {
|
|
4219
|
+
return { handled: true, output: pc5.yellow("Usage: /workflows get <name>") };
|
|
4220
|
+
}
|
|
4221
|
+
const name = args.join(" ").toLowerCase();
|
|
4222
|
+
const raw = readEcosystemFile(path15.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
4223
|
+
if (raw.startsWith("No ")) {
|
|
4224
|
+
return { handled: true, output: raw };
|
|
4225
|
+
}
|
|
4226
|
+
const sections = raw.split(/^## /m).slice(1);
|
|
4227
|
+
const match = sections.find((s) => s.split("\n")[0].trim().toLowerCase() === name);
|
|
4228
|
+
if (!match) {
|
|
4229
|
+
return { handled: true, output: pc5.yellow(`No workflow found: "${args.join(" ")}"`) };
|
|
4230
|
+
}
|
|
4231
|
+
const title = match.split("\n")[0].trim();
|
|
4232
|
+
const body = match.split("\n").slice(1).join("\n").trim();
|
|
4233
|
+
return { handled: true, output: `## ${title}
|
|
4234
|
+
|
|
4235
|
+
${body}` };
|
|
4236
|
+
}
|
|
3535
4237
|
if (action === "help") {
|
|
3536
4238
|
return { handled: true, output: [
|
|
3537
4239
|
pc5.bold("Workflow commands:"),
|
|
3538
4240
|
` ${pc5.cyan("/workflows")} View current workflows`,
|
|
3539
4241
|
` ${pc5.cyan("/workflows add")} <name> Add a workflow`,
|
|
3540
|
-
` ${pc5.cyan("/workflows remove")} <name> Remove a workflow
|
|
4242
|
+
` ${pc5.cyan("/workflows remove")} <name> Remove a workflow`,
|
|
4243
|
+
` ${pc5.cyan("/workflows get")} <name> Show a specific workflow`
|
|
3541
4244
|
].join("\n") };
|
|
3542
4245
|
}
|
|
3543
4246
|
return { handled: true, output: pc5.yellow(`Unknown action: /workflows ${action}. Try /workflows --help`) };
|
|
@@ -3566,10 +4269,34 @@ function handleAkitCommand(_action, _args) {
|
|
|
3566
4269
|
].join("\n")
|
|
3567
4270
|
};
|
|
3568
4271
|
}
|
|
4272
|
+
async function handleToolsCommand(action, args, _ctx) {
|
|
4273
|
+
if (!action || action === "list") {
|
|
4274
|
+
return handleAkitCommand(action, args);
|
|
4275
|
+
}
|
|
4276
|
+
if (action === "search") {
|
|
4277
|
+
if (args.length === 0) {
|
|
4278
|
+
return { handled: true, output: pc5.yellow("Usage: /tools search <query...>") };
|
|
4279
|
+
}
|
|
4280
|
+
const query = args.join(" ").toLowerCase();
|
|
4281
|
+
const home2 = os14.homedir();
|
|
4282
|
+
const toolsFile = path15.join(home2, ".akit", "tools.md");
|
|
4283
|
+
if (!fs15.existsSync(toolsFile)) {
|
|
4284
|
+
return { handled: true, output: pc5.dim(`No tools file found. Use 'npx @aman_asmuei/akit search ${args.join(" ")}' to search the registry.`) };
|
|
4285
|
+
}
|
|
4286
|
+
const raw = fs15.readFileSync(toolsFile, "utf-8").trim();
|
|
4287
|
+
const lines = raw.split("\n");
|
|
4288
|
+
const matches = lines.filter((l) => l.toLowerCase().includes(query));
|
|
4289
|
+
if (matches.length === 0) {
|
|
4290
|
+
return { handled: true, output: pc5.dim(`No tools matching "${query}".`) };
|
|
4291
|
+
}
|
|
4292
|
+
return { handled: true, output: [pc5.bold(`Tools matching "${query}":`), ...matches].join("\n") };
|
|
4293
|
+
}
|
|
4294
|
+
return handleAkitCommand(action, args);
|
|
4295
|
+
}
|
|
3569
4296
|
async function handleSkillsCommand(action, args, ctx) {
|
|
3570
|
-
const home2 =
|
|
4297
|
+
const home2 = os14.homedir();
|
|
3571
4298
|
if (!action) {
|
|
3572
|
-
const content = readEcosystemFile(
|
|
4299
|
+
const content = readEcosystemFile(path15.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
3573
4300
|
return { handled: true, output: content };
|
|
3574
4301
|
}
|
|
3575
4302
|
if (action === "install") {
|
|
@@ -3586,20 +4313,38 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
3586
4313
|
const output = await mcpWrite(ctx, "skills", "skill_uninstall", { name: args.join(" ") });
|
|
3587
4314
|
return { handled: true, output };
|
|
3588
4315
|
}
|
|
4316
|
+
if (action === "search") {
|
|
4317
|
+
if (args.length === 0) {
|
|
4318
|
+
return { handled: true, output: pc5.yellow("Usage: /skills search <query...>") };
|
|
4319
|
+
}
|
|
4320
|
+
const query = args.join(" ").toLowerCase();
|
|
4321
|
+
const home3 = os14.homedir();
|
|
4322
|
+
const raw = readEcosystemFile(path15.join(home3, ".askill", "skills.md"), "skills (askill)");
|
|
4323
|
+
if (raw.startsWith("No ")) {
|
|
4324
|
+
return { handled: true, output: raw };
|
|
4325
|
+
}
|
|
4326
|
+
const lines = raw.split("\n");
|
|
4327
|
+
const matches = lines.filter((l) => l.toLowerCase().includes(query));
|
|
4328
|
+
if (matches.length === 0) {
|
|
4329
|
+
return { handled: true, output: pc5.dim(`No skills matching "${query}".`) };
|
|
4330
|
+
}
|
|
4331
|
+
return { handled: true, output: [pc5.bold(`Skills matching "${query}":`), ...matches].join("\n") };
|
|
4332
|
+
}
|
|
3589
4333
|
if (action === "help") {
|
|
3590
4334
|
return { handled: true, output: [
|
|
3591
4335
|
pc5.bold("Skills commands:"),
|
|
3592
4336
|
` ${pc5.cyan("/skills")} View installed skills`,
|
|
3593
4337
|
` ${pc5.cyan("/skills install")} <name> Install a skill`,
|
|
3594
|
-
` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill
|
|
4338
|
+
` ${pc5.cyan("/skills uninstall")} <name> Uninstall a skill`,
|
|
4339
|
+
` ${pc5.cyan("/skills search")} <query> Search skills by name/description`
|
|
3595
4340
|
].join("\n") };
|
|
3596
4341
|
}
|
|
3597
4342
|
return { handled: true, output: pc5.yellow(`Unknown action: /skills ${action}. Try /skills --help`) };
|
|
3598
4343
|
}
|
|
3599
4344
|
async function handleEvalCommand(action, args, ctx) {
|
|
3600
|
-
const home2 =
|
|
4345
|
+
const home2 = os14.homedir();
|
|
3601
4346
|
if (!action) {
|
|
3602
|
-
const content = readEcosystemFile(
|
|
4347
|
+
const content = readEcosystemFile(path15.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
3603
4348
|
return { handled: true, output: content };
|
|
3604
4349
|
}
|
|
3605
4350
|
if (action === "milestone") {
|
|
@@ -3610,7 +4355,15 @@ async function handleEvalCommand(action, args, ctx) {
|
|
|
3610
4355
|
const output = await mcpWrite(ctx, "eval", "eval_milestone", { text: text3 });
|
|
3611
4356
|
return { handled: true, output };
|
|
3612
4357
|
}
|
|
3613
|
-
|
|
4358
|
+
if (action === "report") {
|
|
4359
|
+
const evalFile = path15.join(home2, ".aeval", "eval.md");
|
|
4360
|
+
if (!fs15.existsSync(evalFile)) {
|
|
4361
|
+
return { handled: true, output: pc5.dim("No eval report found. Log milestones with /eval milestone <text>.") };
|
|
4362
|
+
}
|
|
4363
|
+
const content = fs15.readFileSync(evalFile, "utf-8").trim();
|
|
4364
|
+
return { handled: true, output: [pc5.bold("Eval Report"), "", content].join("\n") };
|
|
4365
|
+
}
|
|
4366
|
+
return { handled: true, output: pc5.yellow(`Unknown action: /eval ${action}. Use /eval, /eval report, or /eval milestone <text>.`) };
|
|
3614
4367
|
}
|
|
3615
4368
|
async function handleMemoryCommand(action, args, ctx) {
|
|
3616
4369
|
if (!action) {
|
|
@@ -3624,7 +4377,7 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
3624
4377
|
return { handled: true, output: pc5.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
3625
4378
|
}
|
|
3626
4379
|
}
|
|
3627
|
-
if (action && !["search", "clear", "timeline", "stats", "export", "since", "fts", "help"].includes(action)) {
|
|
4380
|
+
if (action && !["search", "clear", "timeline", "stats", "export", "since", "fts", "help", "doctor", "repair", "config", "reflect", "consolidate", "tier", "detail", "relate", "expire", "versions", "sync"].includes(action)) {
|
|
3628
4381
|
try {
|
|
3629
4382
|
const topic = [action, ...args].join(" ");
|
|
3630
4383
|
const result = await memoryContext(topic);
|
|
@@ -3642,8 +4395,17 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
3642
4395
|
}
|
|
3643
4396
|
const query = args.join(" ");
|
|
3644
4397
|
try {
|
|
3645
|
-
const result = await
|
|
3646
|
-
|
|
4398
|
+
const result = await memoryMultiRecall(query, { limit: 10 });
|
|
4399
|
+
if (result.total === 0) {
|
|
4400
|
+
return { handled: true, output: pc5.dim("No memories found.") };
|
|
4401
|
+
}
|
|
4402
|
+
const header = `Search results for "${query}" (${result.total}):`;
|
|
4403
|
+
const lines = [pc5.bold(header), ""];
|
|
4404
|
+
for (const m of result.memories) {
|
|
4405
|
+
const tags = m.tags?.length > 0 ? ` ${pc5.dim(m.tags.map((t) => `#${t}`).join(" "))}` : "";
|
|
4406
|
+
lines.push(` [${m.type}] ${m.content}${tags}`);
|
|
4407
|
+
}
|
|
4408
|
+
return { handled: true, output: lines.join("\n") };
|
|
3647
4409
|
} catch (err) {
|
|
3648
4410
|
return { handled: true, output: pc5.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
3649
4411
|
}
|
|
@@ -3808,9 +4570,214 @@ async function handleMemoryCommand(action, args, ctx) {
|
|
|
3808
4570
|
` ${pc5.cyan("/memory export")} [json] Export all memories`,
|
|
3809
4571
|
` ${pc5.cyan("/memory timeline")} View memory timeline`,
|
|
3810
4572
|
` ${pc5.cyan("/memory clear")} <query> Delete matching memories`,
|
|
3811
|
-
` ${pc5.cyan("/memory clear --type")} <type> Delete all of a type
|
|
4573
|
+
` ${pc5.cyan("/memory clear --type")} <type> Delete all of a type`,
|
|
4574
|
+
` ${pc5.cyan("/memory doctor")} Run memory diagnostics`,
|
|
4575
|
+
` ${pc5.cyan("/memory repair")} Dry-run repair (safe)`,
|
|
4576
|
+
` ${pc5.cyan("/memory config")} [key=value] View or update config (e.g. consolidation.maxStaleDays=60)`
|
|
3812
4577
|
].join("\n") };
|
|
3813
4578
|
}
|
|
4579
|
+
if (action === "doctor") {
|
|
4580
|
+
try {
|
|
4581
|
+
const diag = await memoryDoctor();
|
|
4582
|
+
const statusIcon = diag.status === "healthy" ? "\u2705" : "\u26A0\uFE0F";
|
|
4583
|
+
const lines = [
|
|
4584
|
+
`**Memory Diagnostics**`,
|
|
4585
|
+
`Status: ${statusIcon} ${diag.status}`
|
|
4586
|
+
];
|
|
4587
|
+
if (diag.issues?.length) {
|
|
4588
|
+
lines.push("", "**Issues:**");
|
|
4589
|
+
for (const issue of diag.issues) {
|
|
4590
|
+
lines.push(`- ${typeof issue === "string" ? issue : issue.message ?? String(issue)}`);
|
|
4591
|
+
}
|
|
4592
|
+
lines.push("", "_Run `/memory repair` (dry-run) or `/memory repair --apply` to fix._");
|
|
4593
|
+
}
|
|
4594
|
+
return { handled: true, output: lines.join("\n") };
|
|
4595
|
+
} catch (err) {
|
|
4596
|
+
return { handled: true, output: pc5.red(`Memory doctor error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
if (action === "repair") {
|
|
4600
|
+
try {
|
|
4601
|
+
const dryRun = !args.includes("--apply");
|
|
4602
|
+
const result = await memoryRepair({ dryRun });
|
|
4603
|
+
const prefix = dryRun ? "[DRY RUN] " : "";
|
|
4604
|
+
const lines = [`**${prefix}Memory Repair**`];
|
|
4605
|
+
if (result.actions?.length) {
|
|
4606
|
+
lines.push("", "**Actions:**");
|
|
4607
|
+
for (const act of result.actions) {
|
|
4608
|
+
lines.push(`- ${act}`);
|
|
4609
|
+
}
|
|
4610
|
+
} else {
|
|
4611
|
+
lines.push("No actions needed.");
|
|
4612
|
+
}
|
|
4613
|
+
if (dryRun) {
|
|
4614
|
+
lines.push("", "_Run `/memory repair --apply` to execute._");
|
|
4615
|
+
}
|
|
4616
|
+
return { handled: true, output: lines.join("\n") };
|
|
4617
|
+
} catch (err) {
|
|
4618
|
+
return { handled: true, output: pc5.red(`Memory repair error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
if (action === "config") {
|
|
4622
|
+
try {
|
|
4623
|
+
const kvArg = args.find((a) => a.includes("=") && !a.startsWith("-"));
|
|
4624
|
+
if (kvArg) {
|
|
4625
|
+
const eqIdx = kvArg.indexOf("=");
|
|
4626
|
+
const key = kvArg.slice(0, eqIdx);
|
|
4627
|
+
const rawVal = kvArg.slice(eqIdx + 1);
|
|
4628
|
+
if (!rawVal) {
|
|
4629
|
+
return { handled: true, output: pc5.yellow(`Usage: /memory config <key>=<value>`) };
|
|
4630
|
+
}
|
|
4631
|
+
const val = isNaN(Number(rawVal)) ? rawVal : Number(rawVal);
|
|
4632
|
+
const update = buildNestedUpdate(key, val);
|
|
4633
|
+
await memoryConfig(update);
|
|
4634
|
+
return { handled: true, output: `\u2705 Set \`${key}\` \u2192 \`${val}\`` };
|
|
4635
|
+
}
|
|
4636
|
+
const config = await memoryConfig();
|
|
4637
|
+
const lines = ["**Memory Config**", "```"];
|
|
4638
|
+
for (const [k, v] of Object.entries(config)) {
|
|
4639
|
+
if (typeof v === "object" && v !== null) {
|
|
4640
|
+
for (const [sk, sv] of Object.entries(v)) {
|
|
4641
|
+
lines.push(`${k}.${sk}: ${sv}`);
|
|
4642
|
+
}
|
|
4643
|
+
} else {
|
|
4644
|
+
lines.push(`${k}: ${v}`);
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
lines.push("```", "", "_Use `/memory config key=value` to change a setting._");
|
|
4648
|
+
return { handled: true, output: lines.join("\n") };
|
|
4649
|
+
} catch (err) {
|
|
4650
|
+
return { handled: true, output: pc5.red(`Memory config error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
if (action === "reflect") {
|
|
4654
|
+
try {
|
|
4655
|
+
const report = await memoryReflect();
|
|
4656
|
+
const lines = [
|
|
4657
|
+
pc5.bold("Reflection complete"),
|
|
4658
|
+
`Clusters: ${report.clusters.length}`,
|
|
4659
|
+
`Contradictions: ${report.contradictions.length}`,
|
|
4660
|
+
`Synthesis candidates: ${report.synthesisCandidates.length}`,
|
|
4661
|
+
`Knowledge gaps: ${report.knowledgeGaps.length}`,
|
|
4662
|
+
`Health score: ${(report.stats.healthScore * 100).toFixed(0)}%`,
|
|
4663
|
+
`Duration: ${report.durationMs}ms`
|
|
4664
|
+
];
|
|
4665
|
+
return { handled: true, output: lines.join("\n") };
|
|
4666
|
+
} catch (err) {
|
|
4667
|
+
return { handled: true, output: pc5.red(`Reflect error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
if (action === "consolidate") {
|
|
4671
|
+
const apply = args.includes("--apply");
|
|
4672
|
+
try {
|
|
4673
|
+
const report = memoryConsolidate(!apply);
|
|
4674
|
+
const lines = [
|
|
4675
|
+
apply ? pc5.bold("Consolidation applied") : pc5.bold("Consolidation dry-run"),
|
|
4676
|
+
`Merged: ${report.merged}`,
|
|
4677
|
+
`Pruned: ${report.pruned}`,
|
|
4678
|
+
`Promoted: ${report.promoted}`,
|
|
4679
|
+
`Decayed: ${report.decayed}`,
|
|
4680
|
+
`Health score: ${(report.healthScore * 100).toFixed(0)}%`,
|
|
4681
|
+
`Before: ${report.before.total} \u2192 After: ${report.after.total}`
|
|
4682
|
+
];
|
|
4683
|
+
if (!apply) lines.push(pc5.dim("Run with --apply to execute."));
|
|
4684
|
+
return { handled: true, output: lines.join("\n") };
|
|
4685
|
+
} catch (err) {
|
|
4686
|
+
return { handled: true, output: pc5.red(`Consolidate error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (action === "tier") {
|
|
4690
|
+
const id = args[0];
|
|
4691
|
+
const tier = args[1];
|
|
4692
|
+
if (!id || !tier) {
|
|
4693
|
+
return { handled: true, output: pc5.yellow("Usage: /memory tier <id> <core|working|archival>") };
|
|
4694
|
+
}
|
|
4695
|
+
const tierResult = memoryTier(id, tier);
|
|
4696
|
+
if (!tierResult.ok) {
|
|
4697
|
+
return { handled: true, output: pc5.red(`Tier error: ${tierResult.error}`) };
|
|
4698
|
+
}
|
|
4699
|
+
return { handled: true, output: `\u2705 Memory ${tierResult.id} moved to tier: ${tierResult.tier}` };
|
|
4700
|
+
}
|
|
4701
|
+
if (action === "detail") {
|
|
4702
|
+
const id = args[0];
|
|
4703
|
+
if (!id) {
|
|
4704
|
+
return { handled: true, output: pc5.yellow("Usage: /memory detail <id>") };
|
|
4705
|
+
}
|
|
4706
|
+
const memory = memoryDetail(id);
|
|
4707
|
+
if (!memory) {
|
|
4708
|
+
return { handled: true, output: pc5.dim(`Memory not found: ${id}`) };
|
|
4709
|
+
}
|
|
4710
|
+
const lines = [
|
|
4711
|
+
pc5.bold(`Memory: ${memory.id}`),
|
|
4712
|
+
`Content: ${memory.content}`,
|
|
4713
|
+
`Type: ${memory.type}`,
|
|
4714
|
+
`Confidence: ${memory.confidence}`,
|
|
4715
|
+
`Tier: ${memory.tier ?? "working"}`,
|
|
4716
|
+
`Access count: ${memory.accessCount}`,
|
|
4717
|
+
`Created: ${new Date(memory.createdAt).toISOString()}`,
|
|
4718
|
+
memory.tags?.length ? `Tags: ${memory.tags.join(", ")}` : ""
|
|
4719
|
+
].filter(Boolean);
|
|
4720
|
+
return { handled: true, output: lines.join("\n") };
|
|
4721
|
+
}
|
|
4722
|
+
if (action === "relate") {
|
|
4723
|
+
const [fromId, toId, relType, strengthStr] = args;
|
|
4724
|
+
if (!fromId || !toId || !relType) {
|
|
4725
|
+
return { handled: true, output: pc5.yellow("Usage: /memory relate <fromId> <toId> <type> [strength]") };
|
|
4726
|
+
}
|
|
4727
|
+
const strength = strengthStr !== void 0 ? parseFloat(strengthStr) : void 0;
|
|
4728
|
+
const relResult = memoryRelate(fromId, toId, relType, strength);
|
|
4729
|
+
if (!relResult.ok) {
|
|
4730
|
+
return { handled: true, output: pc5.red(`Relate error: ${relResult.error}`) };
|
|
4731
|
+
}
|
|
4732
|
+
return { handled: true, output: `\u2705 Relation created: ${fromId} --[${relType}]--> ${toId} (id: ${relResult.relationId})` };
|
|
4733
|
+
}
|
|
4734
|
+
if (action === "expire") {
|
|
4735
|
+
const id = args[0];
|
|
4736
|
+
if (!id) {
|
|
4737
|
+
return { handled: true, output: pc5.yellow("Usage: /memory expire <id> [reason]") };
|
|
4738
|
+
}
|
|
4739
|
+
const reason = args.slice(1).join(" ") || void 0;
|
|
4740
|
+
const expireResult = memoryExpire(id, reason);
|
|
4741
|
+
if (!expireResult.ok) {
|
|
4742
|
+
return { handled: true, output: pc5.red(`Expire error: ${expireResult.error}`) };
|
|
4743
|
+
}
|
|
4744
|
+
return { handled: true, output: `\u2705 Memory ${expireResult.id} expired${reason ? `: ${reason}` : ""}` };
|
|
4745
|
+
}
|
|
4746
|
+
if (action === "versions") {
|
|
4747
|
+
const id = args[0];
|
|
4748
|
+
if (!id) {
|
|
4749
|
+
return { handled: true, output: pc5.yellow("Usage: /memory versions <id>") };
|
|
4750
|
+
}
|
|
4751
|
+
const versions = memoryVersions(id);
|
|
4752
|
+
if (!versions.length) {
|
|
4753
|
+
return { handled: true, output: pc5.dim(`No version history for: ${id}`) };
|
|
4754
|
+
}
|
|
4755
|
+
const lines = [pc5.bold(`Version history for ${id}:`)];
|
|
4756
|
+
for (const v of versions) {
|
|
4757
|
+
lines.push(` [${new Date(v.editedAt).toISOString()}] ${v.content.slice(0, 80)}${v.content.length > 80 ? "\u2026" : ""}`);
|
|
4758
|
+
}
|
|
4759
|
+
return { handled: true, output: lines.join("\n") };
|
|
4760
|
+
}
|
|
4761
|
+
if (action === "sync") {
|
|
4762
|
+
const syncAction = args[0];
|
|
4763
|
+
if (!syncAction) {
|
|
4764
|
+
return { handled: true, output: pc5.yellow("Usage: /memory sync <import-claude|export-team|import-team|sync-copilot>") };
|
|
4765
|
+
}
|
|
4766
|
+
try {
|
|
4767
|
+
const opts = {};
|
|
4768
|
+
for (const arg of args.slice(1)) {
|
|
4769
|
+
if (arg.startsWith("--")) {
|
|
4770
|
+
const [k, v] = arg.slice(2).split("=");
|
|
4771
|
+
opts[k] = v ?? true;
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
const result = await memorySync(syncAction, opts);
|
|
4775
|
+
return { handled: true, output: `\u2705 Sync [${syncAction}] complete:
|
|
4776
|
+
${JSON.stringify(result, null, 2)}` };
|
|
4777
|
+
} catch (err) {
|
|
4778
|
+
return { handled: true, output: pc5.red(`Sync error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
3814
4781
|
return { handled: true, output: pc5.yellow(`Unknown action: /memory ${action}. Try /memory --help`) };
|
|
3815
4782
|
}
|
|
3816
4783
|
function handleStatusCommand(ctx) {
|
|
@@ -3903,6 +4870,8 @@ function handleHelp() {
|
|
|
3903
4870
|
` ${pc5.cyan("/showcase")} Browse & switch companion templates`,
|
|
3904
4871
|
` ${pc5.cyan("/delegate")} Delegate tasks to sub-agents`,
|
|
3905
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]`,
|
|
3906
4875
|
` ${pc5.cyan("/update")} Check for updates`,
|
|
3907
4876
|
` ${pc5.cyan("/reset")} Full reset [all|memory|config|identity|rules]`,
|
|
3908
4877
|
` ${pc5.cyan("/clear")} Clear conversation history`,
|
|
@@ -3915,10 +4884,10 @@ function handleSave() {
|
|
|
3915
4884
|
}
|
|
3916
4885
|
function handleReset(action) {
|
|
3917
4886
|
const dirs = {
|
|
3918
|
-
config:
|
|
3919
|
-
memory:
|
|
3920
|
-
identity:
|
|
3921
|
-
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")
|
|
3922
4891
|
};
|
|
3923
4892
|
if (action === "help" || !action) {
|
|
3924
4893
|
return {
|
|
@@ -3943,15 +4912,15 @@ function handleReset(action) {
|
|
|
3943
4912
|
const removed = [];
|
|
3944
4913
|
for (const target of targets) {
|
|
3945
4914
|
const dir = dirs[target];
|
|
3946
|
-
if (
|
|
3947
|
-
|
|
4915
|
+
if (fs15.existsSync(dir)) {
|
|
4916
|
+
fs15.rmSync(dir, { recursive: true, force: true });
|
|
3948
4917
|
removed.push(target);
|
|
3949
4918
|
}
|
|
3950
4919
|
}
|
|
3951
4920
|
if (targets.includes("config")) {
|
|
3952
4921
|
const configDir = dirs.config;
|
|
3953
|
-
|
|
3954
|
-
|
|
4922
|
+
fs15.mkdirSync(configDir, { recursive: true });
|
|
4923
|
+
fs15.writeFileSync(path15.join(configDir, ".reconfig"), "", "utf-8");
|
|
3955
4924
|
}
|
|
3956
4925
|
if (removed.length === 0) {
|
|
3957
4926
|
return { handled: true, output: pc5.dim("Nothing to reset \u2014 directories don't exist.") };
|
|
@@ -3968,7 +4937,7 @@ function handleReset(action) {
|
|
|
3968
4937
|
function handleUpdate() {
|
|
3969
4938
|
try {
|
|
3970
4939
|
const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
3971
|
-
const local = true ? "0.
|
|
4940
|
+
const local = true ? "0.25.0" : "unknown";
|
|
3972
4941
|
if (current === local) {
|
|
3973
4942
|
return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
|
|
3974
4943
|
}
|
|
@@ -4012,11 +4981,11 @@ function handleExportCommand() {
|
|
|
4012
4981
|
return { handled: true, exportConversation: true };
|
|
4013
4982
|
}
|
|
4014
4983
|
function handleDebugCommand() {
|
|
4015
|
-
const logPath =
|
|
4016
|
-
if (!
|
|
4984
|
+
const logPath = path15.join(os14.homedir(), ".aman-agent", "debug.log");
|
|
4985
|
+
if (!fs15.existsSync(logPath)) {
|
|
4017
4986
|
return { handled: true, output: pc5.dim("No debug log found.") };
|
|
4018
4987
|
}
|
|
4019
|
-
const content =
|
|
4988
|
+
const content = fs15.readFileSync(logPath, "utf-8");
|
|
4020
4989
|
const lines = content.trim().split("\n");
|
|
4021
4990
|
const last20 = lines.slice(-20).join("\n");
|
|
4022
4991
|
return { handled: true, output: pc5.bold("Debug Log (last 20 entries):\n") + pc5.dim(last20) };
|
|
@@ -4214,7 +5183,7 @@ ${result.response}`
|
|
|
4214
5183
|
};
|
|
4215
5184
|
}
|
|
4216
5185
|
function handleProfileCommand(action, args) {
|
|
4217
|
-
const profilesDir =
|
|
5186
|
+
const profilesDir = path15.join(os14.homedir(), ".acore", "profiles");
|
|
4218
5187
|
if (action === "me") {
|
|
4219
5188
|
const user = loadUserIdentity();
|
|
4220
5189
|
if (!user) {
|
|
@@ -4282,8 +5251,8 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4282
5251
|
};
|
|
4283
5252
|
}
|
|
4284
5253
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
4285
|
-
const profileDir =
|
|
4286
|
-
if (
|
|
5254
|
+
const profileDir = path15.join(profilesDir, slug);
|
|
5255
|
+
if (fs15.existsSync(profileDir)) {
|
|
4287
5256
|
return { handled: true, output: pc5.yellow(`Profile already exists: ${slug}`) };
|
|
4288
5257
|
}
|
|
4289
5258
|
const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
|
|
@@ -4299,16 +5268,16 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4299
5268
|
Use: aman-agent --profile ${slug}`
|
|
4300
5269
|
};
|
|
4301
5270
|
}
|
|
4302
|
-
|
|
4303
|
-
const globalCore =
|
|
4304
|
-
if (
|
|
4305
|
-
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");
|
|
4306
5275
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4307
5276
|
content = content.replace(/^# .+$/m, `# ${aiName}`);
|
|
4308
|
-
|
|
5277
|
+
fs15.writeFileSync(path15.join(profileDir, "core.md"), content, "utf-8");
|
|
4309
5278
|
} else {
|
|
4310
5279
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4311
|
-
|
|
5280
|
+
fs15.writeFileSync(path15.join(profileDir, "core.md"), `# ${aiName}
|
|
4312
5281
|
|
|
4313
5282
|
## Identity
|
|
4314
5283
|
- Role: ${aiName} is your AI companion
|
|
@@ -4321,7 +5290,7 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4321
5290
|
return {
|
|
4322
5291
|
handled: true,
|
|
4323
5292
|
output: pc5.green(`Profile created: ${slug}`) + `
|
|
4324
|
-
Edit: ${
|
|
5293
|
+
Edit: ${path15.join(profileDir, "core.md")}
|
|
4325
5294
|
Use: aman-agent --profile ${slug}
|
|
4326
5295
|
|
|
4327
5296
|
${pc5.dim("Add rules.md or skills.md for profile-specific overrides.")}`
|
|
@@ -4330,9 +5299,9 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
4330
5299
|
case "show": {
|
|
4331
5300
|
const name = args[0];
|
|
4332
5301
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile show <name>") };
|
|
4333
|
-
const profileDir =
|
|
4334
|
-
if (!
|
|
4335
|
-
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"));
|
|
4336
5305
|
const lines = files.map((f) => ` ${f}`);
|
|
4337
5306
|
return { handled: true, output: `Profile: ${pc5.bold(name)}
|
|
4338
5307
|
Files:
|
|
@@ -4341,9 +5310,9 @@ ${lines.join("\n")}` };
|
|
|
4341
5310
|
case "delete": {
|
|
4342
5311
|
const name = args[0];
|
|
4343
5312
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile delete <name>") };
|
|
4344
|
-
const profileDir =
|
|
4345
|
-
if (!
|
|
4346
|
-
|
|
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 });
|
|
4347
5316
|
return { handled: true, output: pc5.dim(`Profile deleted: ${name}`) };
|
|
4348
5317
|
}
|
|
4349
5318
|
case "help":
|
|
@@ -4367,7 +5336,7 @@ ${lines.join("\n")}` };
|
|
|
4367
5336
|
return { handled: true, output: pc5.yellow(`Unknown profile action: ${action}. Try /profile help`) };
|
|
4368
5337
|
}
|
|
4369
5338
|
}
|
|
4370
|
-
function handlePlanCommand(action, args) {
|
|
5339
|
+
function handlePlanCommand(action, args, ctx) {
|
|
4371
5340
|
if (!action) {
|
|
4372
5341
|
const active = getActivePlan();
|
|
4373
5342
|
if (!active) {
|
|
@@ -4396,17 +5365,29 @@ function handlePlanCommand(action, args) {
|
|
|
4396
5365
|
case "done": {
|
|
4397
5366
|
const active = getActivePlan();
|
|
4398
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
|
+
};
|
|
4399
5378
|
if (args.length > 0) {
|
|
4400
5379
|
const stepNum = parseInt(args[0], 10);
|
|
4401
5380
|
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
4402
5381
|
return { handled: true, output: pc5.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
4403
5382
|
}
|
|
4404
5383
|
markStepDone(active, stepNum - 1);
|
|
5384
|
+
recordPlanMilestone(stepNum - 1);
|
|
4405
5385
|
return { handled: true, output: pc5.green(`Step ${stepNum} done!`) + "\n\n" + formatPlan(active) };
|
|
4406
5386
|
}
|
|
4407
5387
|
const next = active.steps.findIndex((s) => !s.done);
|
|
4408
5388
|
if (next < 0) return { handled: true, output: pc5.green("All steps already complete!") };
|
|
4409
5389
|
markStepDone(active, next);
|
|
5390
|
+
recordPlanMilestone(next);
|
|
4410
5391
|
return { handled: true, output: pc5.green(`Step ${next + 1} done!`) + "\n\n" + formatPlan(active) };
|
|
4411
5392
|
}
|
|
4412
5393
|
case "undo": {
|
|
@@ -4549,10 +5530,10 @@ function handleShowcaseCommand(action, args) {
|
|
|
4549
5530
|
Or place it as a sibling directory to aman-agent.`
|
|
4550
5531
|
};
|
|
4551
5532
|
}
|
|
4552
|
-
const corePath =
|
|
5533
|
+
const corePath = path15.join(os14.homedir(), ".acore", "core.md");
|
|
4553
5534
|
let currentShowcase = null;
|
|
4554
|
-
if (
|
|
4555
|
-
const content =
|
|
5535
|
+
if (fs15.existsSync(corePath)) {
|
|
5536
|
+
const content = fs15.readFileSync(corePath, "utf-8");
|
|
4556
5537
|
const nameMatch = content.match(/^# (.+)/m);
|
|
4557
5538
|
if (nameMatch) {
|
|
4558
5539
|
const coreName = nameMatch[1].trim().toLowerCase();
|
|
@@ -4634,6 +5615,65 @@ ${pc5.dim("Existing files are backed up (.bak) before overwriting.")}` };
|
|
|
4634
5615
|
}
|
|
4635
5616
|
return { handled: true, output: pc5.yellow(`Unknown action: /showcase ${action}. Try /showcase help`) };
|
|
4636
5617
|
}
|
|
5618
|
+
async function handleFileCommand(action, args) {
|
|
5619
|
+
if (!action) {
|
|
5620
|
+
return {
|
|
5621
|
+
handled: true,
|
|
5622
|
+
output: [
|
|
5623
|
+
pc5.bold("File commands:"),
|
|
5624
|
+
` ${pc5.cyan("/file read")} <path> Read a text file (max 50 KB)`,
|
|
5625
|
+
` ${pc5.cyan("/file convert")} <path> Attempt to read binary file as text`,
|
|
5626
|
+
` ${pc5.cyan("/file list")} <path> [--recursive] List directory contents`
|
|
5627
|
+
].join("\n")
|
|
5628
|
+
};
|
|
5629
|
+
}
|
|
5630
|
+
if (action === "read" || action === "convert") {
|
|
5631
|
+
const filePath = args[0];
|
|
5632
|
+
if (!filePath) {
|
|
5633
|
+
return { handled: true, output: pc5.yellow(`Usage: /file ${action} <path>`) };
|
|
5634
|
+
}
|
|
5635
|
+
try {
|
|
5636
|
+
const result = await readFile(filePath);
|
|
5637
|
+
const lines = [
|
|
5638
|
+
pc5.bold(`\u{1F4C4} ${result.path}`) + pc5.dim(` (${(result.size / 1024).toFixed(1)} KB)`),
|
|
5639
|
+
"",
|
|
5640
|
+
result.content
|
|
5641
|
+
];
|
|
5642
|
+
if (result.truncated) {
|
|
5643
|
+
lines.push("", pc5.yellow(`\u26A0 File truncated at 50 KB. Use a text editor for the full file.`));
|
|
5644
|
+
}
|
|
5645
|
+
return { handled: true, output: lines.join("\n") };
|
|
5646
|
+
} catch (err) {
|
|
5647
|
+
return { handled: true, output: pc5.red(`File error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
if (action === "list") {
|
|
5651
|
+
const dirPath = args.find((a) => !a.startsWith("-"));
|
|
5652
|
+
if (!dirPath) {
|
|
5653
|
+
return { handled: true, output: pc5.yellow(`Usage: /file list <path> [--recursive]`) };
|
|
5654
|
+
}
|
|
5655
|
+
const recursive = args.includes("--recursive") || args.includes("-r");
|
|
5656
|
+
try {
|
|
5657
|
+
const result = await listFiles(dirPath, { recursive });
|
|
5658
|
+
const lines = [
|
|
5659
|
+
pc5.bold(`\u{1F4C1} ${result.path}`) + pc5.dim(` (${result.total} items)`),
|
|
5660
|
+
""
|
|
5661
|
+
];
|
|
5662
|
+
for (const entry of result.entries) {
|
|
5663
|
+
if (entry.type === "dir") {
|
|
5664
|
+
lines.push(` ${pc5.cyan(entry.name + "/")}`);
|
|
5665
|
+
} else {
|
|
5666
|
+
const kb = entry.size > 0 ? pc5.dim(` ${(entry.size / 1024).toFixed(1)} KB`) : "";
|
|
5667
|
+
lines.push(` ${entry.name}${kb}`);
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
return { handled: true, output: lines.join("\n") };
|
|
5671
|
+
} catch (err) {
|
|
5672
|
+
return { handled: true, output: pc5.red(`File error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
return { handled: true, output: pc5.yellow(`Unknown /file subcommand: ${action}. Try /file for help.`) };
|
|
5676
|
+
}
|
|
4637
5677
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
4638
5678
|
"quit",
|
|
4639
5679
|
"exit",
|
|
@@ -4663,8 +5703,77 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
4663
5703
|
"profile",
|
|
4664
5704
|
"delegate",
|
|
4665
5705
|
"team",
|
|
4666
|
-
"showcase"
|
|
5706
|
+
"showcase",
|
|
5707
|
+
"file",
|
|
5708
|
+
"observe",
|
|
5709
|
+
"postmortem"
|
|
4667
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
|
+
}
|
|
4668
5777
|
async function handleCommand(input, ctx) {
|
|
4669
5778
|
const trimmed = input.trim();
|
|
4670
5779
|
if (!trimmed.startsWith("/")) return { handled: false };
|
|
@@ -4688,6 +5797,7 @@ async function handleCommand(input, ctx) {
|
|
|
4688
5797
|
case "workflows":
|
|
4689
5798
|
return handleWorkflowsCommand(action, args, ctx);
|
|
4690
5799
|
case "tools":
|
|
5800
|
+
return handleToolsCommand(action, args, ctx);
|
|
4691
5801
|
case "akit":
|
|
4692
5802
|
return handleAkitCommand(action, args);
|
|
4693
5803
|
case "skills":
|
|
@@ -4711,7 +5821,7 @@ async function handleCommand(input, ctx) {
|
|
|
4711
5821
|
case "reset":
|
|
4712
5822
|
return handleReset(action);
|
|
4713
5823
|
case "plan":
|
|
4714
|
-
return handlePlanCommand(action, args);
|
|
5824
|
+
return handlePlanCommand(action, args, ctx);
|
|
4715
5825
|
case "profile":
|
|
4716
5826
|
return handleProfileCommand(action, args);
|
|
4717
5827
|
case "delegate":
|
|
@@ -4722,9 +5832,15 @@ async function handleCommand(input, ctx) {
|
|
|
4722
5832
|
return handleReminderCommand(action, args);
|
|
4723
5833
|
case "showcase":
|
|
4724
5834
|
return handleShowcaseCommand(action, args);
|
|
5835
|
+
case "file":
|
|
5836
|
+
return handleFileCommand(action, args);
|
|
4725
5837
|
case "update":
|
|
4726
5838
|
case "upgrade":
|
|
4727
5839
|
return handleUpdate();
|
|
5840
|
+
case "observe":
|
|
5841
|
+
return handleObserveCommand(action, ctx);
|
|
5842
|
+
case "postmortem":
|
|
5843
|
+
return handlePostmortemCommand(action, args, ctx);
|
|
4728
5844
|
default:
|
|
4729
5845
|
return { handled: false };
|
|
4730
5846
|
}
|
|
@@ -4804,9 +5920,9 @@ ${summaryParts.slice(0, 20).join("\n")}
|
|
|
4804
5920
|
}
|
|
4805
5921
|
|
|
4806
5922
|
// src/skill-engine.ts
|
|
4807
|
-
import
|
|
4808
|
-
import
|
|
4809
|
-
import
|
|
5923
|
+
import fs16 from "fs";
|
|
5924
|
+
import path16 from "path";
|
|
5925
|
+
import os15 from "os";
|
|
4810
5926
|
var SKILL_TRIGGERS = {
|
|
4811
5927
|
testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
|
|
4812
5928
|
"api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
|
|
@@ -4821,20 +5937,20 @@ var SKILL_TRIGGERS = {
|
|
|
4821
5937
|
typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
|
|
4822
5938
|
accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
|
|
4823
5939
|
};
|
|
4824
|
-
var LEVEL_FILE =
|
|
5940
|
+
var LEVEL_FILE = path16.join(os15.homedir(), ".aman-agent", "skill-levels.json");
|
|
4825
5941
|
function loadSkillLevels() {
|
|
4826
5942
|
try {
|
|
4827
|
-
if (
|
|
4828
|
-
return JSON.parse(
|
|
5943
|
+
if (fs16.existsSync(LEVEL_FILE)) {
|
|
5944
|
+
return JSON.parse(fs16.readFileSync(LEVEL_FILE, "utf-8"));
|
|
4829
5945
|
}
|
|
4830
5946
|
} catch {
|
|
4831
5947
|
}
|
|
4832
5948
|
return {};
|
|
4833
5949
|
}
|
|
4834
5950
|
function saveSkillLevels(levels) {
|
|
4835
|
-
const dir =
|
|
4836
|
-
if (!
|
|
4837
|
-
|
|
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");
|
|
4838
5954
|
}
|
|
4839
5955
|
function computeLevel(activations) {
|
|
4840
5956
|
if (activations >= 50) return { level: 5, label: "Expert" };
|
|
@@ -5163,6 +6279,7 @@ function matchKnowledge(userInput) {
|
|
|
5163
6279
|
}
|
|
5164
6280
|
|
|
5165
6281
|
// src/memory-extractor.ts
|
|
6282
|
+
import { reflect as reflect2, isReflectionDue as isReflectionDue2 } from "@aman_asmuei/amem-core";
|
|
5166
6283
|
var VALID_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology", "decision", "correction"]);
|
|
5167
6284
|
var MIN_RESPONSE_LENGTH = 50;
|
|
5168
6285
|
var MIN_TURNS_BETWEEN_EMPTY = 3;
|
|
@@ -5247,7 +6364,7 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
5247
6364
|
} catch {
|
|
5248
6365
|
}
|
|
5249
6366
|
try {
|
|
5250
|
-
await memoryStore({
|
|
6367
|
+
const storeResult = await memoryStore({
|
|
5251
6368
|
content: candidate.content,
|
|
5252
6369
|
type: candidate.type,
|
|
5253
6370
|
tags: candidate.tags,
|
|
@@ -5255,18 +6372,26 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
5255
6372
|
source: "auto-extraction",
|
|
5256
6373
|
scope: candidate.scope
|
|
5257
6374
|
});
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
6375
|
+
if (storeResult.action !== "private") {
|
|
6376
|
+
stored++;
|
|
6377
|
+
log.debug("extractor", "Stored " + candidate.type + ": " + candidate.content);
|
|
6378
|
+
if (candidate.type === "pattern" || candidate.type === "preference") {
|
|
6379
|
+
const skillMatch = matchPatternToSkill(candidate.content, candidate.tags);
|
|
6380
|
+
if (skillMatch) {
|
|
6381
|
+
enrichSkill(skillMatch, candidate.content);
|
|
6382
|
+
}
|
|
5264
6383
|
}
|
|
5265
6384
|
}
|
|
5266
6385
|
} catch (err) {
|
|
5267
6386
|
log.warn("extractor", "Failed to store: " + candidate.content, err);
|
|
5268
6387
|
}
|
|
5269
6388
|
}
|
|
6389
|
+
if (stored > 0 && isReflectionDue2(getDb()).due) {
|
|
6390
|
+
try {
|
|
6391
|
+
reflect2(getDb());
|
|
6392
|
+
} catch {
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
5270
6395
|
return stored;
|
|
5271
6396
|
} catch (err) {
|
|
5272
6397
|
log.debug("extractor", "extraction failed", err);
|
|
@@ -5438,9 +6563,9 @@ function humanizeError(message) {
|
|
|
5438
6563
|
}
|
|
5439
6564
|
|
|
5440
6565
|
// src/hints.ts
|
|
5441
|
-
import
|
|
5442
|
-
import
|
|
5443
|
-
import
|
|
6566
|
+
import fs17 from "fs";
|
|
6567
|
+
import path17 from "path";
|
|
6568
|
+
import os16 from "os";
|
|
5444
6569
|
var HINTS = [
|
|
5445
6570
|
{
|
|
5446
6571
|
id: "eval",
|
|
@@ -5478,11 +6603,11 @@ function getHint(state, ctx) {
|
|
|
5478
6603
|
}
|
|
5479
6604
|
return null;
|
|
5480
6605
|
}
|
|
5481
|
-
var HINTS_FILE =
|
|
6606
|
+
var HINTS_FILE = path17.join(os16.homedir(), ".aman-agent", "hints-seen.json");
|
|
5482
6607
|
function loadShownHints() {
|
|
5483
6608
|
try {
|
|
5484
|
-
if (
|
|
5485
|
-
const data = JSON.parse(
|
|
6609
|
+
if (fs17.existsSync(HINTS_FILE)) {
|
|
6610
|
+
const data = JSON.parse(fs17.readFileSync(HINTS_FILE, "utf-8"));
|
|
5486
6611
|
return new Set(Array.isArray(data) ? data : []);
|
|
5487
6612
|
}
|
|
5488
6613
|
} catch {
|
|
@@ -5491,9 +6616,9 @@ function loadShownHints() {
|
|
|
5491
6616
|
}
|
|
5492
6617
|
function saveShownHints(shown) {
|
|
5493
6618
|
try {
|
|
5494
|
-
const dir =
|
|
5495
|
-
|
|
5496
|
-
|
|
6619
|
+
const dir = path17.dirname(HINTS_FILE);
|
|
6620
|
+
fs17.mkdirSync(dir, { recursive: true });
|
|
6621
|
+
fs17.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
|
|
5497
6622
|
} catch {
|
|
5498
6623
|
}
|
|
5499
6624
|
}
|
|
@@ -5630,10 +6755,14 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
5630
6755
|
await bgTasks.waitAll();
|
|
5631
6756
|
bgTasks.displayCompleted();
|
|
5632
6757
|
}
|
|
6758
|
+
if (observationSession) {
|
|
6759
|
+
await flushEvents(observationSession).catch(() => {
|
|
6760
|
+
});
|
|
6761
|
+
}
|
|
5633
6762
|
if (mcpManager && hooksConfig) {
|
|
5634
6763
|
try {
|
|
5635
|
-
const hookCtx = { mcpManager, config: hooksConfig };
|
|
5636
|
-
await onSessionEnd(hookCtx, messages, sessionId);
|
|
6764
|
+
const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
|
|
6765
|
+
await onSessionEnd(hookCtx, messages, sessionId, observationSession);
|
|
5637
6766
|
} catch (err) {
|
|
5638
6767
|
log.debug("agent", "session end hook failed on SIGINT", err);
|
|
5639
6768
|
}
|
|
@@ -5682,6 +6811,13 @@ Type a message, ${pc7.dim("/help")} for commands, or ${pc7.dim("/quit")} to exit
|
|
|
5682
6811
|
log.warn("agent", "session start hook failed", err);
|
|
5683
6812
|
}
|
|
5684
6813
|
}
|
|
6814
|
+
let observationSession;
|
|
6815
|
+
let prevSentiment;
|
|
6816
|
+
if (hooksConfig?.recordObservations !== false) {
|
|
6817
|
+
observationSession = createObservationSession(sessionId);
|
|
6818
|
+
cleanupOldObservations().catch(() => {
|
|
6819
|
+
});
|
|
6820
|
+
}
|
|
5685
6821
|
while (true) {
|
|
5686
6822
|
if (bgTasks.hasCompleted) {
|
|
5687
6823
|
const completed = bgTasks.collectCompleted();
|
|
@@ -5721,13 +6857,24 @@ ${task.result}`
|
|
|
5721
6857
|
}
|
|
5722
6858
|
const input = await prompt();
|
|
5723
6859
|
if (!input.trim()) continue;
|
|
5724
|
-
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
|
+
});
|
|
5725
6868
|
if (cmdResult.handled) {
|
|
5726
6869
|
if (cmdResult.quit) {
|
|
6870
|
+
if (observationSession) {
|
|
6871
|
+
await flushEvents(observationSession).catch(() => {
|
|
6872
|
+
});
|
|
6873
|
+
}
|
|
5727
6874
|
if (mcpManager && hooksConfig) {
|
|
5728
6875
|
try {
|
|
5729
|
-
const hookCtx = { mcpManager, config: hooksConfig };
|
|
5730
|
-
await onSessionEnd(hookCtx, messages, sessionId);
|
|
6876
|
+
const hookCtx = { mcpManager, config: hooksConfig, llmClient: client };
|
|
6877
|
+
await onSessionEnd(hookCtx, messages, sessionId, observationSession);
|
|
5731
6878
|
} catch (err) {
|
|
5732
6879
|
log.debug("agent", "session end hook failed on quit", err);
|
|
5733
6880
|
}
|
|
@@ -5738,9 +6885,9 @@ ${task.result}`
|
|
|
5738
6885
|
}
|
|
5739
6886
|
if (cmdResult.exportConversation) {
|
|
5740
6887
|
try {
|
|
5741
|
-
const exportDir =
|
|
5742
|
-
|
|
5743
|
-
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`);
|
|
5744
6891
|
const lines = [
|
|
5745
6892
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
5746
6893
|
`**Model:** ${model}`,
|
|
@@ -5754,7 +6901,7 @@ ${task.result}`
|
|
|
5754
6901
|
lines.push(`${label} ${msg.content}`, "");
|
|
5755
6902
|
}
|
|
5756
6903
|
}
|
|
5757
|
-
|
|
6904
|
+
fs18.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
5758
6905
|
console.log(pc7.green(`Exported to ${exportPath}`));
|
|
5759
6906
|
} catch {
|
|
5760
6907
|
console.log(pc7.red("Failed to export conversation."));
|
|
@@ -5880,25 +7027,25 @@ ${knowledgeItem.content}
|
|
|
5880
7027
|
for (const match of filePathMatches) {
|
|
5881
7028
|
let filePath = match[1];
|
|
5882
7029
|
if (filePath.startsWith("~/")) {
|
|
5883
|
-
filePath =
|
|
7030
|
+
filePath = path18.join(os17.homedir(), filePath.slice(2));
|
|
5884
7031
|
}
|
|
5885
|
-
if (!
|
|
5886
|
-
const ext =
|
|
7032
|
+
if (!fs18.existsSync(filePath) || !fs18.statSync(filePath).isFile()) continue;
|
|
7033
|
+
const ext = path18.extname(filePath).toLowerCase();
|
|
5887
7034
|
if (imageExts.has(ext)) {
|
|
5888
7035
|
try {
|
|
5889
|
-
const stat =
|
|
7036
|
+
const stat = fs18.statSync(filePath);
|
|
5890
7037
|
if (stat.size > maxImageBytes) {
|
|
5891
|
-
process.stdout.write(pc7.yellow(` [skipped: ${
|
|
7038
|
+
process.stdout.write(pc7.yellow(` [skipped: ${path18.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
5892
7039
|
`));
|
|
5893
7040
|
continue;
|
|
5894
7041
|
}
|
|
5895
|
-
const data =
|
|
7042
|
+
const data = fs18.readFileSync(filePath).toString("base64");
|
|
5896
7043
|
const mediaType = mimeMap[ext] || "image/png";
|
|
5897
7044
|
imageBlocks.push({
|
|
5898
7045
|
type: "image",
|
|
5899
7046
|
source: { type: "base64", media_type: mediaType, data }
|
|
5900
7047
|
});
|
|
5901
|
-
process.stdout.write(pc7.dim(` [attached image: ${
|
|
7048
|
+
process.stdout.write(pc7.dim(` [attached image: ${path18.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
|
|
5902
7049
|
`));
|
|
5903
7050
|
} catch {
|
|
5904
7051
|
process.stdout.write(pc7.dim(` [could not read image: ${filePath}]
|
|
@@ -5906,7 +7053,7 @@ ${knowledgeItem.content}
|
|
|
5906
7053
|
}
|
|
5907
7054
|
} else if (textExts.has(ext) || ext === "") {
|
|
5908
7055
|
try {
|
|
5909
|
-
const content =
|
|
7056
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
5910
7057
|
const maxChars = 5e4;
|
|
5911
7058
|
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
5912
7059
|
|
|
@@ -5916,7 +7063,7 @@ ${knowledgeItem.content}
|
|
|
5916
7063
|
<file path="${filePath}" size="${content.length} chars">
|
|
5917
7064
|
${trimmed}
|
|
5918
7065
|
</file>`;
|
|
5919
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
7066
|
+
process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
|
|
5920
7067
|
`));
|
|
5921
7068
|
} catch {
|
|
5922
7069
|
process.stdout.write(pc7.dim(` [could not read: ${filePath}]
|
|
@@ -5925,7 +7072,7 @@ ${trimmed}
|
|
|
5925
7072
|
} else if (docExts.has(ext)) {
|
|
5926
7073
|
if (mcpManager) {
|
|
5927
7074
|
try {
|
|
5928
|
-
process.stdout.write(pc7.dim(` [converting: ${
|
|
7075
|
+
process.stdout.write(pc7.dim(` [converting: ${path18.basename(filePath)}...]
|
|
5929
7076
|
`));
|
|
5930
7077
|
const converted = await mcpManager.callTool("doc_convert", { path: filePath });
|
|
5931
7078
|
if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
|
|
@@ -5934,7 +7081,7 @@ ${trimmed}
|
|
|
5934
7081
|
<file path="${filePath}" format="${ext}">
|
|
5935
7082
|
${converted.slice(0, 5e4)}
|
|
5936
7083
|
</file>`;
|
|
5937
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
7084
|
+
process.stdout.write(pc7.dim(` [attached: ${path18.basename(filePath)} (converted from ${ext})]
|
|
5938
7085
|
`));
|
|
5939
7086
|
} else {
|
|
5940
7087
|
textContent += `
|
|
@@ -5946,7 +7093,7 @@ ${converted}
|
|
|
5946
7093
|
`));
|
|
5947
7094
|
}
|
|
5948
7095
|
} catch {
|
|
5949
|
-
process.stdout.write(pc7.dim(` [could not convert: ${
|
|
7096
|
+
process.stdout.write(pc7.dim(` [could not convert: ${path18.basename(filePath)}]
|
|
5950
7097
|
`));
|
|
5951
7098
|
}
|
|
5952
7099
|
} else {
|
|
@@ -6027,6 +7174,33 @@ ${converted}
|
|
|
6027
7174
|
});
|
|
6028
7175
|
syncPersonalityToCore(state, mcpManager).catch(() => {
|
|
6029
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
|
+
}
|
|
6030
7204
|
const nudge = formatWellbeingNudge(state);
|
|
6031
7205
|
if (nudge) {
|
|
6032
7206
|
augmentedSystemPrompt += "\n" + nudge;
|
|
@@ -6125,7 +7299,38 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6125
7299
|
}
|
|
6126
7300
|
process.stdout.write(pc7.dim(` [using ${toolUse.name}...]
|
|
6127
7301
|
`));
|
|
6128
|
-
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
|
+
}
|
|
6129
7334
|
const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
|
|
6130
7335
|
if (!skipLogging) {
|
|
6131
7336
|
try {
|
|
@@ -6167,6 +7372,10 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6167
7372
|
});
|
|
6168
7373
|
log.debug("agent", `checkpoint saved at turn ${currentTurn}`);
|
|
6169
7374
|
}
|
|
7375
|
+
if (observationSession && observationSession.events.length >= 5) {
|
|
7376
|
+
flushEvents(observationSession).catch(() => {
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
6170
7379
|
if (hooksConfig?.extractMemories) {
|
|
6171
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("");
|
|
6172
7381
|
if (assistantText) {
|
|
@@ -6188,7 +7397,7 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
6188
7397
|
}
|
|
6189
7398
|
if (hooksConfig?.featureHints) {
|
|
6190
7399
|
hintState.turnCount++;
|
|
6191
|
-
const hasWorkflows =
|
|
7400
|
+
const hasWorkflows = fs18.existsSync(path18.join(os17.homedir(), ".aflow", "flow.md"));
|
|
6192
7401
|
const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
|
|
6193
7402
|
const hint = getHint(hintState, { hasWorkflows, memoryCount });
|
|
6194
7403
|
if (hint) {
|
|
@@ -6234,9 +7443,9 @@ async function saveConversationToMemory(messages, sessionId) {
|
|
|
6234
7443
|
}
|
|
6235
7444
|
|
|
6236
7445
|
// src/index.ts
|
|
6237
|
-
import
|
|
6238
|
-
import
|
|
6239
|
-
import
|
|
7446
|
+
import fs19 from "fs";
|
|
7447
|
+
import path19 from "path";
|
|
7448
|
+
import os18 from "os";
|
|
6240
7449
|
|
|
6241
7450
|
// src/presets.ts
|
|
6242
7451
|
var PRESETS = {
|
|
@@ -6345,9 +7554,9 @@ ${wfSections}`;
|
|
|
6345
7554
|
|
|
6346
7555
|
// src/index.ts
|
|
6347
7556
|
async function autoDetectConfig() {
|
|
6348
|
-
const reconfigMarker =
|
|
6349
|
-
if (
|
|
6350
|
-
|
|
7557
|
+
const reconfigMarker = path19.join(os18.homedir(), ".aman-agent", ".reconfig");
|
|
7558
|
+
if (fs19.existsSync(reconfigMarker)) {
|
|
7559
|
+
fs19.unlinkSync(reconfigMarker);
|
|
6351
7560
|
return null;
|
|
6352
7561
|
}
|
|
6353
7562
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -6376,11 +7585,11 @@ async function autoDetectConfig() {
|
|
|
6376
7585
|
return null;
|
|
6377
7586
|
}
|
|
6378
7587
|
function bootstrapEcosystem() {
|
|
6379
|
-
const home2 =
|
|
6380
|
-
const corePath =
|
|
6381
|
-
if (
|
|
6382
|
-
|
|
6383
|
-
|
|
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, [
|
|
6384
7593
|
"# Aman",
|
|
6385
7594
|
"",
|
|
6386
7595
|
"## Personality",
|
|
@@ -6392,11 +7601,11 @@ function bootstrapEcosystem() {
|
|
|
6392
7601
|
"## Session",
|
|
6393
7602
|
"_New companion \u2014 no prior sessions._"
|
|
6394
7603
|
].join("\n"), "utf-8");
|
|
6395
|
-
const rulesDir =
|
|
6396
|
-
const rulesPath =
|
|
6397
|
-
if (!
|
|
6398
|
-
|
|
6399
|
-
|
|
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, [
|
|
6400
7609
|
"# Guardrails",
|
|
6401
7610
|
"",
|
|
6402
7611
|
"## safety",
|
|
@@ -6408,22 +7617,22 @@ function bootstrapEcosystem() {
|
|
|
6408
7617
|
"- Respect the user's preferences stored in memory"
|
|
6409
7618
|
].join("\n"), "utf-8");
|
|
6410
7619
|
}
|
|
6411
|
-
const flowDir =
|
|
6412
|
-
const flowPath =
|
|
6413
|
-
if (!
|
|
6414
|
-
|
|
6415
|
-
|
|
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");
|
|
6416
7625
|
}
|
|
6417
|
-
const skillDir =
|
|
6418
|
-
const skillPath =
|
|
6419
|
-
if (!
|
|
6420
|
-
|
|
6421
|
-
|
|
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");
|
|
6422
7631
|
}
|
|
6423
7632
|
return true;
|
|
6424
7633
|
}
|
|
6425
7634
|
var program = new Command();
|
|
6426
|
-
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) => {
|
|
6427
7636
|
p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
|
|
6428
7637
|
let config = loadConfig();
|
|
6429
7638
|
if (!config) {
|
|
@@ -6775,19 +7984,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
6775
7984
|
});
|
|
6776
7985
|
if (p3.isCancel(preset)) process.exit(0);
|
|
6777
7986
|
const result = applyPreset(preset, name || "Aman");
|
|
6778
|
-
const home2 =
|
|
6779
|
-
|
|
6780
|
-
|
|
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");
|
|
6781
7990
|
p3.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
|
|
6782
7991
|
if (result.rulesMd) {
|
|
6783
|
-
|
|
6784
|
-
|
|
7992
|
+
fs19.mkdirSync(path19.join(home2, ".arules"), { recursive: true });
|
|
7993
|
+
fs19.writeFileSync(path19.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
|
|
6785
7994
|
const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
|
|
6786
7995
|
p3.log.success(`${ruleCount} rules set`);
|
|
6787
7996
|
}
|
|
6788
7997
|
if (result.flowMd) {
|
|
6789
|
-
|
|
6790
|
-
|
|
7998
|
+
fs19.mkdirSync(path19.join(home2, ".aflow"), { recursive: true });
|
|
7999
|
+
fs19.writeFileSync(path19.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
|
|
6791
8000
|
const wfCount = (result.flowMd.match(/^## /gm) || []).length;
|
|
6792
8001
|
p3.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
|
|
6793
8002
|
}
|