@aman_asmuei/aman-agent 0.8.0 → 0.17.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 +481 -26
- package/dist/index.js +2507 -423
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as p2 from "@clack/prompts";
|
|
4
|
-
import
|
|
4
|
+
import pc7 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs from "fs";
|
|
@@ -15,7 +15,8 @@ var DEFAULT_HOOKS = {
|
|
|
15
15
|
evalPrompt: true,
|
|
16
16
|
autoSessionSave: true,
|
|
17
17
|
extractMemories: true,
|
|
18
|
-
featureHints: true
|
|
18
|
+
featureHints: true,
|
|
19
|
+
personalityAdapt: true
|
|
19
20
|
};
|
|
20
21
|
var CONFIG_DIR = path.join(os.homedir(), ".aman-agent");
|
|
21
22
|
var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
@@ -96,18 +97,35 @@ function buildBudgetedPrompt(components, maxTokens = 8e3) {
|
|
|
96
97
|
|
|
97
98
|
// src/prompt.ts
|
|
98
99
|
var ECOSYSTEM_FILES = [
|
|
99
|
-
{ name: "identity", dir: ".acore", file: "core.md" },
|
|
100
|
+
{ name: "identity", dir: ".acore", file: "core.md", profileOverridable: true },
|
|
100
101
|
{ name: "tools", dir: ".akit", file: "kit.md" },
|
|
101
102
|
{ name: "workflows", dir: ".aflow", file: "flow.md" },
|
|
102
|
-
{ name: "guardrails", dir: ".arules", file: "rules.md" },
|
|
103
|
-
{ name: "skills", dir: ".askill", file: "skills.md" }
|
|
103
|
+
{ name: "guardrails", dir: ".arules", file: "rules.md", profileOverridable: true },
|
|
104
|
+
{ name: "skills", dir: ".askill", file: "skills.md", profileOverridable: true }
|
|
104
105
|
];
|
|
105
|
-
function
|
|
106
|
+
function resolveLayerPath(entry, home2, profile) {
|
|
107
|
+
if (profile && entry.profileOverridable) {
|
|
108
|
+
const profilePath = path2.join(home2, ".acore", "profiles", profile, entry.file);
|
|
109
|
+
if (fs2.existsSync(profilePath)) return profilePath;
|
|
110
|
+
if (entry.name === "guardrails") {
|
|
111
|
+
const altPath = path2.join(home2, ".acore", "profiles", profile, "rules.md");
|
|
112
|
+
if (fs2.existsSync(altPath)) return altPath;
|
|
113
|
+
}
|
|
114
|
+
if (entry.name === "skills") {
|
|
115
|
+
const altPath = path2.join(home2, ".acore", "profiles", profile, "skills.md");
|
|
116
|
+
if (fs2.existsSync(altPath)) return altPath;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const globalPath = path2.join(home2, entry.dir, entry.file);
|
|
120
|
+
if (fs2.existsSync(globalPath)) return globalPath;
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function assembleSystemPrompt(maxTokens, profile) {
|
|
106
124
|
const home2 = os2.homedir();
|
|
107
125
|
const components = [];
|
|
108
126
|
for (const entry of ECOSYSTEM_FILES) {
|
|
109
|
-
const filePath =
|
|
110
|
-
if (
|
|
127
|
+
const filePath = resolveLayerPath(entry, home2, profile);
|
|
128
|
+
if (filePath) {
|
|
111
129
|
const content = fs2.readFileSync(filePath, "utf-8").trim();
|
|
112
130
|
components.push({
|
|
113
131
|
name: entry.name,
|
|
@@ -130,9 +148,43 @@ function assembleSystemPrompt(maxTokens) {
|
|
|
130
148
|
prompt: budgeted.prompt,
|
|
131
149
|
layers: budgeted.included,
|
|
132
150
|
truncated: budgeted.truncated,
|
|
133
|
-
totalTokens: budgeted.totalTokens
|
|
151
|
+
totalTokens: budgeted.totalTokens,
|
|
152
|
+
profile
|
|
134
153
|
};
|
|
135
154
|
}
|
|
155
|
+
function listProfiles() {
|
|
156
|
+
const profilesDir = path2.join(os2.homedir(), ".acore", "profiles");
|
|
157
|
+
if (!fs2.existsSync(profilesDir)) return [];
|
|
158
|
+
const profiles = [];
|
|
159
|
+
for (const entry of fs2.readdirSync(profilesDir, { withFileTypes: true })) {
|
|
160
|
+
if (!entry.isDirectory()) continue;
|
|
161
|
+
const corePath = path2.join(profilesDir, entry.name, "core.md");
|
|
162
|
+
if (!fs2.existsSync(corePath)) continue;
|
|
163
|
+
const content = fs2.readFileSync(corePath, "utf-8");
|
|
164
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
165
|
+
const personalityMatch = content.match(/- Personality:\s*(.+)/);
|
|
166
|
+
profiles.push({
|
|
167
|
+
name: entry.name,
|
|
168
|
+
aiName: nameMatch?.[1]?.trim() || entry.name,
|
|
169
|
+
personality: personalityMatch?.[1]?.trim() || "default"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return profiles;
|
|
173
|
+
}
|
|
174
|
+
function getProfileAiName(profile) {
|
|
175
|
+
const home2 = os2.homedir();
|
|
176
|
+
let corePath;
|
|
177
|
+
if (profile) {
|
|
178
|
+
const profileCorePath = path2.join(home2, ".acore", "profiles", profile, "core.md");
|
|
179
|
+
corePath = fs2.existsSync(profileCorePath) ? profileCorePath : path2.join(home2, ".acore", "core.md");
|
|
180
|
+
} else {
|
|
181
|
+
corePath = path2.join(home2, ".acore", "core.md");
|
|
182
|
+
}
|
|
183
|
+
if (!fs2.existsSync(corePath)) return "Assistant";
|
|
184
|
+
const content = fs2.readFileSync(corePath, "utf-8");
|
|
185
|
+
const match = content.match(/^# (.+)$/m);
|
|
186
|
+
return match?.[1]?.trim() || "Assistant";
|
|
187
|
+
}
|
|
136
188
|
|
|
137
189
|
// src/llm/anthropic.ts
|
|
138
190
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -745,20 +797,20 @@ var McpManager = class {
|
|
|
745
797
|
|
|
746
798
|
// src/agent.ts
|
|
747
799
|
import * as readline from "readline";
|
|
748
|
-
import
|
|
749
|
-
import
|
|
750
|
-
import
|
|
751
|
-
import
|
|
800
|
+
import fs13 from "fs";
|
|
801
|
+
import path13 from "path";
|
|
802
|
+
import os12 from "os";
|
|
803
|
+
import pc6 from "picocolors";
|
|
752
804
|
import { marked } from "marked";
|
|
753
805
|
import { markedTerminal } from "marked-terminal";
|
|
754
806
|
import logUpdate from "log-update";
|
|
755
807
|
|
|
756
808
|
// src/commands.ts
|
|
757
|
-
import
|
|
758
|
-
import
|
|
759
|
-
import
|
|
809
|
+
import fs9 from "fs";
|
|
810
|
+
import path9 from "path";
|
|
811
|
+
import os9 from "os";
|
|
760
812
|
import { execFileSync } from "child_process";
|
|
761
|
-
import
|
|
813
|
+
import pc3 from "picocolors";
|
|
762
814
|
|
|
763
815
|
// src/layers/parsers.ts
|
|
764
816
|
import fs4 from "fs";
|
|
@@ -817,12 +869,796 @@ function getEcosystemStatus(mcpToolCount, amemConnected) {
|
|
|
817
869
|
};
|
|
818
870
|
}
|
|
819
871
|
|
|
872
|
+
// src/memory.ts
|
|
873
|
+
import {
|
|
874
|
+
createDatabase,
|
|
875
|
+
recall,
|
|
876
|
+
buildContext,
|
|
877
|
+
storeMemory,
|
|
878
|
+
consolidateMemories,
|
|
879
|
+
cosineSimilarity,
|
|
880
|
+
preloadEmbeddings,
|
|
881
|
+
buildVectorIndex
|
|
882
|
+
} from "@aman_asmuei/amem-core";
|
|
883
|
+
import path5 from "path";
|
|
884
|
+
import os5 from "os";
|
|
885
|
+
import fs5 from "fs";
|
|
886
|
+
var db = null;
|
|
887
|
+
var currentProject = "global";
|
|
888
|
+
async function initMemory(project) {
|
|
889
|
+
if (db) return db;
|
|
890
|
+
const amemDir = process.env.AMEM_DIR ?? path5.join(os5.homedir(), ".amem");
|
|
891
|
+
if (!fs5.existsSync(amemDir)) fs5.mkdirSync(amemDir, { recursive: true });
|
|
892
|
+
const dbPath = process.env.AMEM_DB ?? path5.join(amemDir, "memory.db");
|
|
893
|
+
db = createDatabase(dbPath);
|
|
894
|
+
currentProject = project ?? "global";
|
|
895
|
+
preloadEmbeddings();
|
|
896
|
+
setTimeout(() => {
|
|
897
|
+
try {
|
|
898
|
+
buildVectorIndex(db);
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
}, 1e3);
|
|
902
|
+
return db;
|
|
903
|
+
}
|
|
904
|
+
function getDb() {
|
|
905
|
+
if (!db) throw new Error("Memory not initialized \u2014 call initMemory() first");
|
|
906
|
+
return db;
|
|
907
|
+
}
|
|
908
|
+
async function memoryRecall(query, opts) {
|
|
909
|
+
return recall(getDb(), {
|
|
910
|
+
query,
|
|
911
|
+
limit: opts?.limit ?? 10,
|
|
912
|
+
compact: opts?.compact ?? true,
|
|
913
|
+
type: opts?.type,
|
|
914
|
+
tag: opts?.tag,
|
|
915
|
+
minConfidence: opts?.minConfidence,
|
|
916
|
+
explain: opts?.explain,
|
|
917
|
+
scope: currentProject
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
async function memoryContext(topic, maxTokens) {
|
|
921
|
+
return buildContext(getDb(), topic, { maxTokens, scope: currentProject });
|
|
922
|
+
}
|
|
923
|
+
async function memoryStore(opts) {
|
|
924
|
+
return storeMemory(getDb(), opts);
|
|
925
|
+
}
|
|
926
|
+
function memoryLog(sessionId, role, content) {
|
|
927
|
+
return getDb().appendLog({
|
|
928
|
+
sessionId,
|
|
929
|
+
role,
|
|
930
|
+
content,
|
|
931
|
+
project: currentProject,
|
|
932
|
+
metadata: {}
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function reminderCheck() {
|
|
936
|
+
return getDb().checkReminders();
|
|
937
|
+
}
|
|
938
|
+
function memoryConsolidate(dryRun = false) {
|
|
939
|
+
return consolidateMemories(getDb(), cosineSimilarity, {
|
|
940
|
+
dryRun,
|
|
941
|
+
maxStaleDays: 90,
|
|
942
|
+
minConfidence: 0.3,
|
|
943
|
+
minAccessCount: 0
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/profile-templates.ts
|
|
948
|
+
import fs6 from "fs";
|
|
949
|
+
import path6 from "path";
|
|
950
|
+
import os6 from "os";
|
|
951
|
+
var BUILT_IN_PROFILES = [
|
|
952
|
+
{
|
|
953
|
+
name: "coder",
|
|
954
|
+
label: "Coder",
|
|
955
|
+
description: "Direct, technical, code-first. Skips pleasantries, shows code.",
|
|
956
|
+
core: `# Coder
|
|
957
|
+
|
|
958
|
+
## Identity
|
|
959
|
+
- Role: Coder is your technical pair programmer
|
|
960
|
+
- Personality: direct, precise, efficient \u2014 code speaks louder than words
|
|
961
|
+
- Communication: lead with code, explain after. No fluff.
|
|
962
|
+
- Values: simplicity over cleverness, working code over perfect code, tests over trust
|
|
963
|
+
- Boundaries: won't pretend to be human, flags when out of depth
|
|
964
|
+
|
|
965
|
+
### Appearance
|
|
966
|
+
- Base: focused developer, dark hoodie, terminal glow
|
|
967
|
+
- Style: minimal
|
|
968
|
+
- Palette: green on black`,
|
|
969
|
+
rules: `# Coder Rules
|
|
970
|
+
|
|
971
|
+
## Always
|
|
972
|
+
- Show code before explaining
|
|
973
|
+
- Include error handling
|
|
974
|
+
- Suggest tests for new code
|
|
975
|
+
|
|
976
|
+
## Never
|
|
977
|
+
- Write code without understanding the requirement
|
|
978
|
+
- Push to main without tests
|
|
979
|
+
- Ignore security implications`
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
name: "writer",
|
|
983
|
+
label: "Writer",
|
|
984
|
+
description: "Creative, eloquent, story-driven. Focuses on narrative and engagement.",
|
|
985
|
+
core: `# Muse
|
|
986
|
+
|
|
987
|
+
## Identity
|
|
988
|
+
- Role: Muse is your creative writing partner
|
|
989
|
+
- Personality: eloquent, imaginative, encouraging \u2014 finds the story in everything
|
|
990
|
+
- Communication: explore ideas together, offer alternatives, celebrate good writing
|
|
991
|
+
- Values: authenticity over formulas, voice over grammar, emotion over information
|
|
992
|
+
- Boundaries: won't write without understanding the audience, flags when content is sensitive
|
|
993
|
+
|
|
994
|
+
### Appearance
|
|
995
|
+
- Base: warm expression, creative energy, pen in hand
|
|
996
|
+
- Style: illustrated
|
|
997
|
+
- Palette: warm amber and cream`,
|
|
998
|
+
rules: `# Writer Rules
|
|
999
|
+
|
|
1000
|
+
## Always
|
|
1001
|
+
- Ask about the target audience
|
|
1002
|
+
- Offer 2-3 angle options before drafting
|
|
1003
|
+
- Read drafts aloud mentally for rhythm
|
|
1004
|
+
|
|
1005
|
+
## Never
|
|
1006
|
+
- Use cliches without subverting them
|
|
1007
|
+
- Write without a clear hook
|
|
1008
|
+
- Ignore tone consistency`
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
name: "researcher",
|
|
1012
|
+
label: "Researcher",
|
|
1013
|
+
description: "Analytical, thorough, citation-focused. Digs deep, verifies claims.",
|
|
1014
|
+
core: `# Scholar
|
|
1015
|
+
|
|
1016
|
+
## Identity
|
|
1017
|
+
- Role: Scholar is your research analyst
|
|
1018
|
+
- Personality: analytical, thorough, intellectually curious \u2014 never takes claims at face value
|
|
1019
|
+
- Communication: present findings with evidence, flag uncertainty, compare perspectives
|
|
1020
|
+
- Values: accuracy over speed, nuance over simplification, primary sources over summaries
|
|
1021
|
+
- Boundaries: clearly marks speculation vs fact, flags when evidence is insufficient
|
|
1022
|
+
|
|
1023
|
+
### Appearance
|
|
1024
|
+
- Base: thoughtful expression, glasses, surrounded by notes
|
|
1025
|
+
- Style: minimal
|
|
1026
|
+
- Palette: navy and white`,
|
|
1027
|
+
rules: `# Researcher Rules
|
|
1028
|
+
|
|
1029
|
+
## Always
|
|
1030
|
+
- Cite sources when making factual claims
|
|
1031
|
+
- Flag confidence level (high/medium/low)
|
|
1032
|
+
- Present multiple perspectives on contested topics
|
|
1033
|
+
|
|
1034
|
+
## Never
|
|
1035
|
+
- Present speculation as fact
|
|
1036
|
+
- Ignore contradicting evidence
|
|
1037
|
+
- Oversimplify complex topics`
|
|
1038
|
+
}
|
|
1039
|
+
];
|
|
1040
|
+
function installProfileTemplate(templateName, userName) {
|
|
1041
|
+
const template = BUILT_IN_PROFILES.find((t) => t.name === templateName);
|
|
1042
|
+
if (!template) return null;
|
|
1043
|
+
const profileDir = path6.join(os6.homedir(), ".acore", "profiles", template.name);
|
|
1044
|
+
if (fs6.existsSync(profileDir)) return `Profile already exists: ${template.name}`;
|
|
1045
|
+
fs6.mkdirSync(profileDir, { recursive: true });
|
|
1046
|
+
let core = template.core;
|
|
1047
|
+
if (userName) {
|
|
1048
|
+
core += `
|
|
1049
|
+
|
|
1050
|
+
---
|
|
1051
|
+
|
|
1052
|
+
## Relationship
|
|
1053
|
+
- Name: ${userName}
|
|
1054
|
+
- Nicknames: []
|
|
1055
|
+
- Communication: [updated over time]
|
|
1056
|
+
- Detail level: balanced
|
|
1057
|
+
`;
|
|
1058
|
+
}
|
|
1059
|
+
fs6.writeFileSync(path6.join(profileDir, "core.md"), core, "utf-8");
|
|
1060
|
+
if (template.rules) {
|
|
1061
|
+
fs6.writeFileSync(path6.join(profileDir, "rules.md"), template.rules, "utf-8");
|
|
1062
|
+
}
|
|
1063
|
+
if (template.skills) {
|
|
1064
|
+
fs6.writeFileSync(path6.join(profileDir, "skills.md"), template.skills, "utf-8");
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/delegate.ts
|
|
1070
|
+
import pc from "picocolors";
|
|
1071
|
+
var isRetryable = (err) => {
|
|
1072
|
+
if (err instanceof Error) {
|
|
1073
|
+
const msg = err.message.toLowerCase();
|
|
1074
|
+
return msg.includes("rate") || msg.includes("timeout") || msg.includes("econnreset");
|
|
1075
|
+
}
|
|
1076
|
+
return false;
|
|
1077
|
+
};
|
|
1078
|
+
async function delegateTask(task, profile, client, mcpManager, options = {}) {
|
|
1079
|
+
const maxTurns = options.maxTurns ?? 10;
|
|
1080
|
+
const silent = options.silent ?? false;
|
|
1081
|
+
const tools = options.tools;
|
|
1082
|
+
try {
|
|
1083
|
+
const { prompt: systemPrompt } = assembleSystemPrompt(void 0, profile);
|
|
1084
|
+
const delegationPrompt = `${systemPrompt}
|
|
1085
|
+
|
|
1086
|
+
<delegation>
|
|
1087
|
+
You are being delegated a specific task by the primary agent. Complete this task thoroughly and return your result. You have access to tools if needed. Focus on the task \u2014 do not ask follow-up questions, just do your best with what you have.
|
|
1088
|
+
</delegation>`;
|
|
1089
|
+
const messages = [
|
|
1090
|
+
{ role: "user", content: task }
|
|
1091
|
+
];
|
|
1092
|
+
const toolsUsed = [];
|
|
1093
|
+
let turns = 0;
|
|
1094
|
+
const onChunk = silent ? () => {
|
|
1095
|
+
} : (chunk) => {
|
|
1096
|
+
if (chunk.type === "text" && chunk.text) {
|
|
1097
|
+
process.stdout.write(chunk.text);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
let response = await withRetry(
|
|
1101
|
+
() => client.chat(delegationPrompt, messages, onChunk, tools),
|
|
1102
|
+
{ maxAttempts: 2, baseDelay: 1e3, retryable: isRetryable }
|
|
1103
|
+
);
|
|
1104
|
+
messages.push(response.message);
|
|
1105
|
+
while (response.toolUses.length > 0 && turns < maxTurns) {
|
|
1106
|
+
turns++;
|
|
1107
|
+
const toolResults = await Promise.all(
|
|
1108
|
+
response.toolUses.map(async (toolUse) => {
|
|
1109
|
+
if (!silent) {
|
|
1110
|
+
process.stdout.write(pc.dim(` [${profile}:${toolUse.name}...]
|
|
1111
|
+
`));
|
|
1112
|
+
}
|
|
1113
|
+
toolsUsed.push(toolUse.name);
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await mcpManager.callTool(toolUse.name, toolUse.input);
|
|
1116
|
+
return {
|
|
1117
|
+
type: "tool_result",
|
|
1118
|
+
tool_use_id: toolUse.id,
|
|
1119
|
+
content: result
|
|
1120
|
+
};
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
return {
|
|
1123
|
+
type: "tool_result",
|
|
1124
|
+
tool_use_id: toolUse.id,
|
|
1125
|
+
content: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1126
|
+
is_error: true
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
})
|
|
1130
|
+
);
|
|
1131
|
+
messages.push({ role: "user", content: toolResults });
|
|
1132
|
+
response = await withRetry(
|
|
1133
|
+
() => client.chat(delegationPrompt, messages, onChunk, tools),
|
|
1134
|
+
{ maxAttempts: 2, baseDelay: 1e3, retryable: isRetryable }
|
|
1135
|
+
);
|
|
1136
|
+
messages.push(response.message);
|
|
1137
|
+
}
|
|
1138
|
+
const finalMessage = response.message;
|
|
1139
|
+
const responseText = typeof finalMessage.content === "string" ? finalMessage.content : finalMessage.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("");
|
|
1140
|
+
return {
|
|
1141
|
+
profile,
|
|
1142
|
+
task,
|
|
1143
|
+
response: responseText,
|
|
1144
|
+
toolsUsed: [...new Set(toolsUsed)],
|
|
1145
|
+
turns,
|
|
1146
|
+
success: true
|
|
1147
|
+
};
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
1150
|
+
log.warn("delegate", `Delegation to ${profile} failed: ${error}`);
|
|
1151
|
+
return {
|
|
1152
|
+
profile,
|
|
1153
|
+
task,
|
|
1154
|
+
response: "",
|
|
1155
|
+
toolsUsed: [],
|
|
1156
|
+
turns: 0,
|
|
1157
|
+
success: false,
|
|
1158
|
+
error
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async function delegateParallel(tasks, client, mcpManager, options = {}) {
|
|
1163
|
+
return Promise.all(
|
|
1164
|
+
tasks.map(
|
|
1165
|
+
({ task, profile }) => delegateTask(task, profile, client, mcpManager, { ...options, silent: true })
|
|
1166
|
+
)
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
async function delegatePipeline(steps, initialInput, client, mcpManager, options = {}) {
|
|
1170
|
+
const results = [];
|
|
1171
|
+
let previousResult = initialInput;
|
|
1172
|
+
for (const step of steps) {
|
|
1173
|
+
const task = step.taskTemplate.replace("{{input}}", previousResult);
|
|
1174
|
+
if (!options.silent) {
|
|
1175
|
+
process.stdout.write(pc.dim(`
|
|
1176
|
+
[delegating to ${step.profile}...]
|
|
1177
|
+
`));
|
|
1178
|
+
}
|
|
1179
|
+
const result = await delegateTask(task, step.profile, client, mcpManager, {
|
|
1180
|
+
...options,
|
|
1181
|
+
silent: true
|
|
1182
|
+
});
|
|
1183
|
+
results.push(result);
|
|
1184
|
+
if (!result.success) break;
|
|
1185
|
+
previousResult = result.response;
|
|
1186
|
+
}
|
|
1187
|
+
return results;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/teams.ts
|
|
1191
|
+
import fs7 from "fs";
|
|
1192
|
+
import path7 from "path";
|
|
1193
|
+
import os7 from "os";
|
|
1194
|
+
import pc2 from "picocolors";
|
|
1195
|
+
function getTeamsDir() {
|
|
1196
|
+
return path7.join(os7.homedir(), ".acore", "teams");
|
|
1197
|
+
}
|
|
1198
|
+
function ensureTeamsDir() {
|
|
1199
|
+
const dir = getTeamsDir();
|
|
1200
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
1201
|
+
return dir;
|
|
1202
|
+
}
|
|
1203
|
+
function teamPath(name) {
|
|
1204
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
1205
|
+
return path7.join(ensureTeamsDir(), `${slug}.json`);
|
|
1206
|
+
}
|
|
1207
|
+
function createTeam(team) {
|
|
1208
|
+
const fp = teamPath(team.name);
|
|
1209
|
+
fs7.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
|
|
1210
|
+
}
|
|
1211
|
+
function loadTeam(name) {
|
|
1212
|
+
const fp = teamPath(name);
|
|
1213
|
+
if (!fs7.existsSync(fp)) return null;
|
|
1214
|
+
try {
|
|
1215
|
+
return JSON.parse(fs7.readFileSync(fp, "utf-8"));
|
|
1216
|
+
} catch {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function listTeams() {
|
|
1221
|
+
const dir = getTeamsDir();
|
|
1222
|
+
if (!fs7.existsSync(dir)) return [];
|
|
1223
|
+
const teams = [];
|
|
1224
|
+
for (const file of fs7.readdirSync(dir)) {
|
|
1225
|
+
if (!file.endsWith(".json")) continue;
|
|
1226
|
+
try {
|
|
1227
|
+
const content = fs7.readFileSync(path7.join(dir, file), "utf-8");
|
|
1228
|
+
teams.push(JSON.parse(content));
|
|
1229
|
+
} catch {
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return teams;
|
|
1233
|
+
}
|
|
1234
|
+
function deleteTeam(name) {
|
|
1235
|
+
const fp = teamPath(name);
|
|
1236
|
+
if (!fs7.existsSync(fp)) return false;
|
|
1237
|
+
fs7.unlinkSync(fp);
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
async function runTeam(team, task, client, mcpManager, tools) {
|
|
1241
|
+
process.stdout.write(pc2.dim(`
|
|
1242
|
+
Team: ${team.name} (${team.workflow} mode)
|
|
1243
|
+
`));
|
|
1244
|
+
process.stdout.write(pc2.dim(` Members: ${team.members.map((m) => m.profile).join(", ")}
|
|
1245
|
+
|
|
1246
|
+
`));
|
|
1247
|
+
switch (team.workflow) {
|
|
1248
|
+
case "pipeline":
|
|
1249
|
+
return runPipeline(team, task, client, mcpManager, tools);
|
|
1250
|
+
case "parallel":
|
|
1251
|
+
return runParallel(team, task, client, mcpManager, tools);
|
|
1252
|
+
case "coordinator":
|
|
1253
|
+
return runCoordinator(team, task, client, mcpManager, tools);
|
|
1254
|
+
default:
|
|
1255
|
+
return {
|
|
1256
|
+
team: team.name,
|
|
1257
|
+
task,
|
|
1258
|
+
workflow: team.workflow,
|
|
1259
|
+
results: [],
|
|
1260
|
+
finalOutput: `Unknown workflow mode: ${team.workflow}`,
|
|
1261
|
+
success: false
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
async function runPipeline(team, task, client, mcpManager, tools) {
|
|
1266
|
+
const steps = team.members.map((m, i) => ({
|
|
1267
|
+
profile: m.profile,
|
|
1268
|
+
taskTemplate: i === 0 ? `${task}
|
|
1269
|
+
|
|
1270
|
+
Your role: ${m.role}` : `${m.role}. Here is the previous agent's work:
|
|
1271
|
+
|
|
1272
|
+
{{input}}`
|
|
1273
|
+
}));
|
|
1274
|
+
for (const step of steps) {
|
|
1275
|
+
process.stdout.write(pc2.dim(` [${step.profile}: ${team.members.find((m) => m.profile === step.profile)?.role}...]
|
|
1276
|
+
`));
|
|
1277
|
+
}
|
|
1278
|
+
const results = await delegatePipeline(steps, task, client, mcpManager, {
|
|
1279
|
+
tools,
|
|
1280
|
+
silent: true
|
|
1281
|
+
});
|
|
1282
|
+
const lastResult = results[results.length - 1];
|
|
1283
|
+
const success = results.every((r) => r.success);
|
|
1284
|
+
return {
|
|
1285
|
+
team: team.name,
|
|
1286
|
+
task,
|
|
1287
|
+
workflow: "pipeline",
|
|
1288
|
+
results,
|
|
1289
|
+
finalOutput: lastResult?.response || "",
|
|
1290
|
+
success
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
async function runParallel(team, task, client, mcpManager, tools) {
|
|
1294
|
+
const tasks = team.members.map((m) => ({
|
|
1295
|
+
profile: m.profile,
|
|
1296
|
+
task: `${task}
|
|
1297
|
+
|
|
1298
|
+
Your specific role: ${m.role}. Focus only on your role.`
|
|
1299
|
+
}));
|
|
1300
|
+
for (const m of team.members) {
|
|
1301
|
+
process.stdout.write(pc2.dim(` [${m.profile}: ${m.role} (parallel)...]
|
|
1302
|
+
`));
|
|
1303
|
+
}
|
|
1304
|
+
const results = await delegateParallel(tasks, client, mcpManager, { tools });
|
|
1305
|
+
const mergeInput = results.filter((r) => r.success).map((r) => `[${r.profile} \u2014 ${team.members.find((m) => m.profile === r.profile)?.role}]:
|
|
1306
|
+
${r.response}`).join("\n\n---\n\n");
|
|
1307
|
+
process.stdout.write(pc2.dim(` [merging results...]
|
|
1308
|
+
`));
|
|
1309
|
+
const mergeResult = await delegateTask(
|
|
1310
|
+
`You are the team coordinator. Multiple agents worked on this task in parallel. Merge their outputs into a single cohesive result. Keep the best parts from each.
|
|
1311
|
+
|
|
1312
|
+
Original task: ${task}
|
|
1313
|
+
|
|
1314
|
+
${mergeInput}`,
|
|
1315
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1316
|
+
client,
|
|
1317
|
+
mcpManager,
|
|
1318
|
+
{ tools, silent: true }
|
|
1319
|
+
);
|
|
1320
|
+
return {
|
|
1321
|
+
team: team.name,
|
|
1322
|
+
task,
|
|
1323
|
+
workflow: "parallel",
|
|
1324
|
+
results: [...results, mergeResult],
|
|
1325
|
+
finalOutput: mergeResult.response,
|
|
1326
|
+
success: results.some((r) => r.success)
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
async function runCoordinator(team, task, client, mcpManager, tools) {
|
|
1330
|
+
const memberDescriptions = team.members.map((m) => `- ${m.profile}: ${m.role}`).join("\n");
|
|
1331
|
+
process.stdout.write(pc2.dim(` [coordinator planning...]
|
|
1332
|
+
`));
|
|
1333
|
+
const planResult = await delegateTask(
|
|
1334
|
+
`You are the coordinator of a team. Your job is to break down this task and decide which team members should handle each part.
|
|
1335
|
+
|
|
1336
|
+
Team members:
|
|
1337
|
+
${memberDescriptions}
|
|
1338
|
+
|
|
1339
|
+
Task: ${task}
|
|
1340
|
+
|
|
1341
|
+
Respond with a JSON array of assignments:
|
|
1342
|
+
[{"profile": "member-name", "subtask": "what they should do"}]
|
|
1343
|
+
|
|
1344
|
+
Only use the JSON array, no other text.`,
|
|
1345
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1346
|
+
client,
|
|
1347
|
+
mcpManager,
|
|
1348
|
+
{ tools: void 0, silent: true, maxTurns: 0 }
|
|
1349
|
+
);
|
|
1350
|
+
if (!planResult.success) {
|
|
1351
|
+
return {
|
|
1352
|
+
team: team.name,
|
|
1353
|
+
task,
|
|
1354
|
+
workflow: "coordinator",
|
|
1355
|
+
results: [planResult],
|
|
1356
|
+
finalOutput: `Coordinator failed to plan: ${planResult.error}`,
|
|
1357
|
+
success: false
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
let assignments;
|
|
1361
|
+
try {
|
|
1362
|
+
let cleaned = planResult.response.trim();
|
|
1363
|
+
const codeBlockMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
1364
|
+
if (codeBlockMatch) cleaned = codeBlockMatch[1].trim();
|
|
1365
|
+
assignments = JSON.parse(cleaned);
|
|
1366
|
+
} catch {
|
|
1367
|
+
assignments = team.members.map((m) => ({ profile: m.profile, subtask: `${m.role}: ${task}` }));
|
|
1368
|
+
}
|
|
1369
|
+
for (const a of assignments) {
|
|
1370
|
+
process.stdout.write(pc2.dim(` [${a.profile}: ${a.subtask.slice(0, 60)}...]
|
|
1371
|
+
`));
|
|
1372
|
+
}
|
|
1373
|
+
const results = await delegateParallel(
|
|
1374
|
+
assignments.map((a) => ({ profile: a.profile, task: a.subtask })),
|
|
1375
|
+
client,
|
|
1376
|
+
mcpManager,
|
|
1377
|
+
{ tools }
|
|
1378
|
+
);
|
|
1379
|
+
const mergeInput = results.filter((r) => r.success).map((r, i) => `[${assignments[i]?.profile} \u2014 ${assignments[i]?.subtask}]:
|
|
1380
|
+
${r.response}`).join("\n\n---\n\n");
|
|
1381
|
+
process.stdout.write(pc2.dim(` [coordinator merging...]
|
|
1382
|
+
`));
|
|
1383
|
+
const mergeResult = await delegateTask(
|
|
1384
|
+
`You are the team coordinator. Your team members completed their assigned work. Combine their outputs into a single cohesive, polished result.
|
|
1385
|
+
|
|
1386
|
+
Original task: ${task}
|
|
1387
|
+
|
|
1388
|
+
${mergeInput}`,
|
|
1389
|
+
team.coordinator === "default" ? team.members[0]?.profile || "default" : team.coordinator,
|
|
1390
|
+
client,
|
|
1391
|
+
mcpManager,
|
|
1392
|
+
{ tools, silent: true }
|
|
1393
|
+
);
|
|
1394
|
+
return {
|
|
1395
|
+
team: team.name,
|
|
1396
|
+
task,
|
|
1397
|
+
workflow: "coordinator",
|
|
1398
|
+
results: [...results, mergeResult],
|
|
1399
|
+
finalOutput: mergeResult.response,
|
|
1400
|
+
success: results.some((r) => r.success)
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
function formatTeam(team) {
|
|
1404
|
+
const lines = [];
|
|
1405
|
+
lines.push(`Team: ${pc2.bold(team.name)}`);
|
|
1406
|
+
lines.push(`Goal: ${team.goal}`);
|
|
1407
|
+
lines.push(`Mode: ${team.workflow}`);
|
|
1408
|
+
lines.push(`Coordinator: ${team.coordinator}`);
|
|
1409
|
+
lines.push("");
|
|
1410
|
+
lines.push("Members:");
|
|
1411
|
+
for (const m of team.members) {
|
|
1412
|
+
lines.push(` ${pc2.bold(m.profile)} \u2014 ${m.role}`);
|
|
1413
|
+
}
|
|
1414
|
+
return lines.join("\n");
|
|
1415
|
+
}
|
|
1416
|
+
function formatTeamResult(result) {
|
|
1417
|
+
const lines = [];
|
|
1418
|
+
lines.push(`
|
|
1419
|
+
${pc2.bold(`Team: ${result.team}`)} (${result.workflow})`);
|
|
1420
|
+
for (const r of result.results) {
|
|
1421
|
+
const status = r.success ? pc2.green("\u2713") : pc2.red("\u2717");
|
|
1422
|
+
const tools = r.toolsUsed.length > 0 ? pc2.dim(` (${r.toolsUsed.join(", ")})`) : "";
|
|
1423
|
+
lines.push(` ${status} ${pc2.bold(r.profile)}${tools}`);
|
|
1424
|
+
}
|
|
1425
|
+
lines.push("");
|
|
1426
|
+
lines.push(result.finalOutput);
|
|
1427
|
+
return lines.join("\n");
|
|
1428
|
+
}
|
|
1429
|
+
var BUILT_IN_TEAMS = [
|
|
1430
|
+
{
|
|
1431
|
+
name: "content-team",
|
|
1432
|
+
goal: "Create and publish high-quality content",
|
|
1433
|
+
coordinator: "default",
|
|
1434
|
+
members: [
|
|
1435
|
+
{ profile: "writer", role: "Draft compelling content with engaging narrative" },
|
|
1436
|
+
{ profile: "researcher", role: "Fact-check claims and add citations" }
|
|
1437
|
+
],
|
|
1438
|
+
workflow: "pipeline"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: "dev-team",
|
|
1442
|
+
goal: "Build and review code with quality assurance",
|
|
1443
|
+
coordinator: "default",
|
|
1444
|
+
members: [
|
|
1445
|
+
{ profile: "coder", role: "Write clean, tested implementation code" },
|
|
1446
|
+
{ profile: "researcher", role: "Review for security, performance, and best practices" }
|
|
1447
|
+
],
|
|
1448
|
+
workflow: "pipeline"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
name: "research-team",
|
|
1452
|
+
goal: "Deep research with multiple perspectives",
|
|
1453
|
+
coordinator: "default",
|
|
1454
|
+
members: [
|
|
1455
|
+
{ profile: "researcher", role: "Research the topic thoroughly with citations" },
|
|
1456
|
+
{ profile: "writer", role: "Synthesize findings into clear, readable format" }
|
|
1457
|
+
],
|
|
1458
|
+
workflow: "pipeline"
|
|
1459
|
+
}
|
|
1460
|
+
];
|
|
1461
|
+
|
|
1462
|
+
// src/plans.ts
|
|
1463
|
+
import fs8 from "fs";
|
|
1464
|
+
import path8 from "path";
|
|
1465
|
+
import os8 from "os";
|
|
1466
|
+
function getPlansDir() {
|
|
1467
|
+
const localDir = path8.join(process.cwd(), ".acore", "plans");
|
|
1468
|
+
const localAcore = path8.join(process.cwd(), ".acore");
|
|
1469
|
+
if (fs8.existsSync(localAcore)) return localDir;
|
|
1470
|
+
return path8.join(os8.homedir(), ".acore", "plans");
|
|
1471
|
+
}
|
|
1472
|
+
function ensurePlansDir() {
|
|
1473
|
+
const dir = getPlansDir();
|
|
1474
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
1475
|
+
return dir;
|
|
1476
|
+
}
|
|
1477
|
+
function planPath(name) {
|
|
1478
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1479
|
+
return path8.join(ensurePlansDir(), `${slug}.md`);
|
|
1480
|
+
}
|
|
1481
|
+
function serializePlan(plan) {
|
|
1482
|
+
const lines = [];
|
|
1483
|
+
lines.push(`# ${plan.name}`);
|
|
1484
|
+
lines.push("");
|
|
1485
|
+
lines.push(`**Goal:** ${plan.goal}`);
|
|
1486
|
+
lines.push(`**Created:** ${plan.createdAt}`);
|
|
1487
|
+
lines.push(`**Updated:** ${plan.updatedAt}`);
|
|
1488
|
+
lines.push(`**Active:** ${plan.active}`);
|
|
1489
|
+
lines.push("");
|
|
1490
|
+
lines.push("## Steps");
|
|
1491
|
+
lines.push("");
|
|
1492
|
+
for (const step of plan.steps) {
|
|
1493
|
+
lines.push(`- [${step.done ? "x" : " "}] ${step.text}`);
|
|
1494
|
+
}
|
|
1495
|
+
lines.push("");
|
|
1496
|
+
return lines.join("\n");
|
|
1497
|
+
}
|
|
1498
|
+
function parsePlan(content, filePath) {
|
|
1499
|
+
try {
|
|
1500
|
+
const nameMatch = content.match(/^# (.+)/m);
|
|
1501
|
+
const goalMatch = content.match(/\*\*Goal:\*\*\s*(.+)/);
|
|
1502
|
+
const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
|
|
1503
|
+
const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
|
|
1504
|
+
const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
|
|
1505
|
+
const name = nameMatch?.[1]?.trim() || path8.basename(filePath, ".md");
|
|
1506
|
+
const goal = goalMatch?.[1]?.trim() || "";
|
|
1507
|
+
const createdAt = createdMatch?.[1]?.trim() || "";
|
|
1508
|
+
const updatedAt = updatedMatch?.[1]?.trim() || "";
|
|
1509
|
+
const active = activeMatch?.[1]?.trim() === "true";
|
|
1510
|
+
const steps = [];
|
|
1511
|
+
const stepMatches = content.matchAll(/- \[([ x])\] (.+)/g);
|
|
1512
|
+
for (const match of stepMatches) {
|
|
1513
|
+
steps.push({
|
|
1514
|
+
done: match[1] === "x",
|
|
1515
|
+
text: match[2].trim()
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
return { name, goal, steps, createdAt, updatedAt, active };
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
log.debug("plans", "Failed to parse plan: " + filePath, err);
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function createPlan(name, goal, steps) {
|
|
1525
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1526
|
+
const plan = {
|
|
1527
|
+
name,
|
|
1528
|
+
goal,
|
|
1529
|
+
steps: steps.map((text2) => ({ text: text2, done: false })),
|
|
1530
|
+
createdAt: now,
|
|
1531
|
+
updatedAt: now,
|
|
1532
|
+
active: true
|
|
1533
|
+
};
|
|
1534
|
+
const existing = listPlans();
|
|
1535
|
+
for (const p3 of existing) {
|
|
1536
|
+
if (p3.active) {
|
|
1537
|
+
p3.active = false;
|
|
1538
|
+
p3.updatedAt = now;
|
|
1539
|
+
savePlan(p3);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
savePlan(plan);
|
|
1543
|
+
return plan;
|
|
1544
|
+
}
|
|
1545
|
+
function savePlan(plan) {
|
|
1546
|
+
const fp = planPath(plan.name);
|
|
1547
|
+
fs8.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
1548
|
+
}
|
|
1549
|
+
function loadPlan(name) {
|
|
1550
|
+
const fp = planPath(name);
|
|
1551
|
+
if (!fs8.existsSync(fp)) return null;
|
|
1552
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
1553
|
+
return parsePlan(content, fp);
|
|
1554
|
+
}
|
|
1555
|
+
function listPlans() {
|
|
1556
|
+
const dir = getPlansDir();
|
|
1557
|
+
if (!fs8.existsSync(dir)) return [];
|
|
1558
|
+
const plans = [];
|
|
1559
|
+
for (const file of fs8.readdirSync(dir)) {
|
|
1560
|
+
if (!file.endsWith(".md")) continue;
|
|
1561
|
+
const fp = path8.join(dir, file);
|
|
1562
|
+
const content = fs8.readFileSync(fp, "utf-8");
|
|
1563
|
+
const plan = parsePlan(content, fp);
|
|
1564
|
+
if (plan) plans.push(plan);
|
|
1565
|
+
}
|
|
1566
|
+
return plans;
|
|
1567
|
+
}
|
|
1568
|
+
function getActivePlan() {
|
|
1569
|
+
const plans = listPlans();
|
|
1570
|
+
return plans.find((p3) => p3.active) || null;
|
|
1571
|
+
}
|
|
1572
|
+
function markStepDone(plan, stepIndex) {
|
|
1573
|
+
if (stepIndex < 0 || stepIndex >= plan.steps.length) return false;
|
|
1574
|
+
plan.steps[stepIndex].done = true;
|
|
1575
|
+
plan.updatedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1576
|
+
savePlan(plan);
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
function markStepUndone(plan, stepIndex) {
|
|
1580
|
+
if (stepIndex < 0 || stepIndex >= plan.steps.length) return false;
|
|
1581
|
+
plan.steps[stepIndex].done = false;
|
|
1582
|
+
plan.updatedAt = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1583
|
+
savePlan(plan);
|
|
1584
|
+
return true;
|
|
1585
|
+
}
|
|
1586
|
+
function setActivePlan(name) {
|
|
1587
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1588
|
+
const plans = listPlans();
|
|
1589
|
+
for (const p3 of plans) {
|
|
1590
|
+
if (p3.active) {
|
|
1591
|
+
p3.active = false;
|
|
1592
|
+
p3.updatedAt = now;
|
|
1593
|
+
savePlan(p3);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
const target = loadPlan(name);
|
|
1597
|
+
if (!target) return null;
|
|
1598
|
+
target.active = true;
|
|
1599
|
+
target.updatedAt = now;
|
|
1600
|
+
savePlan(target);
|
|
1601
|
+
return target;
|
|
1602
|
+
}
|
|
1603
|
+
function formatPlan(plan) {
|
|
1604
|
+
const total = plan.steps.length;
|
|
1605
|
+
const done = plan.steps.filter((s) => s.done).length;
|
|
1606
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
1607
|
+
const bar = progressBar(pct);
|
|
1608
|
+
const lines = [];
|
|
1609
|
+
lines.push(`Plan: ${plan.name} ${plan.active ? "(active)" : "(inactive)"}`);
|
|
1610
|
+
lines.push(`Goal: ${plan.goal}`);
|
|
1611
|
+
lines.push(`Progress: ${bar} ${done}/${total} (${pct}%)`);
|
|
1612
|
+
lines.push("");
|
|
1613
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
1614
|
+
const step = plan.steps[i];
|
|
1615
|
+
const marker = step.done ? "\u2713" : " ";
|
|
1616
|
+
const num = String(i + 1).padStart(2, " ");
|
|
1617
|
+
lines.push(` ${num}. [${marker}] ${step.text}`);
|
|
1618
|
+
}
|
|
1619
|
+
if (done === total && total > 0) {
|
|
1620
|
+
lines.push("\n All steps complete!");
|
|
1621
|
+
} else {
|
|
1622
|
+
const next = plan.steps.findIndex((s) => !s.done);
|
|
1623
|
+
if (next >= 0) {
|
|
1624
|
+
lines.push(`
|
|
1625
|
+
Next: Step ${next + 1} \u2014 ${plan.steps[next].text}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return lines.join("\n");
|
|
1629
|
+
}
|
|
1630
|
+
function formatPlanForPrompt(plan) {
|
|
1631
|
+
const total = plan.steps.length;
|
|
1632
|
+
const done = plan.steps.filter((s) => s.done).length;
|
|
1633
|
+
const lines = [];
|
|
1634
|
+
lines.push(`<active-plan name="${plan.name}" progress="${done}/${total}">`);
|
|
1635
|
+
lines.push(`Goal: ${plan.goal}`);
|
|
1636
|
+
lines.push("");
|
|
1637
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
1638
|
+
const step = plan.steps[i];
|
|
1639
|
+
lines.push(`- [${step.done ? "x" : " "}] Step ${i + 1}: ${step.text}`);
|
|
1640
|
+
}
|
|
1641
|
+
const next = plan.steps.findIndex((s) => !s.done);
|
|
1642
|
+
if (next >= 0) {
|
|
1643
|
+
lines.push("");
|
|
1644
|
+
lines.push(`Current focus: Step ${next + 1} \u2014 ${plan.steps[next].text}`);
|
|
1645
|
+
lines.push("After completing the current step, remind the user to mark it done with /plan done and suggest committing their work.");
|
|
1646
|
+
}
|
|
1647
|
+
lines.push("</active-plan>");
|
|
1648
|
+
return lines.join("\n");
|
|
1649
|
+
}
|
|
1650
|
+
function progressBar(pct) {
|
|
1651
|
+
const filled = Math.round(pct / 5);
|
|
1652
|
+
const empty = 20 - filled;
|
|
1653
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}]`;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
820
1656
|
// src/commands.ts
|
|
821
1657
|
function readEcosystemFile(filePath, label) {
|
|
822
|
-
if (!
|
|
823
|
-
return
|
|
1658
|
+
if (!fs9.existsSync(filePath)) {
|
|
1659
|
+
return pc3.dim(`No ${label} file found at ${filePath}`);
|
|
824
1660
|
}
|
|
825
|
-
return
|
|
1661
|
+
return fs9.readFileSync(filePath, "utf-8").trim();
|
|
826
1662
|
}
|
|
827
1663
|
function parseCommand(input) {
|
|
828
1664
|
const trimmed = input.trim();
|
|
@@ -834,25 +1670,25 @@ function parseCommand(input) {
|
|
|
834
1670
|
}
|
|
835
1671
|
async function mcpWrite(ctx, layer, tool, args) {
|
|
836
1672
|
if (!ctx.mcpManager) {
|
|
837
|
-
return
|
|
1673
|
+
return pc3.red(`Cannot modify ${layer}: aman-mcp not connected. Start it with: npx @aman_asmuei/aman-mcp`);
|
|
838
1674
|
}
|
|
839
1675
|
const result = await ctx.mcpManager.callTool(tool, args);
|
|
840
1676
|
if (result.startsWith("Error")) {
|
|
841
|
-
return
|
|
1677
|
+
return pc3.red(result);
|
|
842
1678
|
}
|
|
843
|
-
return
|
|
1679
|
+
return pc3.green(result);
|
|
844
1680
|
}
|
|
845
1681
|
async function handleIdentityCommand(action, args, ctx) {
|
|
846
|
-
const home2 =
|
|
1682
|
+
const home2 = os9.homedir();
|
|
847
1683
|
if (!action) {
|
|
848
|
-
const content = readEcosystemFile(
|
|
1684
|
+
const content = readEcosystemFile(path9.join(home2, ".acore", "core.md"), "identity (acore)");
|
|
849
1685
|
return { handled: true, output: content };
|
|
850
1686
|
}
|
|
851
1687
|
if (action === "update") {
|
|
852
1688
|
if (args.length === 0) {
|
|
853
1689
|
return {
|
|
854
1690
|
handled: true,
|
|
855
|
-
output:
|
|
1691
|
+
output: pc3.yellow("Usage: /identity update <section>\nTip: describe changes in natural language and the AI will update via MCP.")
|
|
856
1692
|
};
|
|
857
1693
|
}
|
|
858
1694
|
const section = args[0];
|
|
@@ -860,23 +1696,23 @@ async function handleIdentityCommand(action, args, ctx) {
|
|
|
860
1696
|
if (!content) {
|
|
861
1697
|
return {
|
|
862
1698
|
handled: true,
|
|
863
|
-
output:
|
|
1699
|
+
output: pc3.yellow("Usage: /identity update <section> <new content...>\nExample: /identity update Personality Warm, curious, and direct.")
|
|
864
1700
|
};
|
|
865
1701
|
}
|
|
866
1702
|
const output = await mcpWrite(ctx, "identity", "identity_update_section", { section, content });
|
|
867
1703
|
return { handled: true, output };
|
|
868
1704
|
}
|
|
869
|
-
return { handled: true, output:
|
|
1705
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /identity ${action}. Use /identity or /identity update <section>.`) };
|
|
870
1706
|
}
|
|
871
1707
|
async function handleRulesCommand(action, args, ctx) {
|
|
872
|
-
const home2 =
|
|
1708
|
+
const home2 = os9.homedir();
|
|
873
1709
|
if (!action) {
|
|
874
|
-
const content = readEcosystemFile(
|
|
1710
|
+
const content = readEcosystemFile(path9.join(home2, ".arules", "rules.md"), "guardrails (arules)");
|
|
875
1711
|
return { handled: true, output: content };
|
|
876
1712
|
}
|
|
877
1713
|
if (action === "add") {
|
|
878
1714
|
if (args.length < 2) {
|
|
879
|
-
return { handled: true, output:
|
|
1715
|
+
return { handled: true, output: pc3.yellow("Usage: /rules add <category> <rule text...>") };
|
|
880
1716
|
}
|
|
881
1717
|
const category = args[0];
|
|
882
1718
|
const rule = args.slice(1).join(" ");
|
|
@@ -885,41 +1721,41 @@ async function handleRulesCommand(action, args, ctx) {
|
|
|
885
1721
|
}
|
|
886
1722
|
if (action === "remove") {
|
|
887
1723
|
if (args.length < 2) {
|
|
888
|
-
return { handled: true, output:
|
|
1724
|
+
return { handled: true, output: pc3.yellow("Usage: /rules remove <category> <index>") };
|
|
889
1725
|
}
|
|
890
1726
|
const output = await mcpWrite(ctx, "rules", "rules_remove", { category: args[0], index: parseInt(args[1], 10) });
|
|
891
1727
|
return { handled: true, output };
|
|
892
1728
|
}
|
|
893
1729
|
if (action === "toggle") {
|
|
894
1730
|
if (args.length < 2) {
|
|
895
|
-
return { handled: true, output:
|
|
1731
|
+
return { handled: true, output: pc3.yellow("Usage: /rules toggle <category> <index>") };
|
|
896
1732
|
}
|
|
897
1733
|
const output = await mcpWrite(ctx, "rules", "rules_toggle", { category: args[0], index: parseInt(args[1], 10) });
|
|
898
1734
|
return { handled: true, output };
|
|
899
1735
|
}
|
|
900
|
-
return { handled: true, output:
|
|
1736
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /rules ${action}. Use /rules [add|remove|toggle].`) };
|
|
901
1737
|
}
|
|
902
1738
|
async function handleWorkflowsCommand(action, args, ctx) {
|
|
903
|
-
const home2 =
|
|
1739
|
+
const home2 = os9.homedir();
|
|
904
1740
|
if (!action) {
|
|
905
|
-
const content = readEcosystemFile(
|
|
1741
|
+
const content = readEcosystemFile(path9.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
906
1742
|
return { handled: true, output: content };
|
|
907
1743
|
}
|
|
908
1744
|
if (action === "add") {
|
|
909
1745
|
if (args.length < 1) {
|
|
910
|
-
return { handled: true, output:
|
|
1746
|
+
return { handled: true, output: pc3.yellow("Usage: /workflows add <name>") };
|
|
911
1747
|
}
|
|
912
1748
|
const output = await mcpWrite(ctx, "workflows", "workflow_add", { name: args.join(" ") });
|
|
913
1749
|
return { handled: true, output };
|
|
914
1750
|
}
|
|
915
1751
|
if (action === "remove") {
|
|
916
1752
|
if (args.length < 1) {
|
|
917
|
-
return { handled: true, output:
|
|
1753
|
+
return { handled: true, output: pc3.yellow("Usage: /workflows remove <name>") };
|
|
918
1754
|
}
|
|
919
1755
|
const output = await mcpWrite(ctx, "workflows", "workflow_remove", { name: args.join(" ") });
|
|
920
1756
|
return { handled: true, output };
|
|
921
1757
|
}
|
|
922
|
-
return { handled: true, output:
|
|
1758
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /workflows ${action}. Use /workflows [add|remove].`) };
|
|
923
1759
|
}
|
|
924
1760
|
var AKIT_REGISTRY = [
|
|
925
1761
|
{ name: "web-search", description: "Search the web for current information", category: "search", mcp: { package: "@anthropic/web-search", command: "npx", args: ["-y", "@anthropic/web-search"] } },
|
|
@@ -936,42 +1772,43 @@ var AKIT_REGISTRY = [
|
|
|
936
1772
|
{ name: "docker", description: "Manage Docker containers", category: "automation", mcp: { package: "@modelcontextprotocol/server-docker", command: "npx", args: ["-y", "@modelcontextprotocol/server-docker"] } },
|
|
937
1773
|
{ name: "slack", description: "Send and read Slack messages", category: "communication", mcp: { package: "@modelcontextprotocol/server-slack", command: "npx", args: ["-y", "@modelcontextprotocol/server-slack"], env: { SLACK_BOT_TOKEN: "" } }, envHint: "Set SLACK_BOT_TOKEN from your Slack app settings" },
|
|
938
1774
|
{ name: "notion", description: "Read and write Notion pages", category: "communication", mcp: { package: "@notionhq/notion-mcp-server", command: "npx", args: ["-y", "@notionhq/notion-mcp-server"], env: { NOTION_API_KEY: "" } }, envHint: "Set NOTION_API_KEY from https://notion.so/my-integrations" },
|
|
1775
|
+
{ name: "social", description: "Post to Bluesky, X/Twitter, Threads", category: "communication", mcp: { package: "@aman_asmuei/aman-social", command: "npx", args: ["-y", "@aman_asmuei/aman-social"] }, envHint: "Set BLUESKY_HANDLE + BLUESKY_APP_PASSWORD, TWITTER_API_KEY + secrets, or THREADS_ACCESS_TOKEN" },
|
|
939
1776
|
{ name: "memory", description: "Persistent AI memory via amem", category: "memory", mcp: { package: "@aman_asmuei/amem", command: "npx", args: ["-y", "@aman_asmuei/amem"] } },
|
|
940
1777
|
{ name: "docling", description: "Convert PDF, DOCX, PPTX, XLSX to markdown", category: "documents", mcp: { package: "docling-mcp", command: "uvx", args: ["docling-mcp"] }, envHint: "Requires Python 3.10+. Install: pip install docling" }
|
|
941
1778
|
];
|
|
942
1779
|
function loadAkitInstalled() {
|
|
943
|
-
const filePath =
|
|
944
|
-
if (!
|
|
1780
|
+
const filePath = path9.join(os9.homedir(), ".akit", "installed.json");
|
|
1781
|
+
if (!fs9.existsSync(filePath)) return [];
|
|
945
1782
|
try {
|
|
946
|
-
return JSON.parse(
|
|
1783
|
+
return JSON.parse(fs9.readFileSync(filePath, "utf-8"));
|
|
947
1784
|
} catch {
|
|
948
1785
|
return [];
|
|
949
1786
|
}
|
|
950
1787
|
}
|
|
951
1788
|
function saveAkitInstalled(tools) {
|
|
952
|
-
const dir =
|
|
953
|
-
|
|
954
|
-
|
|
1789
|
+
const dir = path9.join(os9.homedir(), ".akit");
|
|
1790
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1791
|
+
fs9.writeFileSync(path9.join(dir, "installed.json"), JSON.stringify(tools, null, 2) + "\n", "utf-8");
|
|
955
1792
|
}
|
|
956
1793
|
function addToAmanAgentConfig(name, mcpConfig) {
|
|
957
|
-
const configPath =
|
|
958
|
-
if (!
|
|
1794
|
+
const configPath = path9.join(os9.homedir(), ".aman-agent", "config.json");
|
|
1795
|
+
if (!fs9.existsSync(configPath)) return;
|
|
959
1796
|
try {
|
|
960
|
-
const config = JSON.parse(
|
|
1797
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
961
1798
|
if (!config.mcpServers) config.mcpServers = {};
|
|
962
1799
|
config.mcpServers[name] = mcpConfig;
|
|
963
|
-
|
|
1800
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
964
1801
|
} catch {
|
|
965
1802
|
}
|
|
966
1803
|
}
|
|
967
1804
|
function removeFromAmanAgentConfig(name) {
|
|
968
|
-
const configPath =
|
|
969
|
-
if (!
|
|
1805
|
+
const configPath = path9.join(os9.homedir(), ".aman-agent", "config.json");
|
|
1806
|
+
if (!fs9.existsSync(configPath)) return;
|
|
970
1807
|
try {
|
|
971
|
-
const config = JSON.parse(
|
|
1808
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
972
1809
|
if (config.mcpServers) {
|
|
973
1810
|
delete config.mcpServers[name];
|
|
974
|
-
|
|
1811
|
+
fs9.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
975
1812
|
}
|
|
976
1813
|
} catch {
|
|
977
1814
|
}
|
|
@@ -983,27 +1820,27 @@ function handleAkitCommand(action, args) {
|
|
|
983
1820
|
const available2 = AKIT_REGISTRY.filter((t) => !installedNames.has(t.name));
|
|
984
1821
|
if (args.length < 1) {
|
|
985
1822
|
if (available2.length === 0) {
|
|
986
|
-
return { handled: true, output:
|
|
1823
|
+
return { handled: true, output: pc3.green("All tools are installed!") };
|
|
987
1824
|
}
|
|
988
|
-
const lines3 = [
|
|
1825
|
+
const lines3 = [pc3.bold("Select a tool to install:"), ""];
|
|
989
1826
|
available2.forEach((tool2, i) => {
|
|
990
|
-
const num2 =
|
|
991
|
-
lines3.push(` ${num2} ${tool2.name.padEnd(16)} ${
|
|
1827
|
+
const num2 = pc3.cyan(String(i + 1).padStart(2));
|
|
1828
|
+
lines3.push(` ${num2} ${tool2.name.padEnd(16)} ${pc3.dim(tool2.description)}`);
|
|
992
1829
|
});
|
|
993
1830
|
lines3.push("");
|
|
994
|
-
lines3.push(` Type: ${
|
|
995
|
-
lines3.push(` Custom: ${
|
|
1831
|
+
lines3.push(` Type: ${pc3.cyan("/akit add <number>")} or ${pc3.cyan("/akit add <name>")}`);
|
|
1832
|
+
lines3.push(` Custom: ${pc3.cyan("/akit add custom <name> <command> <args...>")}`);
|
|
996
1833
|
return { handled: true, output: lines3.join("\n") };
|
|
997
1834
|
}
|
|
998
1835
|
if (args[0].toLowerCase() === "custom") {
|
|
999
1836
|
if (args.length < 3) {
|
|
1000
|
-
return { handled: true, output:
|
|
1837
|
+
return { handled: true, output: pc3.yellow("Usage: /akit add custom <name> <command> <args...>\nExample: /akit add custom my-tool npx -y @org/my-mcp-server") };
|
|
1001
1838
|
}
|
|
1002
1839
|
const customName = args[1];
|
|
1003
1840
|
const customCommand = args[2];
|
|
1004
1841
|
const customArgs = args.slice(3);
|
|
1005
1842
|
if (installedNames.has(customName)) {
|
|
1006
|
-
return { handled: true, output:
|
|
1843
|
+
return { handled: true, output: pc3.yellow(`${customName} is already installed.`) };
|
|
1007
1844
|
}
|
|
1008
1845
|
installed.push({
|
|
1009
1846
|
name: customName,
|
|
@@ -1015,8 +1852,8 @@ function handleAkitCommand(action, args) {
|
|
|
1015
1852
|
return {
|
|
1016
1853
|
handled: true,
|
|
1017
1854
|
output: [
|
|
1018
|
-
|
|
1019
|
-
|
|
1855
|
+
pc3.green(`\u2713 Added ${pc3.bold(customName)}`) + pc3.dim(` (custom MCP: ${customCommand} ${customArgs.join(" ")})`),
|
|
1856
|
+
pc3.dim(" Restart aman-agent to load the new tool.")
|
|
1020
1857
|
].join("\n")
|
|
1021
1858
|
};
|
|
1022
1859
|
}
|
|
@@ -1032,13 +1869,13 @@ function handleAkitCommand(action, args) {
|
|
|
1032
1869
|
return {
|
|
1033
1870
|
handled: true,
|
|
1034
1871
|
output: [
|
|
1035
|
-
|
|
1036
|
-
`Type ${
|
|
1872
|
+
pc3.red(`Tool "${input}" not found.`),
|
|
1873
|
+
`Type ${pc3.cyan("/akit add")} to see available tools.`
|
|
1037
1874
|
].join("\n")
|
|
1038
1875
|
};
|
|
1039
1876
|
}
|
|
1040
1877
|
if (installedNames.has(tool.name)) {
|
|
1041
|
-
return { handled: true, output:
|
|
1878
|
+
return { handled: true, output: pc3.yellow(`${tool.name} is already installed.`) };
|
|
1042
1879
|
}
|
|
1043
1880
|
installed.push({
|
|
1044
1881
|
name: tool.name,
|
|
@@ -1053,255 +1890,253 @@ function handleAkitCommand(action, args) {
|
|
|
1053
1890
|
});
|
|
1054
1891
|
}
|
|
1055
1892
|
const lines2 = [
|
|
1056
|
-
|
|
1893
|
+
pc3.green(`\u2713 Added ${pc3.bold(tool.name)}`) + (tool.mcp ? pc3.dim(` (MCP: ${tool.mcp.package})`) : "")
|
|
1057
1894
|
];
|
|
1058
1895
|
if (tool.envHint) {
|
|
1059
|
-
lines2.push(
|
|
1896
|
+
lines2.push(pc3.yellow(` \u26A0 ${tool.envHint}`));
|
|
1060
1897
|
}
|
|
1061
1898
|
if (tool.mcp) {
|
|
1062
|
-
lines2.push(
|
|
1899
|
+
lines2.push(pc3.dim(" Restart aman-agent to load the new tool."));
|
|
1063
1900
|
}
|
|
1064
1901
|
return { handled: true, output: lines2.join("\n") };
|
|
1065
1902
|
}
|
|
1066
1903
|
if (action === "remove") {
|
|
1067
1904
|
if (args.length < 1) {
|
|
1068
|
-
return { handled: true, output:
|
|
1905
|
+
return { handled: true, output: pc3.yellow("Usage: /akit remove <tool>") };
|
|
1069
1906
|
}
|
|
1070
1907
|
const toolName = args[0].toLowerCase();
|
|
1071
1908
|
if (!installedNames.has(toolName)) {
|
|
1072
|
-
return { handled: true, output:
|
|
1909
|
+
return { handled: true, output: pc3.red(`${toolName} is not installed.`) };
|
|
1073
1910
|
}
|
|
1074
1911
|
const updated = installed.filter((t) => t.name !== toolName);
|
|
1075
1912
|
saveAkitInstalled(updated);
|
|
1076
1913
|
removeFromAmanAgentConfig(toolName);
|
|
1077
1914
|
return {
|
|
1078
1915
|
handled: true,
|
|
1079
|
-
output:
|
|
1916
|
+
output: pc3.green(`\u2713 Removed ${pc3.bold(toolName)}`) + pc3.dim(" (restart aman-agent to apply)")
|
|
1080
1917
|
};
|
|
1081
1918
|
}
|
|
1082
1919
|
if (action === "help") {
|
|
1083
1920
|
return {
|
|
1084
1921
|
handled: true,
|
|
1085
1922
|
output: [
|
|
1086
|
-
|
|
1923
|
+
pc3.bold("akit \u2014 Tool Management"),
|
|
1087
1924
|
"",
|
|
1088
|
-
` ${
|
|
1089
|
-
` ${
|
|
1090
|
-
` ${
|
|
1925
|
+
` ${pc3.cyan("/akit")} List installed & available tools`,
|
|
1926
|
+
` ${pc3.cyan("/akit add <tool>")} Install a tool`,
|
|
1927
|
+
` ${pc3.cyan("/akit remove <tool>")} Uninstall a tool`
|
|
1091
1928
|
].join("\n")
|
|
1092
1929
|
};
|
|
1093
1930
|
}
|
|
1094
1931
|
const available = AKIT_REGISTRY.filter((t) => !installedNames.has(t.name));
|
|
1095
|
-
const lines = [
|
|
1932
|
+
const lines = [pc3.bold("akit \u2014 AI Tool Manager"), ""];
|
|
1096
1933
|
if (installed.length > 0) {
|
|
1097
|
-
lines.push(` ${
|
|
1934
|
+
lines.push(` ${pc3.bold(`Installed (${installed.length})`)}`);
|
|
1098
1935
|
for (const tool of installed) {
|
|
1099
|
-
const mcp = tool.mcpConfigured ?
|
|
1100
|
-
lines.push(` ${
|
|
1936
|
+
const mcp = tool.mcpConfigured ? pc3.green("MCP") : pc3.dim("manual");
|
|
1937
|
+
lines.push(` ${pc3.green("\u25CF")} ${pc3.bold(tool.name.padEnd(16))} ${mcp} ${pc3.dim(tool.installedAt)}`);
|
|
1101
1938
|
}
|
|
1102
1939
|
lines.push("");
|
|
1103
1940
|
}
|
|
1104
1941
|
if (available.length > 0) {
|
|
1105
|
-
lines.push(` ${
|
|
1942
|
+
lines.push(` ${pc3.bold(`Available (${available.length})`)}`);
|
|
1106
1943
|
const byCategory = /* @__PURE__ */ new Map();
|
|
1107
1944
|
for (const tool of available) {
|
|
1108
1945
|
if (!byCategory.has(tool.category)) byCategory.set(tool.category, []);
|
|
1109
1946
|
byCategory.get(tool.category).push(tool);
|
|
1110
1947
|
}
|
|
1111
1948
|
for (const [category, tools] of byCategory) {
|
|
1112
|
-
lines.push(` ${
|
|
1949
|
+
lines.push(` ${pc3.dim(category)}`);
|
|
1113
1950
|
for (const tool of tools) {
|
|
1114
|
-
lines.push(` ${
|
|
1951
|
+
lines.push(` ${pc3.dim("\u25CB")} ${tool.name.padEnd(16)} ${pc3.dim(tool.description)}`);
|
|
1115
1952
|
}
|
|
1116
1953
|
}
|
|
1117
1954
|
lines.push("");
|
|
1118
1955
|
}
|
|
1119
|
-
lines.push(` ${
|
|
1120
|
-
lines.push(` ${
|
|
1956
|
+
lines.push(` ${pc3.cyan("/akit add <tool>")} Install a tool`);
|
|
1957
|
+
lines.push(` ${pc3.cyan("/akit remove <tool>")} Uninstall a tool`);
|
|
1121
1958
|
return { handled: true, output: lines.join("\n") };
|
|
1122
1959
|
}
|
|
1123
1960
|
async function handleSkillsCommand(action, args, ctx) {
|
|
1124
|
-
const home2 =
|
|
1961
|
+
const home2 = os9.homedir();
|
|
1125
1962
|
if (!action) {
|
|
1126
|
-
const content = readEcosystemFile(
|
|
1963
|
+
const content = readEcosystemFile(path9.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
1127
1964
|
return { handled: true, output: content };
|
|
1128
1965
|
}
|
|
1129
1966
|
if (action === "install") {
|
|
1130
1967
|
if (args.length < 1) {
|
|
1131
|
-
return { handled: true, output:
|
|
1968
|
+
return { handled: true, output: pc3.yellow("Usage: /skills install <name>") };
|
|
1132
1969
|
}
|
|
1133
1970
|
const output = await mcpWrite(ctx, "skills", "skill_install", { name: args.join(" ") });
|
|
1134
1971
|
return { handled: true, output };
|
|
1135
1972
|
}
|
|
1136
1973
|
if (action === "uninstall") {
|
|
1137
1974
|
if (args.length < 1) {
|
|
1138
|
-
return { handled: true, output:
|
|
1975
|
+
return { handled: true, output: pc3.yellow("Usage: /skills uninstall <name>") };
|
|
1139
1976
|
}
|
|
1140
1977
|
const output = await mcpWrite(ctx, "skills", "skill_uninstall", { name: args.join(" ") });
|
|
1141
1978
|
return { handled: true, output };
|
|
1142
1979
|
}
|
|
1143
|
-
return { handled: true, output:
|
|
1980
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /skills ${action}. Use /skills [install|uninstall].`) };
|
|
1144
1981
|
}
|
|
1145
1982
|
async function handleEvalCommand(action, args, ctx) {
|
|
1146
|
-
const home2 =
|
|
1983
|
+
const home2 = os9.homedir();
|
|
1147
1984
|
if (!action) {
|
|
1148
|
-
const content = readEcosystemFile(
|
|
1985
|
+
const content = readEcosystemFile(path9.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
1149
1986
|
return { handled: true, output: content };
|
|
1150
1987
|
}
|
|
1151
1988
|
if (action === "milestone") {
|
|
1152
1989
|
if (args.length < 1) {
|
|
1153
|
-
return { handled: true, output:
|
|
1990
|
+
return { handled: true, output: pc3.yellow("Usage: /eval milestone <text...>") };
|
|
1154
1991
|
}
|
|
1155
1992
|
const text2 = args.join(" ");
|
|
1156
1993
|
const output = await mcpWrite(ctx, "eval", "eval_milestone", { text: text2 });
|
|
1157
1994
|
return { handled: true, output };
|
|
1158
1995
|
}
|
|
1159
|
-
return { handled: true, output:
|
|
1996
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /eval ${action}. Use /eval or /eval milestone <text>.`) };
|
|
1160
1997
|
}
|
|
1161
1998
|
async function handleMemoryCommand(action, args, ctx) {
|
|
1162
1999
|
if (!action) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
output:
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
return { handled: true, output: pc.red(result) };
|
|
2000
|
+
try {
|
|
2001
|
+
const result = await memoryContext("recent context");
|
|
2002
|
+
if (result.memoriesUsed === 0) {
|
|
2003
|
+
return { handled: true, output: pc3.dim("No memories yet. Start chatting and I'll remember what matters.") };
|
|
2004
|
+
}
|
|
2005
|
+
return { handled: true, output: result.text };
|
|
2006
|
+
} catch (err) {
|
|
2007
|
+
return { handled: true, output: pc3.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
1172
2008
|
}
|
|
1173
|
-
return { handled: true, output: result };
|
|
1174
2009
|
}
|
|
1175
2010
|
if (action && !["search", "clear", "timeline"].includes(action)) {
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return { handled: true, output:
|
|
2011
|
+
try {
|
|
2012
|
+
const topic = [action, ...args].join(" ");
|
|
2013
|
+
const result = await memoryContext(topic);
|
|
2014
|
+
if (result.memoriesUsed === 0) {
|
|
2015
|
+
return { handled: true, output: pc3.dim(`No memories found for: "${topic}".`) };
|
|
2016
|
+
}
|
|
2017
|
+
return { handled: true, output: result.text };
|
|
2018
|
+
} catch (err) {
|
|
2019
|
+
return { handled: true, output: pc3.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
1183
2020
|
}
|
|
1184
|
-
return { handled: true, output: result };
|
|
1185
2021
|
}
|
|
1186
2022
|
if (action === "search") {
|
|
1187
2023
|
if (args.length < 1) {
|
|
1188
|
-
return { handled: true, output:
|
|
2024
|
+
return { handled: true, output: pc3.yellow("Usage: /memory search <query...>") };
|
|
1189
2025
|
}
|
|
1190
2026
|
const query = args.join(" ");
|
|
1191
|
-
|
|
1192
|
-
|
|
2027
|
+
try {
|
|
2028
|
+
const result = await memoryRecall(query);
|
|
2029
|
+
return { handled: true, output: result.total === 0 ? pc3.dim("No memories found.") : result.text };
|
|
2030
|
+
} catch (err) {
|
|
2031
|
+
return { handled: true, output: pc3.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
2032
|
+
}
|
|
1193
2033
|
}
|
|
1194
2034
|
if (action === "clear") {
|
|
1195
2035
|
if (args.length < 1) {
|
|
1196
|
-
return { handled: true, output:
|
|
2036
|
+
return { handled: true, output: pc3.yellow("Usage: /memory clear <category>") };
|
|
1197
2037
|
}
|
|
1198
2038
|
const output = await mcpWrite(ctx, "memory", "memory_forget", { category: args[0] });
|
|
1199
2039
|
return { handled: true, output };
|
|
1200
2040
|
}
|
|
1201
2041
|
if (action === "timeline") {
|
|
1202
|
-
if (!ctx.mcpManager) {
|
|
1203
|
-
return { handled: true, output: pc.red("Memory not available: MCP not connected.") };
|
|
1204
|
-
}
|
|
1205
2042
|
try {
|
|
1206
|
-
const result = await
|
|
1207
|
-
if (result.
|
|
1208
|
-
return { handled: true, output:
|
|
2043
|
+
const result = await memoryRecall("*", { limit: 500, compact: false });
|
|
2044
|
+
if (result.total === 0) {
|
|
2045
|
+
return { handled: true, output: pc3.dim("No memories yet. Start chatting and I'll remember what matters.") };
|
|
1209
2046
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
2047
|
+
const memories = result.memories;
|
|
2048
|
+
if (memories.length > 0) {
|
|
2049
|
+
const byDate = /* @__PURE__ */ new Map();
|
|
2050
|
+
for (const mem of memories) {
|
|
2051
|
+
const createdAt = mem.created_at;
|
|
2052
|
+
const date = createdAt ? new Date(createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric" }) : "Unknown";
|
|
2053
|
+
byDate.set(date, (byDate.get(date) || 0) + 1);
|
|
2054
|
+
}
|
|
2055
|
+
const maxCount = Math.max(...byDate.values());
|
|
2056
|
+
const barWidth = 10;
|
|
2057
|
+
const lines = [pc3.bold("Memory Timeline:"), ""];
|
|
2058
|
+
for (const [date, count] of byDate) {
|
|
2059
|
+
const filled = Math.round(count / maxCount * barWidth);
|
|
2060
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(barWidth - filled);
|
|
2061
|
+
lines.push(` ${date.padEnd(8)} ${bar} ${count} memories`);
|
|
2062
|
+
}
|
|
2063
|
+
const tags = /* @__PURE__ */ new Map();
|
|
2064
|
+
for (const mem of memories) {
|
|
2065
|
+
const memTags = mem.tags;
|
|
2066
|
+
if (Array.isArray(memTags)) {
|
|
2067
|
+
for (const tag of memTags) {
|
|
2068
|
+
tags.set(tag, (tags.get(tag) || 0) + 1);
|
|
1232
2069
|
}
|
|
1233
2070
|
}
|
|
1234
|
-
lines.push("");
|
|
1235
|
-
lines.push(` Total: ${memories.length} memories`);
|
|
1236
|
-
if (tags.size > 0) {
|
|
1237
|
-
const topTags = [...tags.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag, count]) => `#${tag} (${count})`).join(", ");
|
|
1238
|
-
lines.push(` Top tags: ${topTags}`);
|
|
1239
|
-
}
|
|
1240
|
-
return { handled: true, output: lines.join("\n") };
|
|
1241
2071
|
}
|
|
1242
|
-
|
|
2072
|
+
lines.push("");
|
|
2073
|
+
lines.push(` Total: ${result.total} memories`);
|
|
2074
|
+
if (tags.size > 0) {
|
|
2075
|
+
const topTags = [...tags.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([tag, count]) => `#${tag} (${count})`).join(", ");
|
|
2076
|
+
lines.push(` Top tags: ${topTags}`);
|
|
2077
|
+
}
|
|
2078
|
+
return { handled: true, output: lines.join("\n") };
|
|
1243
2079
|
}
|
|
1244
|
-
|
|
1245
|
-
return { handled: true, output: `Total memories: ~${lineCount} entries.` };
|
|
2080
|
+
return { handled: true, output: `Total memories: ${result.total} entries.` };
|
|
1246
2081
|
} catch {
|
|
1247
|
-
return { handled: true, output:
|
|
2082
|
+
return { handled: true, output: pc3.red("Failed to retrieve memory timeline.") };
|
|
1248
2083
|
}
|
|
1249
2084
|
}
|
|
1250
|
-
return { handled: true, output:
|
|
2085
|
+
return { handled: true, output: pc3.yellow(`Unknown action: /memory ${action}. Use /memory [search|clear|timeline].`) };
|
|
1251
2086
|
}
|
|
1252
2087
|
function handleStatusCommand(ctx) {
|
|
1253
2088
|
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
1254
2089
|
const amemConnected = mcpToolCount > 0;
|
|
1255
2090
|
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
1256
|
-
const lines = [
|
|
2091
|
+
const lines = [pc3.bold("Aman Ecosystem Dashboard"), ""];
|
|
1257
2092
|
for (const layer of status.layers) {
|
|
1258
|
-
const icon = layer.exists ?
|
|
1259
|
-
const name =
|
|
1260
|
-
const summary = layer.exists ? layer.summary :
|
|
2093
|
+
const icon = layer.exists ? pc3.green("\u25CF") : pc3.dim("\u25CB");
|
|
2094
|
+
const name = pc3.bold(layer.name.padEnd(12));
|
|
2095
|
+
const summary = layer.exists ? layer.summary : pc3.dim("not configured");
|
|
1261
2096
|
lines.push(` ${icon} ${name} ${summary}`);
|
|
1262
2097
|
}
|
|
1263
2098
|
lines.push("");
|
|
1264
|
-
lines.push(` ${status.mcpConnected ?
|
|
1265
|
-
lines.push(` ${status.amemConnected ?
|
|
2099
|
+
lines.push(` ${status.mcpConnected ? pc3.green("\u25CF") : pc3.dim("\u25CB")} ${pc3.bold("MCP".padEnd(12))} ${status.mcpConnected ? `${status.mcpToolCount} tools available` : pc3.dim("not connected")}`);
|
|
2100
|
+
lines.push(` ${status.amemConnected ? pc3.green("\u25CF") : pc3.dim("\u25CB")} ${pc3.bold("Memory".padEnd(12))} ${status.amemConnected ? "connected" : pc3.dim("not connected")}`);
|
|
1266
2101
|
return { handled: true, output: lines.join("\n") };
|
|
1267
2102
|
}
|
|
1268
2103
|
function handleDoctorCommand(ctx) {
|
|
1269
2104
|
const mcpToolCount = ctx.mcpManager ? ctx.mcpManager.getTools().length : 0;
|
|
1270
2105
|
const amemConnected = mcpToolCount > 0;
|
|
1271
2106
|
const status = getEcosystemStatus(mcpToolCount, amemConnected);
|
|
1272
|
-
const lines = [
|
|
2107
|
+
const lines = [pc3.bold("Aman Health Check"), ""];
|
|
1273
2108
|
let healthy = 0;
|
|
1274
2109
|
let fixes = 0;
|
|
1275
2110
|
let suggestions = 0;
|
|
1276
2111
|
for (const layer of status.layers) {
|
|
1277
2112
|
if (layer.exists) {
|
|
1278
|
-
lines.push(` ${
|
|
2113
|
+
lines.push(` ${pc3.green("\u2713")} ${layer.name.padEnd(12)} ${pc3.green(layer.summary)}`);
|
|
1279
2114
|
healthy++;
|
|
1280
2115
|
} else {
|
|
1281
2116
|
const isRequired = ["identity", "rules"].includes(layer.name.toLowerCase());
|
|
1282
2117
|
if (isRequired) {
|
|
1283
|
-
lines.push(` ${
|
|
1284
|
-
lines.push(` ${
|
|
2118
|
+
lines.push(` ${pc3.red("\u2717")} ${layer.name.padEnd(12)} ${pc3.red("missing")}`);
|
|
2119
|
+
lines.push(` ${pc3.dim("\u2192 Fix: aman-agent init")}`);
|
|
1285
2120
|
fixes++;
|
|
1286
2121
|
} else {
|
|
1287
|
-
lines.push(` ${
|
|
2122
|
+
lines.push(` ${pc3.yellow("\u26A0")} ${layer.name.padEnd(12)} ${pc3.yellow("empty")}`);
|
|
1288
2123
|
const cmd = layer.name.toLowerCase() === "workflows" ? "/workflows add <name>" : layer.name.toLowerCase() === "tools" ? "/tools add <name> <type> <desc>" : layer.name.toLowerCase() === "skills" ? "/skills install <name>" : "";
|
|
1289
|
-
if (cmd) lines.push(` ${
|
|
2124
|
+
if (cmd) lines.push(` ${pc3.dim(`\u2192 Add with ${cmd}`)}`);
|
|
1290
2125
|
suggestions++;
|
|
1291
2126
|
}
|
|
1292
2127
|
}
|
|
1293
2128
|
}
|
|
1294
2129
|
lines.push("");
|
|
1295
|
-
lines.push(` ${status.mcpConnected ?
|
|
2130
|
+
lines.push(` ${status.mcpConnected ? pc3.green("\u2713") : pc3.red("\u2717")} ${"MCP".padEnd(12)} ${status.mcpConnected ? pc3.green(`${status.mcpToolCount} tools`) : pc3.red("not connected")}`);
|
|
1296
2131
|
if (!status.mcpConnected) {
|
|
1297
|
-
lines.push(` ${
|
|
2132
|
+
lines.push(` ${pc3.dim("\u2192 Fix: ensure npx is available and network is connected")}`);
|
|
1298
2133
|
fixes++;
|
|
1299
2134
|
} else {
|
|
1300
2135
|
healthy++;
|
|
1301
2136
|
}
|
|
1302
|
-
lines.push(` ${status.amemConnected ?
|
|
2137
|
+
lines.push(` ${status.amemConnected ? pc3.green("\u2713") : pc3.red("\u2717")} ${"Memory".padEnd(12)} ${status.amemConnected ? pc3.green("connected") : pc3.red("not connected")}`);
|
|
1303
2138
|
if (!status.amemConnected) {
|
|
1304
|
-
lines.push(` ${
|
|
2139
|
+
lines.push(` ${pc3.dim("\u2192 Fix: npx @aman_asmuei/amem")}`);
|
|
1305
2140
|
fixes++;
|
|
1306
2141
|
} else {
|
|
1307
2142
|
healthy++;
|
|
@@ -1315,26 +2150,26 @@ function handleHelp() {
|
|
|
1315
2150
|
return {
|
|
1316
2151
|
handled: true,
|
|
1317
2152
|
output: [
|
|
1318
|
-
|
|
1319
|
-
` ${
|
|
1320
|
-
` ${
|
|
1321
|
-
` ${
|
|
1322
|
-
` ${
|
|
1323
|
-
` ${
|
|
1324
|
-
` ${
|
|
1325
|
-
` ${
|
|
1326
|
-
` ${
|
|
1327
|
-
` ${
|
|
1328
|
-
` ${
|
|
1329
|
-
` ${
|
|
1330
|
-
` ${
|
|
1331
|
-
` ${
|
|
1332
|
-
` ${
|
|
1333
|
-
` ${
|
|
1334
|
-
` ${
|
|
1335
|
-
` ${
|
|
1336
|
-
` ${
|
|
1337
|
-
` ${
|
|
2153
|
+
pc3.bold("Commands:"),
|
|
2154
|
+
` ${pc3.cyan("/help")} Show this help`,
|
|
2155
|
+
` ${pc3.cyan("/identity")} View identity [update <section>]`,
|
|
2156
|
+
` ${pc3.cyan("/rules")} View rules [add|remove|toggle ...]`,
|
|
2157
|
+
` ${pc3.cyan("/workflows")} View workflows [add|remove ...]`,
|
|
2158
|
+
` ${pc3.cyan("/akit")} Manage tools [add|remove <tool>]`,
|
|
2159
|
+
` ${pc3.cyan("/skills")} View skills [install|uninstall ...]`,
|
|
2160
|
+
` ${pc3.cyan("/eval")} View evaluation [milestone ...]`,
|
|
2161
|
+
` ${pc3.cyan("/memory")} View recent memories [search|clear|timeline]`,
|
|
2162
|
+
` ${pc3.cyan("/status")} Ecosystem dashboard`,
|
|
2163
|
+
` ${pc3.cyan("/doctor")} Health check all layers`,
|
|
2164
|
+
` ${pc3.cyan("/decisions")} View decision log [<project>]`,
|
|
2165
|
+
` ${pc3.cyan("/export")} Export conversation to markdown`,
|
|
2166
|
+
` ${pc3.cyan("/debug")} Show debug log`,
|
|
2167
|
+
` ${pc3.cyan("/save")} Save conversation to memory`,
|
|
2168
|
+
` ${pc3.cyan("/model")} Show current LLM model`,
|
|
2169
|
+
` ${pc3.cyan("/update")} Check for updates`,
|
|
2170
|
+
` ${pc3.cyan("/reconfig")} Reset LLM config`,
|
|
2171
|
+
` ${pc3.cyan("/clear")} Clear conversation history`,
|
|
2172
|
+
` ${pc3.cyan("/quit")} Exit`
|
|
1338
2173
|
].join("\n")
|
|
1339
2174
|
};
|
|
1340
2175
|
}
|
|
@@ -1342,18 +2177,18 @@ function handleSave() {
|
|
|
1342
2177
|
return { handled: true, saveConversation: true };
|
|
1343
2178
|
}
|
|
1344
2179
|
function handleReconfig() {
|
|
1345
|
-
const configDir =
|
|
1346
|
-
const configPath =
|
|
1347
|
-
if (
|
|
1348
|
-
|
|
2180
|
+
const configDir = path9.join(os9.homedir(), ".aman-agent");
|
|
2181
|
+
const configPath = path9.join(configDir, "config.json");
|
|
2182
|
+
if (fs9.existsSync(configPath)) {
|
|
2183
|
+
fs9.unlinkSync(configPath);
|
|
1349
2184
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
2185
|
+
fs9.mkdirSync(configDir, { recursive: true });
|
|
2186
|
+
fs9.writeFileSync(path9.join(configDir, ".reconfig"), "", "utf-8");
|
|
1352
2187
|
return {
|
|
1353
2188
|
handled: true,
|
|
1354
2189
|
quit: true,
|
|
1355
2190
|
output: [
|
|
1356
|
-
|
|
2191
|
+
pc3.green("Config reset."),
|
|
1357
2192
|
"Next run will prompt you to choose your LLM provider."
|
|
1358
2193
|
].join("\n")
|
|
1359
2194
|
};
|
|
@@ -1361,20 +2196,20 @@ function handleReconfig() {
|
|
|
1361
2196
|
function handleUpdate() {
|
|
1362
2197
|
try {
|
|
1363
2198
|
const current = execFileSync("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
1364
|
-
const local = JSON.parse(
|
|
2199
|
+
const local = JSON.parse(fs9.readFileSync(path9.join(__dirname, "..", "package.json"), "utf-8")).version;
|
|
1365
2200
|
if (current === local) {
|
|
1366
|
-
return { handled: true, output: `${
|
|
2201
|
+
return { handled: true, output: `${pc3.green("Up to date")} \u2014 v${local}` };
|
|
1367
2202
|
}
|
|
1368
2203
|
return {
|
|
1369
2204
|
handled: true,
|
|
1370
2205
|
output: [
|
|
1371
|
-
`${
|
|
2206
|
+
`${pc3.yellow("Update available:")} v${local} \u2192 v${current}`,
|
|
1372
2207
|
"",
|
|
1373
2208
|
`Run this in your terminal:`,
|
|
1374
|
-
` ${
|
|
2209
|
+
` ${pc3.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
1375
2210
|
"",
|
|
1376
2211
|
`Or use npx (always latest):`,
|
|
1377
|
-
` ${
|
|
2212
|
+
` ${pc3.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
1378
2213
|
].join("\n")
|
|
1379
2214
|
};
|
|
1380
2215
|
} catch {
|
|
@@ -1382,42 +2217,422 @@ function handleUpdate() {
|
|
|
1382
2217
|
handled: true,
|
|
1383
2218
|
output: [
|
|
1384
2219
|
`To update, run in your terminal:`,
|
|
1385
|
-
` ${
|
|
2220
|
+
` ${pc3.bold("npm install -g @aman_asmuei/aman-agent@latest")}`,
|
|
1386
2221
|
"",
|
|
1387
2222
|
`Or use npx (always latest):`,
|
|
1388
|
-
` ${
|
|
2223
|
+
` ${pc3.bold("npx @aman_asmuei/aman-agent@latest")}`
|
|
1389
2224
|
].join("\n")
|
|
1390
2225
|
};
|
|
1391
2226
|
}
|
|
1392
2227
|
}
|
|
1393
|
-
async function handleDecisionsCommand(action, _args,
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
2228
|
+
async function handleDecisionsCommand(action, _args, _ctx) {
|
|
2229
|
+
try {
|
|
2230
|
+
const result = await memoryRecall("decision", { type: "decision", limit: 20 });
|
|
2231
|
+
if (result.total === 0) {
|
|
2232
|
+
return { handled: true, output: pc3.dim("No decisions recorded yet.") };
|
|
2233
|
+
}
|
|
2234
|
+
return { handled: true, output: pc3.bold("Decision Log:\n") + result.text };
|
|
2235
|
+
} catch (err) {
|
|
2236
|
+
return { handled: true, output: pc3.red(`Memory error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
function handleExportCommand() {
|
|
2240
|
+
return { handled: true, exportConversation: true };
|
|
2241
|
+
}
|
|
2242
|
+
function handleDebugCommand() {
|
|
2243
|
+
const logPath = path9.join(os9.homedir(), ".aman-agent", "debug.log");
|
|
2244
|
+
if (!fs9.existsSync(logPath)) {
|
|
2245
|
+
return { handled: true, output: pc3.dim("No debug log found.") };
|
|
2246
|
+
}
|
|
2247
|
+
const content = fs9.readFileSync(logPath, "utf-8");
|
|
2248
|
+
const lines = content.trim().split("\n");
|
|
2249
|
+
const last20 = lines.slice(-20).join("\n");
|
|
2250
|
+
return { handled: true, output: pc3.bold("Debug Log (last 20 entries):\n") + pc3.dim(last20) };
|
|
2251
|
+
}
|
|
2252
|
+
async function handleTeamCommand(action, args, ctx) {
|
|
2253
|
+
if (!action || action === "list") {
|
|
2254
|
+
const teams = listTeams();
|
|
2255
|
+
if (teams.length === 0) {
|
|
2256
|
+
return {
|
|
2257
|
+
handled: true,
|
|
2258
|
+
output: pc3.dim("No teams yet. Create one:") + "\n /team create <name> Create from built-in template\n /team create Show available templates"
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
const lines = teams.map((t) => {
|
|
2262
|
+
const members = t.members.map((m) => m.profile).join(", ");
|
|
2263
|
+
return ` ${pc3.bold(t.name)} (${t.workflow}) \u2014 ${members}`;
|
|
2264
|
+
});
|
|
2265
|
+
return { handled: true, output: "Teams:\n" + lines.join("\n") };
|
|
2266
|
+
}
|
|
2267
|
+
switch (action) {
|
|
2268
|
+
case "create": {
|
|
2269
|
+
const name = args[0];
|
|
2270
|
+
if (!name) {
|
|
2271
|
+
const lines = BUILT_IN_TEAMS.map((t) => {
|
|
2272
|
+
const members2 = t.members.map((m) => m.profile).join(" \u2192 ");
|
|
2273
|
+
return ` ${pc3.bold(t.name)} (${t.workflow}) \u2014 ${members2}
|
|
2274
|
+
${pc3.dim(t.goal)}`;
|
|
2275
|
+
});
|
|
2276
|
+
return {
|
|
2277
|
+
handled: true,
|
|
2278
|
+
output: "Built-in teams:\n" + lines.join("\n\n") + "\n\nUsage:\n /team create content-team Install built-in\n /team create <name> <mode> <profile1:role>,<profile2:role> Custom"
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
const builtIn = BUILT_IN_TEAMS.find((t) => t.name === name);
|
|
2282
|
+
if (builtIn) {
|
|
2283
|
+
createTeam(builtIn);
|
|
2284
|
+
return { handled: true, output: pc3.green(`Team installed: ${builtIn.name}`) + "\n\n" + formatTeam(builtIn) };
|
|
2285
|
+
}
|
|
2286
|
+
const mode = args[1];
|
|
2287
|
+
const membersStr = args[2];
|
|
2288
|
+
if (!mode || !membersStr) {
|
|
2289
|
+
return { handled: true, output: pc3.yellow("Usage: /team create <name> <pipeline|parallel|coordinator> <profile1:role>,<profile2:role>") };
|
|
2290
|
+
}
|
|
2291
|
+
if (!["pipeline", "parallel", "coordinator"].includes(mode)) {
|
|
2292
|
+
return { handled: true, output: pc3.yellow("Mode must be: pipeline, parallel, or coordinator") };
|
|
2293
|
+
}
|
|
2294
|
+
const members = membersStr.split(",").map((m) => {
|
|
2295
|
+
const [profile, ...roleParts] = m.trim().split(":");
|
|
2296
|
+
return { profile: profile.trim(), role: roleParts.join(":").trim() || profile.trim() };
|
|
2297
|
+
});
|
|
2298
|
+
const team = {
|
|
2299
|
+
name,
|
|
2300
|
+
goal: `Team: ${name}`,
|
|
2301
|
+
coordinator: "default",
|
|
2302
|
+
members,
|
|
2303
|
+
workflow: mode
|
|
2304
|
+
};
|
|
2305
|
+
createTeam(team);
|
|
2306
|
+
return { handled: true, output: pc3.green(`Team created!`) + "\n\n" + formatTeam(team) };
|
|
2307
|
+
}
|
|
2308
|
+
case "run": {
|
|
2309
|
+
const teamName = args[0];
|
|
2310
|
+
const task = args.slice(1).join(" ");
|
|
2311
|
+
if (!teamName || !task) {
|
|
2312
|
+
return { handled: true, output: pc3.yellow("Usage: /team run <team-name> <task description>") };
|
|
2313
|
+
}
|
|
2314
|
+
const team = loadTeam(teamName);
|
|
2315
|
+
if (!team) return { handled: true, output: pc3.red(`Team not found: ${teamName}`) };
|
|
2316
|
+
if (!ctx.llmClient || !ctx.mcpManager) {
|
|
2317
|
+
return { handled: true, output: pc3.red("Team execution requires LLM client and MCP.") };
|
|
2318
|
+
}
|
|
2319
|
+
const result = await runTeam(team, task, ctx.llmClient, ctx.mcpManager, ctx.tools);
|
|
2320
|
+
return { handled: true, output: formatTeamResult(result) };
|
|
2321
|
+
}
|
|
2322
|
+
case "show": {
|
|
2323
|
+
const name = args[0];
|
|
2324
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /team show <name>") };
|
|
2325
|
+
const team = loadTeam(name);
|
|
2326
|
+
if (!team) return { handled: true, output: pc3.red(`Team not found: ${name}`) };
|
|
2327
|
+
return { handled: true, output: formatTeam(team) };
|
|
2328
|
+
}
|
|
2329
|
+
case "delete": {
|
|
2330
|
+
const name = args[0];
|
|
2331
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /team delete <name>") };
|
|
2332
|
+
if (!deleteTeam(name)) return { handled: true, output: pc3.red(`Team not found: ${name}`) };
|
|
2333
|
+
return { handled: true, output: pc3.dim(`Team deleted: ${name}`) };
|
|
2334
|
+
}
|
|
2335
|
+
case "help":
|
|
2336
|
+
return { handled: true, output: `Team commands:
|
|
2337
|
+
/team List all teams
|
|
2338
|
+
/team create Show built-in templates
|
|
2339
|
+
/team create <name> Install built-in team
|
|
2340
|
+
/team create <n> <mode> <m> Custom team (mode: pipeline|parallel|coordinator)
|
|
2341
|
+
/team run <name> <task> Run a task with a team
|
|
2342
|
+
/team show <name> Show team details
|
|
2343
|
+
/team delete <name> Delete a team
|
|
2344
|
+
|
|
2345
|
+
Modes:
|
|
2346
|
+
pipeline Sequential: agent1 \u2192 agent2 \u2192 agent3
|
|
2347
|
+
parallel All agents work concurrently, coordinator merges
|
|
2348
|
+
coordinator Coordinator LLM decides how to split the task
|
|
2349
|
+
|
|
2350
|
+
Examples:
|
|
2351
|
+
/team create content-team
|
|
2352
|
+
/team run content-team Write a blog post about AI companions
|
|
2353
|
+
/team create review-squad pipeline coder:implement,researcher:review
|
|
2354
|
+
/team run review-squad Build a rate limiter in TypeScript` };
|
|
2355
|
+
default:
|
|
2356
|
+
return { handled: true, output: pc3.yellow(`Unknown team action: ${action}. Try /team help`) };
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
async function handleDelegateCommand(action, args, ctx) {
|
|
2360
|
+
if (!action) {
|
|
2361
|
+
return { handled: true, output: `Delegate commands:
|
|
2362
|
+
/delegate <profile> <task> Delegate a task to a profile
|
|
2363
|
+
/delegate pipeline <p1> <p2> ... Run a sequential pipeline
|
|
2364
|
+
/delegate help Show help
|
|
2365
|
+
|
|
2366
|
+
Examples:
|
|
2367
|
+
/delegate writer Write a blog post about AI companions
|
|
2368
|
+
/delegate coder Review this code for security issues
|
|
2369
|
+
/delegate pipeline writer,researcher Write and fact-check an article about quantum computing` };
|
|
2370
|
+
}
|
|
2371
|
+
if (action === "help") {
|
|
2372
|
+
return { handled: true, output: `Delegate a task to a sub-agent with a specific profile.
|
|
2373
|
+
|
|
2374
|
+
The sub-agent runs with its own identity, rules, and skills but shares
|
|
2375
|
+
your memory and tools. Results come back to you.
|
|
2376
|
+
|
|
2377
|
+
Usage:
|
|
2378
|
+
/delegate <profile> <task>
|
|
2379
|
+
/delegate pipeline <profile1>,<profile2> <task>
|
|
2380
|
+
|
|
2381
|
+
The pipeline mode passes each agent's output to the next:
|
|
2382
|
+
writer drafts \u2192 researcher reviews \u2192 writer polishes` };
|
|
2383
|
+
}
|
|
2384
|
+
if (!ctx.llmClient || !ctx.mcpManager) {
|
|
2385
|
+
return { handled: true, output: pc3.red("Delegation requires LLM client and MCP. Not available.") };
|
|
2386
|
+
}
|
|
2387
|
+
if (action === "pipeline") {
|
|
2388
|
+
const profileList = args[0];
|
|
2389
|
+
const task2 = args.slice(1).join(" ");
|
|
2390
|
+
if (!profileList || !task2) {
|
|
2391
|
+
return { handled: true, output: pc3.yellow("Usage: /delegate pipeline <profile1>,<profile2> <task>") };
|
|
2392
|
+
}
|
|
2393
|
+
const profiles = profileList.split(",").map((p3) => p3.trim());
|
|
2394
|
+
const steps = profiles.map((profile2, i) => {
|
|
2395
|
+
if (i === 0) {
|
|
2396
|
+
return { profile: profile2, taskTemplate: task2 };
|
|
2397
|
+
}
|
|
2398
|
+
return { profile: profile2, taskTemplate: `Review and improve the following:
|
|
2399
|
+
|
|
2400
|
+
{{input}}` };
|
|
2401
|
+
});
|
|
2402
|
+
process.stdout.write(pc3.dim(`
|
|
2403
|
+
Pipeline: ${profiles.join(" \u2192 ")}
|
|
2404
|
+
`));
|
|
2405
|
+
const results = await delegatePipeline(steps, task2, ctx.llmClient, ctx.mcpManager, { tools: ctx.tools });
|
|
2406
|
+
const output = [];
|
|
2407
|
+
for (const r of results) {
|
|
2408
|
+
if (r.success) {
|
|
2409
|
+
output.push(`
|
|
2410
|
+
${pc3.bold(`[${r.profile}]`)} ${pc3.green("\u2713")} (${r.turns} tool turns)`);
|
|
2411
|
+
output.push(r.response.slice(0, 2e3));
|
|
2412
|
+
if (r.toolsUsed.length > 0) output.push(pc3.dim(` Tools: ${r.toolsUsed.join(", ")}`));
|
|
2413
|
+
} else {
|
|
2414
|
+
output.push(`
|
|
2415
|
+
${pc3.bold(`[${r.profile}]`)} ${pc3.red("\u2717")} ${r.error}`);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return { handled: true, output: output.join("\n") };
|
|
2419
|
+
}
|
|
2420
|
+
const profile = action;
|
|
2421
|
+
const task = args.join(" ");
|
|
2422
|
+
if (!task) {
|
|
2423
|
+
return { handled: true, output: pc3.yellow(`Usage: /delegate ${profile} <task description>`) };
|
|
2424
|
+
}
|
|
2425
|
+
process.stdout.write(pc3.dim(`
|
|
2426
|
+
[delegating to ${profile}...]
|
|
2427
|
+
|
|
2428
|
+
`));
|
|
2429
|
+
const result = await delegateTask(task, profile, ctx.llmClient, ctx.mcpManager, { tools: ctx.tools });
|
|
2430
|
+
if (!result.success) {
|
|
2431
|
+
return { handled: true, output: pc3.red(`Delegation failed: ${result.error}`) };
|
|
2432
|
+
}
|
|
2433
|
+
const meta = [];
|
|
2434
|
+
if (result.toolsUsed.length > 0) meta.push(`Tools: ${result.toolsUsed.join(", ")}`);
|
|
2435
|
+
if (result.turns > 0) meta.push(`${result.turns} tool turns`);
|
|
2436
|
+
return {
|
|
2437
|
+
handled: true,
|
|
2438
|
+
output: `
|
|
2439
|
+
${pc3.bold(`[${profile}]`)} ${pc3.green("\u2713")}${meta.length > 0 ? " " + pc3.dim(`(${meta.join(", ")})`) : ""}
|
|
2440
|
+
|
|
2441
|
+
${result.response}`
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function handleProfileCommand(action, args) {
|
|
2445
|
+
const profilesDir = path9.join(os9.homedir(), ".acore", "profiles");
|
|
2446
|
+
if (!action || action === "list") {
|
|
2447
|
+
const profiles = listProfiles();
|
|
2448
|
+
if (profiles.length === 0) {
|
|
2449
|
+
return { handled: true, output: pc3.dim("No profiles yet. Create one with: /profile create <name>") };
|
|
2450
|
+
}
|
|
2451
|
+
const lines = profiles.map(
|
|
2452
|
+
(p3) => ` ${pc3.bold(p3.name)} \u2014 ${p3.aiName} (${pc3.dim(p3.personality)})`
|
|
2453
|
+
);
|
|
2454
|
+
return { handled: true, output: "Profiles:\n" + lines.join("\n") + "\n\n" + pc3.dim("Switch with: aman-agent --profile <name>") };
|
|
2455
|
+
}
|
|
2456
|
+
switch (action) {
|
|
2457
|
+
case "create": {
|
|
2458
|
+
const name = args[0];
|
|
2459
|
+
if (!name) {
|
|
2460
|
+
const lines = BUILT_IN_PROFILES.map(
|
|
2461
|
+
(t) => ` ${pc3.bold(t.name)} \u2014 ${t.label}: ${pc3.dim(t.description)}`
|
|
2462
|
+
);
|
|
2463
|
+
return {
|
|
2464
|
+
handled: true,
|
|
2465
|
+
output: "Built-in profiles:\n" + lines.join("\n") + "\n\nUsage:\n /profile create coder Install built-in template\n /profile create <custom> Create blank profile"
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
2469
|
+
const profileDir = path9.join(profilesDir, slug);
|
|
2470
|
+
if (fs9.existsSync(profileDir)) {
|
|
2471
|
+
return { handled: true, output: pc3.yellow(`Profile already exists: ${slug}`) };
|
|
2472
|
+
}
|
|
2473
|
+
const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
|
|
2474
|
+
if (builtIn) {
|
|
2475
|
+
const err = installProfileTemplate(slug);
|
|
2476
|
+
if (err) return { handled: true, output: pc3.red(err) };
|
|
2477
|
+
return {
|
|
2478
|
+
handled: true,
|
|
2479
|
+
output: pc3.green(`Profile installed: ${builtIn.label}`) + `
|
|
2480
|
+
AI name: ${builtIn.core.match(/^# (.+)/m)?.[1] || slug}
|
|
2481
|
+
${pc3.dim(builtIn.description)}
|
|
2482
|
+
|
|
2483
|
+
Use: aman-agent --profile ${slug}`
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
fs9.mkdirSync(profileDir, { recursive: true });
|
|
2487
|
+
const globalCore = path9.join(os9.homedir(), ".acore", "core.md");
|
|
2488
|
+
if (fs9.existsSync(globalCore)) {
|
|
2489
|
+
let content = fs9.readFileSync(globalCore, "utf-8");
|
|
2490
|
+
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2491
|
+
content = content.replace(/^# .+$/m, `# ${aiName}`);
|
|
2492
|
+
fs9.writeFileSync(path9.join(profileDir, "core.md"), content, "utf-8");
|
|
2493
|
+
} else {
|
|
2494
|
+
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
2495
|
+
fs9.writeFileSync(path9.join(profileDir, "core.md"), `# ${aiName}
|
|
2496
|
+
|
|
2497
|
+
## Identity
|
|
2498
|
+
- Role: ${aiName} is your AI companion
|
|
2499
|
+
- Personality: helpful, adaptive
|
|
2500
|
+
- Communication: clear and concise
|
|
2501
|
+
- Values: honesty, simplicity
|
|
2502
|
+
- Boundaries: won't pretend to be human
|
|
2503
|
+
`, "utf-8");
|
|
2504
|
+
}
|
|
2505
|
+
return {
|
|
2506
|
+
handled: true,
|
|
2507
|
+
output: pc3.green(`Profile created: ${slug}`) + `
|
|
2508
|
+
Edit: ${path9.join(profileDir, "core.md")}
|
|
2509
|
+
Use: aman-agent --profile ${slug}
|
|
2510
|
+
|
|
2511
|
+
${pc3.dim("Add rules.md or skills.md for profile-specific overrides.")}`
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
case "show": {
|
|
2515
|
+
const name = args[0];
|
|
2516
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /profile show <name>") };
|
|
2517
|
+
const profileDir = path9.join(profilesDir, name);
|
|
2518
|
+
if (!fs9.existsSync(profileDir)) return { handled: true, output: pc3.red(`Profile not found: ${name}`) };
|
|
2519
|
+
const files = fs9.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
|
|
2520
|
+
const lines = files.map((f) => ` ${f}`);
|
|
2521
|
+
return { handled: true, output: `Profile: ${pc3.bold(name)}
|
|
2522
|
+
Files:
|
|
2523
|
+
${lines.join("\n")}` };
|
|
2524
|
+
}
|
|
2525
|
+
case "delete": {
|
|
2526
|
+
const name = args[0];
|
|
2527
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /profile delete <name>") };
|
|
2528
|
+
const profileDir = path9.join(profilesDir, name);
|
|
2529
|
+
if (!fs9.existsSync(profileDir)) return { handled: true, output: pc3.red(`Profile not found: ${name}`) };
|
|
2530
|
+
fs9.rmSync(profileDir, { recursive: true });
|
|
2531
|
+
return { handled: true, output: pc3.dim(`Profile deleted: ${name}`) };
|
|
2532
|
+
}
|
|
2533
|
+
case "help":
|
|
2534
|
+
return { handled: true, output: `Profile commands:
|
|
2535
|
+
/profile List all profiles
|
|
2536
|
+
/profile create <n> Create new profile
|
|
2537
|
+
/profile show <n> Show profile files
|
|
2538
|
+
/profile delete <n> Delete a profile
|
|
2539
|
+
|
|
2540
|
+
Use profiles:
|
|
2541
|
+
aman-agent --profile <name>
|
|
2542
|
+
AMAN_PROFILE=<name> aman-agent` };
|
|
2543
|
+
default:
|
|
2544
|
+
return { handled: true, output: pc3.yellow(`Unknown profile action: ${action}. Try /profile help`) };
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function handlePlanCommand(action, args) {
|
|
2548
|
+
if (!action) {
|
|
2549
|
+
const active = getActivePlan();
|
|
2550
|
+
if (!active) {
|
|
2551
|
+
return { handled: true, output: pc3.dim("No active plan. Create one with: /plan create <name> | <goal> | <step1>, <step2>, ...") };
|
|
2552
|
+
}
|
|
2553
|
+
return { handled: true, output: formatPlan(active) };
|
|
1406
2554
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2555
|
+
switch (action) {
|
|
2556
|
+
case "create": {
|
|
2557
|
+
const fullArgs = args.join(" ");
|
|
2558
|
+
const parts = fullArgs.split("|").map((p3) => p3.trim());
|
|
2559
|
+
if (parts.length < 3) {
|
|
2560
|
+
return { handled: true, output: pc3.yellow("Usage: /plan create <name> | <goal> | <step1>, <step2>, ...") };
|
|
2561
|
+
}
|
|
2562
|
+
const name = parts[0];
|
|
2563
|
+
const goal = parts[1];
|
|
2564
|
+
const steps = parts[2].split(",").map((s) => s.trim()).filter(Boolean);
|
|
2565
|
+
if (steps.length === 0) {
|
|
2566
|
+
return { handled: true, output: pc3.yellow("Need at least one step. Separate steps with commas.") };
|
|
2567
|
+
}
|
|
2568
|
+
const plan = createPlan(name, goal, steps);
|
|
2569
|
+
return { handled: true, output: pc3.green(`Plan created!
|
|
2570
|
+
|
|
2571
|
+
`) + formatPlan(plan) };
|
|
2572
|
+
}
|
|
2573
|
+
case "done": {
|
|
2574
|
+
const active = getActivePlan();
|
|
2575
|
+
if (!active) return { handled: true, output: pc3.yellow("No active plan.") };
|
|
2576
|
+
if (args.length > 0) {
|
|
2577
|
+
const stepNum = parseInt(args[0], 10);
|
|
2578
|
+
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
2579
|
+
return { handled: true, output: pc3.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
2580
|
+
}
|
|
2581
|
+
markStepDone(active, stepNum - 1);
|
|
2582
|
+
return { handled: true, output: pc3.green(`Step ${stepNum} done!`) + "\n\n" + formatPlan(active) };
|
|
2583
|
+
}
|
|
2584
|
+
const next = active.steps.findIndex((s) => !s.done);
|
|
2585
|
+
if (next < 0) return { handled: true, output: pc3.green("All steps already complete!") };
|
|
2586
|
+
markStepDone(active, next);
|
|
2587
|
+
return { handled: true, output: pc3.green(`Step ${next + 1} done!`) + "\n\n" + formatPlan(active) };
|
|
2588
|
+
}
|
|
2589
|
+
case "undo": {
|
|
2590
|
+
const active = getActivePlan();
|
|
2591
|
+
if (!active) return { handled: true, output: pc3.yellow("No active plan.") };
|
|
2592
|
+
const stepNum = parseInt(args[0], 10);
|
|
2593
|
+
if (isNaN(stepNum) || stepNum < 1 || stepNum > active.steps.length) {
|
|
2594
|
+
return { handled: true, output: pc3.yellow(`Invalid step number. Range: 1-${active.steps.length}`) };
|
|
2595
|
+
}
|
|
2596
|
+
markStepUndone(active, stepNum - 1);
|
|
2597
|
+
return { handled: true, output: pc3.dim(`Step ${stepNum} unmarked.`) + "\n\n" + formatPlan(active) };
|
|
2598
|
+
}
|
|
2599
|
+
case "list": {
|
|
2600
|
+
const plans = listPlans();
|
|
2601
|
+
if (plans.length === 0) return { handled: true, output: pc3.dim("No plans yet.") };
|
|
2602
|
+
const lines = plans.map((p3) => {
|
|
2603
|
+
const done = p3.steps.filter((s) => s.done).length;
|
|
2604
|
+
const total = p3.steps.length;
|
|
2605
|
+
const status = p3.active ? pc3.green("active") : pc3.dim("inactive");
|
|
2606
|
+
return ` ${p3.name} \u2014 ${done}/${total} steps (${status})`;
|
|
2607
|
+
});
|
|
2608
|
+
return { handled: true, output: "Plans:\n" + lines.join("\n") };
|
|
2609
|
+
}
|
|
2610
|
+
case "switch": {
|
|
2611
|
+
const name = args.join(" ");
|
|
2612
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /plan switch <name>") };
|
|
2613
|
+
const plan = setActivePlan(name);
|
|
2614
|
+
if (!plan) return { handled: true, output: pc3.red(`Plan not found: ${name}`) };
|
|
2615
|
+
return { handled: true, output: pc3.green(`Switched to: ${plan.name}`) + "\n\n" + formatPlan(plan) };
|
|
2616
|
+
}
|
|
2617
|
+
case "show": {
|
|
2618
|
+
const name = args.join(" ");
|
|
2619
|
+
if (!name) return { handled: true, output: pc3.yellow("Usage: /plan show <name>") };
|
|
2620
|
+
const plan = loadPlan(name);
|
|
2621
|
+
if (!plan) return { handled: true, output: pc3.red(`Plan not found: ${name}`) };
|
|
2622
|
+
return { handled: true, output: formatPlan(plan) };
|
|
2623
|
+
}
|
|
2624
|
+
case "help":
|
|
2625
|
+
return { handled: true, output: `Plan commands:
|
|
2626
|
+
/plan Show active plan
|
|
2627
|
+
/plan create <name> | <goal> | <step1>, <step2>, ...
|
|
2628
|
+
/plan done [step#] Mark step complete (next if no number)
|
|
2629
|
+
/plan undo <step#> Unmark a step
|
|
2630
|
+
/plan list List all plans
|
|
2631
|
+
/plan switch <name> Switch active plan
|
|
2632
|
+
/plan show <name> Show a specific plan` };
|
|
2633
|
+
default:
|
|
2634
|
+
return { handled: true, output: pc3.yellow(`Unknown plan action: ${action}. Try /plan help`) };
|
|
1416
2635
|
}
|
|
1417
|
-
const content = fs5.readFileSync(logPath, "utf-8");
|
|
1418
|
-
const lines = content.trim().split("\n");
|
|
1419
|
-
const last20 = lines.slice(-20).join("\n");
|
|
1420
|
-
return { handled: true, output: pc.bold("Debug Log (last 20 entries):\n") + pc.dim(last20) };
|
|
1421
2636
|
}
|
|
1422
2637
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1423
2638
|
"quit",
|
|
@@ -1443,7 +2658,11 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1443
2658
|
"update-config",
|
|
1444
2659
|
"reconfig",
|
|
1445
2660
|
"update",
|
|
1446
|
-
"upgrade"
|
|
2661
|
+
"upgrade",
|
|
2662
|
+
"plan",
|
|
2663
|
+
"profile",
|
|
2664
|
+
"delegate",
|
|
2665
|
+
"team"
|
|
1447
2666
|
]);
|
|
1448
2667
|
async function handleCommand(input, ctx) {
|
|
1449
2668
|
const trimmed = input.trim();
|
|
@@ -1458,9 +2677,9 @@ async function handleCommand(input, ctx) {
|
|
|
1458
2677
|
case "help":
|
|
1459
2678
|
return handleHelp();
|
|
1460
2679
|
case "clear":
|
|
1461
|
-
return { handled: true, output:
|
|
2680
|
+
return { handled: true, output: pc3.dim("Conversation cleared."), clearHistory: true };
|
|
1462
2681
|
case "model":
|
|
1463
|
-
return { handled: true, output: ctx.model ? `Model: ${
|
|
2682
|
+
return { handled: true, output: ctx.model ? `Model: ${pc3.bold(ctx.model)}` : "Model: unknown" };
|
|
1464
2683
|
case "identity":
|
|
1465
2684
|
return handleIdentityCommand(action, args, ctx);
|
|
1466
2685
|
case "rules":
|
|
@@ -1491,6 +2710,14 @@ async function handleCommand(input, ctx) {
|
|
|
1491
2710
|
case "update-config":
|
|
1492
2711
|
case "reconfig":
|
|
1493
2712
|
return handleReconfig();
|
|
2713
|
+
case "plan":
|
|
2714
|
+
return handlePlanCommand(action, args);
|
|
2715
|
+
case "profile":
|
|
2716
|
+
return handleProfileCommand(action, args);
|
|
2717
|
+
case "delegate":
|
|
2718
|
+
return handleDelegateCommand(action, args, ctx);
|
|
2719
|
+
case "team":
|
|
2720
|
+
return handleTeamCommand(action, args, ctx);
|
|
1494
2721
|
case "update":
|
|
1495
2722
|
case "upgrade":
|
|
1496
2723
|
return handleUpdate();
|
|
@@ -1500,8 +2727,183 @@ async function handleCommand(input, ctx) {
|
|
|
1500
2727
|
}
|
|
1501
2728
|
|
|
1502
2729
|
// src/hooks.ts
|
|
1503
|
-
import
|
|
2730
|
+
import pc4 from "picocolors";
|
|
1504
2731
|
import * as p from "@clack/prompts";
|
|
2732
|
+
import fs10 from "fs";
|
|
2733
|
+
import path10 from "path";
|
|
2734
|
+
|
|
2735
|
+
// src/personality.ts
|
|
2736
|
+
var FRUSTRATION_SIGNALS = [
|
|
2737
|
+
/\b(ugh|argh|damn|dammit|wtf|ffs|shit|fuck|crap|hate this|stupid|broken|still not|doesn't work|not working|won't work|keeps failing|again\?!|what the hell|for the love of|give up|giving up|fed up)\b/i,
|
|
2738
|
+
/\b(why (is|does|won't|can't|isn't)|same (error|issue|problem|bug)|tried everything|nothing works|no idea|lost|stuck|frustrated|annoying|impossible)\b/i,
|
|
2739
|
+
/!{2,}/,
|
|
2740
|
+
// multiple exclamation marks
|
|
2741
|
+
/\?{2,}/
|
|
2742
|
+
// multiple question marks (exasperation)
|
|
2743
|
+
];
|
|
2744
|
+
var EXCITEMENT_SIGNALS = [
|
|
2745
|
+
/\b(amazing|awesome|perfect|brilliant|love it|yes!|nice!|great!|finally|it works|nailed it|beautiful|incredible|exactly|that's it|hell yeah|wow|woah|let's go)\b/i,
|
|
2746
|
+
/\b(excited|pumped|stoked|can't wait|this is great|so cool|love this)\b/i,
|
|
2747
|
+
/!{1,}.*(!|🎉|🚀|✨|💪|🔥)/
|
|
2748
|
+
];
|
|
2749
|
+
var CONFUSION_SIGNALS = [
|
|
2750
|
+
/\b(confused|don't understand|what do you mean|huh\??|makes no sense|i'm lost|unclear|what\?|how does that|wait what|can you explain|i don't get)\b/i,
|
|
2751
|
+
/\b(which one|what's the difference|should i|not sure (if|what|how|why|whether))\b/i
|
|
2752
|
+
];
|
|
2753
|
+
var FATIGUE_SIGNALS = [
|
|
2754
|
+
/\b(tired|exhausted|long day|need (a )?break|calling it|wrapping up|done for (now|today)|heading (to bed|off)|good night|gn|signing off|one more thing then|last one)\b/i,
|
|
2755
|
+
/\b(brain (is )?fried|can't think|eyes (are )?heavy|running on fumes|barely awake)\b/i
|
|
2756
|
+
];
|
|
2757
|
+
function scorePatterns(text2, patterns) {
|
|
2758
|
+
let hits = 0;
|
|
2759
|
+
for (const p3 of patterns) {
|
|
2760
|
+
if (p3.test(text2)) hits++;
|
|
2761
|
+
}
|
|
2762
|
+
return Math.min(hits / patterns.length, 1);
|
|
2763
|
+
}
|
|
2764
|
+
function detectSentiment(recentMessages) {
|
|
2765
|
+
if (recentMessages.length === 0) {
|
|
2766
|
+
return { frustration: 0, excitement: 0, confusion: 0, fatigue: 0, dominant: "neutral" };
|
|
2767
|
+
}
|
|
2768
|
+
const weights = [1, 0.6, 0.3, 0.2, 0.1];
|
|
2769
|
+
let frustration = 0, excitement = 0, confusion = 0, fatigue = 0;
|
|
2770
|
+
let totalWeight = 0;
|
|
2771
|
+
for (let i = 0; i < Math.min(recentMessages.length, weights.length); i++) {
|
|
2772
|
+
const msg = recentMessages[recentMessages.length - 1 - i];
|
|
2773
|
+
const w = weights[i];
|
|
2774
|
+
totalWeight += w;
|
|
2775
|
+
frustration += scorePatterns(msg, FRUSTRATION_SIGNALS) * w;
|
|
2776
|
+
excitement += scorePatterns(msg, EXCITEMENT_SIGNALS) * w;
|
|
2777
|
+
confusion += scorePatterns(msg, CONFUSION_SIGNALS) * w;
|
|
2778
|
+
fatigue += scorePatterns(msg, FATIGUE_SIGNALS) * w;
|
|
2779
|
+
}
|
|
2780
|
+
if (totalWeight > 0) {
|
|
2781
|
+
frustration /= totalWeight;
|
|
2782
|
+
excitement /= totalWeight;
|
|
2783
|
+
confusion /= totalWeight;
|
|
2784
|
+
fatigue /= totalWeight;
|
|
2785
|
+
}
|
|
2786
|
+
const scores = { frustrated: frustration, excited: excitement, confused: confusion, fatigued: fatigue };
|
|
2787
|
+
const maxKey = Object.entries(scores).reduce((a, b) => a[1] > b[1] ? a : b);
|
|
2788
|
+
const dominant = maxKey[1] > 0.15 ? maxKey[0] : "neutral";
|
|
2789
|
+
return { frustration, excitement, confusion, fatigue, dominant };
|
|
2790
|
+
}
|
|
2791
|
+
function computePersonality(signals) {
|
|
2792
|
+
const { timePeriod, sessionMinutes, turnCount, recentMessages } = signals;
|
|
2793
|
+
const sentiment = detectSentiment(recentMessages || []);
|
|
2794
|
+
let energy = "steady";
|
|
2795
|
+
if (timePeriod === "morning" && sentiment.dominant !== "fatigued") {
|
|
2796
|
+
energy = "high-drive";
|
|
2797
|
+
} else if (timePeriod === "late-night" || timePeriod === "night" && sessionMinutes > 45) {
|
|
2798
|
+
energy = "reflective";
|
|
2799
|
+
} else if (sentiment.dominant === "fatigued") {
|
|
2800
|
+
energy = "reflective";
|
|
2801
|
+
} else if (sentiment.dominant === "excited") {
|
|
2802
|
+
energy = "high-drive";
|
|
2803
|
+
} else if (timePeriod === "afternoon" && turnCount > 20) {
|
|
2804
|
+
energy = "reflective";
|
|
2805
|
+
}
|
|
2806
|
+
let activeMode = "Default";
|
|
2807
|
+
if (timePeriod === "late-night") {
|
|
2808
|
+
activeMode = "Personal";
|
|
2809
|
+
} else if (sentiment.dominant === "frustrated" || sentiment.dominant === "fatigued") {
|
|
2810
|
+
activeMode = "Personal";
|
|
2811
|
+
}
|
|
2812
|
+
const readParts = [];
|
|
2813
|
+
switch (timePeriod) {
|
|
2814
|
+
case "late-night":
|
|
2815
|
+
readParts.push("late night session");
|
|
2816
|
+
if (sessionMinutes > 60) readParts.push("been going a while");
|
|
2817
|
+
else readParts.push("quiet hours");
|
|
2818
|
+
break;
|
|
2819
|
+
case "morning":
|
|
2820
|
+
readParts.push("morning session");
|
|
2821
|
+
if (turnCount <= 3) readParts.push("just getting started");
|
|
2822
|
+
else readParts.push("building momentum");
|
|
2823
|
+
break;
|
|
2824
|
+
case "afternoon":
|
|
2825
|
+
readParts.push("afternoon session");
|
|
2826
|
+
if (turnCount > 15) readParts.push("deep in flow");
|
|
2827
|
+
else readParts.push("steady pace");
|
|
2828
|
+
break;
|
|
2829
|
+
case "evening":
|
|
2830
|
+
readParts.push("evening session");
|
|
2831
|
+
if (sessionMinutes > 60) readParts.push("long session");
|
|
2832
|
+
break;
|
|
2833
|
+
case "night":
|
|
2834
|
+
readParts.push("night session");
|
|
2835
|
+
if (sessionMinutes > 45) readParts.push("getting late");
|
|
2836
|
+
break;
|
|
2837
|
+
}
|
|
2838
|
+
switch (sentiment.dominant) {
|
|
2839
|
+
case "frustrated":
|
|
2840
|
+
readParts.push("user seems stuck or frustrated");
|
|
2841
|
+
break;
|
|
2842
|
+
case "excited":
|
|
2843
|
+
readParts.push("user is energized and making progress");
|
|
2844
|
+
break;
|
|
2845
|
+
case "confused":
|
|
2846
|
+
readParts.push("user may need clearer explanations");
|
|
2847
|
+
break;
|
|
2848
|
+
case "fatigued":
|
|
2849
|
+
readParts.push("user seems tired");
|
|
2850
|
+
break;
|
|
2851
|
+
}
|
|
2852
|
+
const currentRead = readParts.join(", ");
|
|
2853
|
+
const sleepReminder = timePeriod === "late-night" && sessionMinutes > 60 || timePeriod === "night" && sessionMinutes > 90;
|
|
2854
|
+
let wellbeingNudge = null;
|
|
2855
|
+
if (sleepReminder && sentiment.dominant === "frustrated") {
|
|
2856
|
+
wellbeingNudge = "sleep-frustrated";
|
|
2857
|
+
} else if (sleepReminder) {
|
|
2858
|
+
wellbeingNudge = "sleep";
|
|
2859
|
+
} else if (sentiment.dominant === "frustrated" && sessionMinutes > 90) {
|
|
2860
|
+
wellbeingNudge = "break-frustrated";
|
|
2861
|
+
} else if (sentiment.dominant === "frustrated" && turnCount > 15) {
|
|
2862
|
+
wellbeingNudge = "step-back";
|
|
2863
|
+
} else if (sentiment.dominant === "fatigued") {
|
|
2864
|
+
wellbeingNudge = "rest";
|
|
2865
|
+
} else if (sessionMinutes > 120) {
|
|
2866
|
+
wellbeingNudge = "break-long-session";
|
|
2867
|
+
}
|
|
2868
|
+
return { currentRead, energy, activeMode, sleepReminder, wellbeingNudge, sentiment };
|
|
2869
|
+
}
|
|
2870
|
+
var WELLBEING_NUDGES = {
|
|
2871
|
+
"sleep": `<wellbeing>
|
|
2872
|
+
It's late and this session has been running a while. When there's a natural pause, gently mention they might want to wrap up soon. One brief mention is enough \u2014 don't be pushy.
|
|
2873
|
+
</wellbeing>`,
|
|
2874
|
+
"sleep-frustrated": `<wellbeing>
|
|
2875
|
+
It's late, the session has been long, and the user seems frustrated. This is a tough combination. Acknowledge what they're dealing with is hard, suggest they sleep on it \u2014 fresh eyes in the morning often solve what hours of late-night debugging can't. Be warm, not condescending.
|
|
2876
|
+
</wellbeing>`,
|
|
2877
|
+
"break-frustrated": `<wellbeing>
|
|
2878
|
+
The user has been at this for over 90 minutes and seems frustrated. If the conversation allows, gently suggest stepping away for a few minutes \u2014 a short break often unblocks what persistence can't. Frame it as a strategy, not giving up.
|
|
2879
|
+
</wellbeing>`,
|
|
2880
|
+
"step-back": `<wellbeing>
|
|
2881
|
+
The user seems stuck or frustrated. Consider: offer to re-approach the problem from a different angle, break it into smaller pieces, or explain the underlying concept. Match their directness \u2014 don't over-soothe, just help them find a way forward.
|
|
2882
|
+
</wellbeing>`,
|
|
2883
|
+
"rest": `<wellbeing>
|
|
2884
|
+
The user seems tired. Keep responses concise and to the point. If they mention wrapping up, support that. Don't add extra complexity or tangents.
|
|
2885
|
+
</wellbeing>`,
|
|
2886
|
+
"break-long-session": `<wellbeing>
|
|
2887
|
+
This session has been running for over 2 hours. If there's a natural moment, a brief mention that a short break might help maintain focus is fine. Once is enough.
|
|
2888
|
+
</wellbeing>`
|
|
2889
|
+
};
|
|
2890
|
+
function formatWellbeingNudge(state) {
|
|
2891
|
+
if (!state.wellbeingNudge) return null;
|
|
2892
|
+
return WELLBEING_NUDGES[state.wellbeingNudge] || null;
|
|
2893
|
+
}
|
|
2894
|
+
async function syncPersonalityToCore(state, mcpManager) {
|
|
2895
|
+
try {
|
|
2896
|
+
await mcpManager.callTool("identity_update_dynamics", {
|
|
2897
|
+
currentRead: state.currentRead,
|
|
2898
|
+
energy: state.energy,
|
|
2899
|
+
activeMode: state.activeMode
|
|
2900
|
+
});
|
|
2901
|
+
} catch (err) {
|
|
2902
|
+
log.debug("personality", "identity_update_dynamics failed", err);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
// src/hooks.ts
|
|
1505
2907
|
function getTimeContext() {
|
|
1506
2908
|
const now = /* @__PURE__ */ new Date();
|
|
1507
2909
|
const hour = now.getHours();
|
|
@@ -1521,6 +2923,10 @@ Adapt your tone naturally \u2014 don't announce the time, just be contextually a
|
|
|
1521
2923
|
</time-context>`;
|
|
1522
2924
|
}
|
|
1523
2925
|
var isHookCall = false;
|
|
2926
|
+
var sessionStartTime = Date.now();
|
|
2927
|
+
function getSessionStartTime() {
|
|
2928
|
+
return sessionStartTime;
|
|
2929
|
+
}
|
|
1524
2930
|
async function onSessionStart(ctx) {
|
|
1525
2931
|
let greeting = "";
|
|
1526
2932
|
let contextInjection = "";
|
|
@@ -1529,10 +2935,8 @@ async function onSessionStart(ctx) {
|
|
|
1529
2935
|
const visibleReminders = [];
|
|
1530
2936
|
try {
|
|
1531
2937
|
isHookCall = true;
|
|
1532
|
-
const recallResult = await
|
|
1533
|
-
|
|
1534
|
-
firstRun = true;
|
|
1535
|
-
}
|
|
2938
|
+
const recallResult = await memoryRecall("*", { limit: 1 });
|
|
2939
|
+
firstRun = recallResult.total === 0;
|
|
1536
2940
|
} catch {
|
|
1537
2941
|
firstRun = true;
|
|
1538
2942
|
} finally {
|
|
@@ -1562,9 +2966,9 @@ ${contextInjection}`;
|
|
|
1562
2966
|
if (ctx.config.memoryRecall) {
|
|
1563
2967
|
try {
|
|
1564
2968
|
isHookCall = true;
|
|
1565
|
-
const
|
|
1566
|
-
if (
|
|
1567
|
-
greeting +=
|
|
2969
|
+
const contextResult = await memoryContext("session context");
|
|
2970
|
+
if (contextResult.memoriesUsed > 0) {
|
|
2971
|
+
greeting += contextResult.text;
|
|
1568
2972
|
}
|
|
1569
2973
|
} catch (err) {
|
|
1570
2974
|
log.warn("hooks", "memory_context recall failed", err);
|
|
@@ -1595,12 +2999,12 @@ ${contextInjection}`;
|
|
|
1595
2999
|
else greeting = timeContext;
|
|
1596
3000
|
try {
|
|
1597
3001
|
isHookCall = true;
|
|
1598
|
-
const
|
|
1599
|
-
if (
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
for (const
|
|
1603
|
-
visibleReminders.push(
|
|
3002
|
+
const reminders = reminderCheck();
|
|
3003
|
+
if (reminders.length > 0) {
|
|
3004
|
+
const reminderText = reminders.map((r) => r.content).join("\n");
|
|
3005
|
+
greeting += "\n\n<pending-reminders>\n" + reminderText + "\n</pending-reminders>";
|
|
3006
|
+
for (const r of reminders) {
|
|
3007
|
+
visibleReminders.push(r.content);
|
|
1604
3008
|
}
|
|
1605
3009
|
}
|
|
1606
3010
|
} catch (err) {
|
|
@@ -1608,6 +3012,27 @@ ${contextInjection}`;
|
|
|
1608
3012
|
} finally {
|
|
1609
3013
|
isHookCall = false;
|
|
1610
3014
|
}
|
|
3015
|
+
if (ctx.config.personalityAdapt !== false) {
|
|
3016
|
+
sessionStartTime = Date.now();
|
|
3017
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
3018
|
+
let period;
|
|
3019
|
+
if (hour < 6) period = "late-night";
|
|
3020
|
+
else if (hour < 12) period = "morning";
|
|
3021
|
+
else if (hour < 17) period = "afternoon";
|
|
3022
|
+
else if (hour < 21) period = "evening";
|
|
3023
|
+
else period = "night";
|
|
3024
|
+
const state = computePersonality({
|
|
3025
|
+
timePeriod: period,
|
|
3026
|
+
sessionMinutes: 0,
|
|
3027
|
+
turnCount: 0
|
|
3028
|
+
});
|
|
3029
|
+
syncPersonalityToCore(state, ctx.mcpManager).catch(() => {
|
|
3030
|
+
});
|
|
3031
|
+
const nudge = formatWellbeingNudge(state);
|
|
3032
|
+
if (nudge) {
|
|
3033
|
+
greeting += "\n" + nudge;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
1611
3036
|
if (greeting) {
|
|
1612
3037
|
contextInjection = `<session-context>
|
|
1613
3038
|
${greeting}
|
|
@@ -1689,16 +3114,12 @@ async function onWorkflowMatch(userInput, ctx) {
|
|
|
1689
3114
|
async function onSessionEnd(ctx, messages, sessionId) {
|
|
1690
3115
|
try {
|
|
1691
3116
|
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
1692
|
-
console.log(
|
|
3117
|
+
console.log(pc4.dim("\n Saving conversation to memory..."));
|
|
1693
3118
|
const textMessages = messages.filter((m) => typeof m.content === "string").slice(-50);
|
|
1694
3119
|
for (const msg of textMessages) {
|
|
1695
3120
|
try {
|
|
1696
3121
|
isHookCall = true;
|
|
1697
|
-
|
|
1698
|
-
session_id: sessionId,
|
|
1699
|
-
role: msg.role,
|
|
1700
|
-
content: msg.content.slice(0, 5e3)
|
|
1701
|
-
});
|
|
3122
|
+
memoryLog(sessionId, msg.role, msg.content.slice(0, 5e3));
|
|
1702
3123
|
} catch (err) {
|
|
1703
3124
|
log.debug("hooks", "memory_log write failed for " + sessionId, err);
|
|
1704
3125
|
} finally {
|
|
@@ -1724,7 +3145,57 @@ async function onSessionEnd(ctx, messages, sessionId) {
|
|
|
1724
3145
|
isHookCall = false;
|
|
1725
3146
|
}
|
|
1726
3147
|
}
|
|
1727
|
-
console.log(
|
|
3148
|
+
console.log(pc4.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
3149
|
+
}
|
|
3150
|
+
const projectContextPath = path10.join(process.cwd(), ".acore", "context.md");
|
|
3151
|
+
if (fs10.existsSync(projectContextPath) && messages.length > 2) {
|
|
3152
|
+
try {
|
|
3153
|
+
let contextContent = fs10.readFileSync(projectContextPath, "utf-8");
|
|
3154
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3155
|
+
let lastUserMsg = "";
|
|
3156
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3157
|
+
if (messages[i].role === "user" && typeof messages[i].content === "string") {
|
|
3158
|
+
lastUserMsg = messages[i].content.slice(0, 200);
|
|
3159
|
+
break;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
const sessionPattern = /## Session\n[\s\S]*?(?=\n## |$)/;
|
|
3163
|
+
if (sessionPattern.test(contextContent)) {
|
|
3164
|
+
const newSession = `## Session
|
|
3165
|
+
- Last updated: ${now}
|
|
3166
|
+
- Resume: ${lastUserMsg || "See conversation history"}
|
|
3167
|
+
- Active topics: [see memory]
|
|
3168
|
+
- Recent decisions: [see memory]
|
|
3169
|
+
- Temp notes: [cleared]`;
|
|
3170
|
+
contextContent = contextContent.replace(sessionPattern, newSession);
|
|
3171
|
+
fs10.writeFileSync(projectContextPath, contextContent, "utf-8");
|
|
3172
|
+
log.debug("hooks", `Updated project context: ${projectContextPath}`);
|
|
3173
|
+
}
|
|
3174
|
+
} catch (err) {
|
|
3175
|
+
log.debug("hooks", "project context update failed", err);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
if (ctx.config.personalityAdapt !== false) {
|
|
3179
|
+
const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
|
|
3180
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
3181
|
+
let period;
|
|
3182
|
+
if (hour < 6) period = "late-night";
|
|
3183
|
+
else if (hour < 12) period = "morning";
|
|
3184
|
+
else if (hour < 17) period = "afternoon";
|
|
3185
|
+
else if (hour < 21) period = "evening";
|
|
3186
|
+
else period = "night";
|
|
3187
|
+
const turnCount = messages.filter((m) => m.role === "user").length;
|
|
3188
|
+
const finalState = computePersonality({
|
|
3189
|
+
timePeriod: period,
|
|
3190
|
+
sessionMinutes,
|
|
3191
|
+
turnCount
|
|
3192
|
+
});
|
|
3193
|
+
try {
|
|
3194
|
+
isHookCall = true;
|
|
3195
|
+
await syncPersonalityToCore(finalState, ctx.mcpManager);
|
|
3196
|
+
} finally {
|
|
3197
|
+
isHookCall = false;
|
|
3198
|
+
}
|
|
1728
3199
|
}
|
|
1729
3200
|
if (ctx.config.evalPrompt) {
|
|
1730
3201
|
const rating = await p.select({
|
|
@@ -1826,6 +3297,365 @@ ${summaryParts.slice(0, 20).join("\n")}
|
|
|
1826
3297
|
messages.push(...recent);
|
|
1827
3298
|
}
|
|
1828
3299
|
|
|
3300
|
+
// src/skill-engine.ts
|
|
3301
|
+
import fs11 from "fs";
|
|
3302
|
+
import path11 from "path";
|
|
3303
|
+
import os10 from "os";
|
|
3304
|
+
var SKILL_TRIGGERS = {
|
|
3305
|
+
testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
|
|
3306
|
+
"api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
|
|
3307
|
+
security: ["security", "auth", "csrf", "xss", "injection", "cors", "jwt", "token", "oauth", "password", "hash", "encrypt", "vulnerability", "owasp", "sanitize"],
|
|
3308
|
+
performance: ["performance", "slow", "latency", "cache", "optimize", "profil", "bundle size", "lazy load", "memory leak", "benchmark", "bottleneck"],
|
|
3309
|
+
"code-review": ["review", "pr review", "pull request", "code quality", "clean code", "best practice"],
|
|
3310
|
+
documentation: ["document", "readme", "jsdoc", "tsdoc", "changelog", "adr", "comment"],
|
|
3311
|
+
"git-workflow": ["git", "branch", "merge", "rebase", "cherry-pick", "bisect", "stash", "commit message", "pr", "pull request"],
|
|
3312
|
+
debugging: ["debug", "breakpoint", "stack trace", "error", "exception", "crash", "bug", "issue", "unexpected", "reproduce"],
|
|
3313
|
+
refactoring: ["refactor", "extract", "rename", "move", "split", "consolidate", "dry", "code smell", "technical debt", "legacy"],
|
|
3314
|
+
database: ["database", "schema", "migration", "index", "query", "sql", "postgres", "mysql", "sqlite", "mongo", "orm", "prisma", "drizzle"],
|
|
3315
|
+
typescript: ["typescript", "type", "interface", "generic", "infer", "utility type", "zod", "discriminated union", "type guard", "as const"],
|
|
3316
|
+
accessibility: ["accessibility", "a11y", "aria", "screen reader", "wcag", "semantic html", "tab order", "focus", "contrast"]
|
|
3317
|
+
};
|
|
3318
|
+
var LEVEL_FILE = path11.join(os10.homedir(), ".aman-agent", "skill-levels.json");
|
|
3319
|
+
function loadSkillLevels() {
|
|
3320
|
+
try {
|
|
3321
|
+
if (fs11.existsSync(LEVEL_FILE)) {
|
|
3322
|
+
return JSON.parse(fs11.readFileSync(LEVEL_FILE, "utf-8"));
|
|
3323
|
+
}
|
|
3324
|
+
} catch {
|
|
3325
|
+
}
|
|
3326
|
+
return {};
|
|
3327
|
+
}
|
|
3328
|
+
function saveSkillLevels(levels) {
|
|
3329
|
+
const dir = path11.dirname(LEVEL_FILE);
|
|
3330
|
+
if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
|
|
3331
|
+
fs11.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
|
|
3332
|
+
}
|
|
3333
|
+
function computeLevel(activations) {
|
|
3334
|
+
if (activations >= 50) return { level: 5, label: "Expert" };
|
|
3335
|
+
if (activations >= 25) return { level: 4, label: "Advanced" };
|
|
3336
|
+
if (activations >= 10) return { level: 3, label: "Proficient" };
|
|
3337
|
+
if (activations >= 3) return { level: 2, label: "Familiar" };
|
|
3338
|
+
return { level: 1, label: "Learning" };
|
|
3339
|
+
}
|
|
3340
|
+
function recordActivation(skillName) {
|
|
3341
|
+
const levels = loadSkillLevels();
|
|
3342
|
+
if (!levels[skillName]) {
|
|
3343
|
+
levels[skillName] = { name: skillName, activations: 0, lastUsed: "", userPatterns: [] };
|
|
3344
|
+
}
|
|
3345
|
+
levels[skillName].activations++;
|
|
3346
|
+
levels[skillName].lastUsed = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3347
|
+
saveSkillLevels(levels);
|
|
3348
|
+
return computeLevel(levels[skillName].activations);
|
|
3349
|
+
}
|
|
3350
|
+
function matchSkills(userInput, installedSkillNames) {
|
|
3351
|
+
const input = userInput.toLowerCase();
|
|
3352
|
+
const matched = [];
|
|
3353
|
+
for (const skillName of installedSkillNames) {
|
|
3354
|
+
const triggers = SKILL_TRIGGERS[skillName];
|
|
3355
|
+
if (!triggers) continue;
|
|
3356
|
+
for (const trigger of triggers) {
|
|
3357
|
+
if (input.includes(trigger)) {
|
|
3358
|
+
matched.push(skillName);
|
|
3359
|
+
break;
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
return matched;
|
|
3364
|
+
}
|
|
3365
|
+
function formatSkillContext(skillName, skillContent, level) {
|
|
3366
|
+
let depthHint;
|
|
3367
|
+
if (level.level >= 4) {
|
|
3368
|
+
depthHint = "User is advanced \u2014 skip basics, focus on edge cases and proactive optimization.";
|
|
3369
|
+
} else if (level.level >= 3) {
|
|
3370
|
+
depthHint = "User is proficient \u2014 brief reminders of principles, focus on the specific task.";
|
|
3371
|
+
} else if (level.level >= 2) {
|
|
3372
|
+
depthHint = "User is familiar \u2014 explain reasoning briefly, show patterns.";
|
|
3373
|
+
} else {
|
|
3374
|
+
depthHint = "User is learning \u2014 explain concepts clearly, show examples, be patient.";
|
|
3375
|
+
}
|
|
3376
|
+
return `<active-skill name="${skillName}" level="${level.level}" label="${level.label}">
|
|
3377
|
+
${depthHint}
|
|
3378
|
+
|
|
3379
|
+
${skillContent}
|
|
3380
|
+
</active-skill>`;
|
|
3381
|
+
}
|
|
3382
|
+
async function autoTriggerSkills(userInput, mcpManager) {
|
|
3383
|
+
try {
|
|
3384
|
+
const result = await mcpManager.callTool("skill_list", {});
|
|
3385
|
+
const skills = JSON.parse(result);
|
|
3386
|
+
const installed = skills.filter((s) => s.installed).map((s) => s.name);
|
|
3387
|
+
if (installed.length === 0) return "";
|
|
3388
|
+
const matched = matchSkills(userInput, installed);
|
|
3389
|
+
if (matched.length === 0) return "";
|
|
3390
|
+
const blocks = [];
|
|
3391
|
+
for (const skillName of matched.slice(0, 2)) {
|
|
3392
|
+
const level = recordActivation(skillName);
|
|
3393
|
+
const skillsContent = await mcpManager.callTool("skill_search", { query: skillName });
|
|
3394
|
+
const skillEntries = JSON.parse(skillsContent);
|
|
3395
|
+
const entry = skillEntries.find((s) => s.name.toLowerCase() === skillName.toLowerCase());
|
|
3396
|
+
if (entry) {
|
|
3397
|
+
blocks.push(formatSkillContext(skillName, entry.description, level));
|
|
3398
|
+
}
|
|
3399
|
+
log.debug("skill-engine", `Auto-triggered: ${skillName} (Lv.${level.level} ${level.label})`);
|
|
3400
|
+
}
|
|
3401
|
+
return blocks.join("\n\n");
|
|
3402
|
+
} catch (err) {
|
|
3403
|
+
log.debug("skill-engine", "autoTriggerSkills failed", err);
|
|
3404
|
+
return "";
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
function enrichSkill(skillName, pattern) {
|
|
3408
|
+
const levels = loadSkillLevels();
|
|
3409
|
+
if (!levels[skillName]) {
|
|
3410
|
+
levels[skillName] = { name: skillName, activations: 0, lastUsed: "", userPatterns: [] };
|
|
3411
|
+
}
|
|
3412
|
+
const existing = levels[skillName].userPatterns;
|
|
3413
|
+
if (existing.length >= 20) return;
|
|
3414
|
+
if (existing.some((p3) => p3.toLowerCase() === pattern.toLowerCase())) return;
|
|
3415
|
+
levels[skillName].userPatterns.push(pattern);
|
|
3416
|
+
saveSkillLevels(levels);
|
|
3417
|
+
log.debug("skill-engine", `Enriched ${skillName} with pattern: ${pattern.slice(0, 80)}`);
|
|
3418
|
+
}
|
|
3419
|
+
function matchPatternToSkill(patternContent, tags) {
|
|
3420
|
+
const combined = (patternContent + " " + tags.join(" ")).toLowerCase();
|
|
3421
|
+
for (const [skillName, triggers] of Object.entries(SKILL_TRIGGERS)) {
|
|
3422
|
+
for (const trigger of triggers) {
|
|
3423
|
+
if (combined.includes(trigger)) {
|
|
3424
|
+
return skillName;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
return null;
|
|
3429
|
+
}
|
|
3430
|
+
var KNOWLEDGE_LIBRARY = [
|
|
3431
|
+
{
|
|
3432
|
+
name: "security-headers",
|
|
3433
|
+
category: "security",
|
|
3434
|
+
description: "Essential HTTP security headers for web applications",
|
|
3435
|
+
content: `Essential Security Headers:
|
|
3436
|
+
- Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
|
|
3437
|
+
- X-Content-Type-Options: nosniff
|
|
3438
|
+
- X-Frame-Options: DENY
|
|
3439
|
+
- Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
|
3440
|
+
- X-XSS-Protection: 0 (CSP replaces this)
|
|
3441
|
+
- Referrer-Policy: strict-origin-when-cross-origin
|
|
3442
|
+
- Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
|
3443
|
+
},
|
|
3444
|
+
{
|
|
3445
|
+
name: "docker-node",
|
|
3446
|
+
category: "deployment",
|
|
3447
|
+
description: "Production Node.js Dockerfile template",
|
|
3448
|
+
content: `FROM node:22-alpine AS builder
|
|
3449
|
+
WORKDIR /app
|
|
3450
|
+
COPY package*.json ./
|
|
3451
|
+
RUN npm ci --production=false
|
|
3452
|
+
COPY . .
|
|
3453
|
+
RUN npm run build
|
|
3454
|
+
|
|
3455
|
+
FROM node:22-alpine
|
|
3456
|
+
WORKDIR /app
|
|
3457
|
+
RUN addgroup -g 1001 -S nodejs && adduser -S appuser -u 1001
|
|
3458
|
+
COPY --from=builder /app/dist ./dist
|
|
3459
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
3460
|
+
COPY --from=builder /app/package.json ./
|
|
3461
|
+
USER appuser
|
|
3462
|
+
EXPOSE 3000
|
|
3463
|
+
CMD ["node", "dist/index.js"]`
|
|
3464
|
+
},
|
|
3465
|
+
{
|
|
3466
|
+
name: "github-actions-node",
|
|
3467
|
+
category: "ci",
|
|
3468
|
+
description: "CI/CD pipeline for Node.js with GitHub Actions",
|
|
3469
|
+
content: `name: CI
|
|
3470
|
+
on: [push, pull_request]
|
|
3471
|
+
jobs:
|
|
3472
|
+
build:
|
|
3473
|
+
runs-on: ubuntu-latest
|
|
3474
|
+
steps:
|
|
3475
|
+
- uses: actions/checkout@v5
|
|
3476
|
+
- uses: actions/setup-node@v5
|
|
3477
|
+
with: { node-version: 22 }
|
|
3478
|
+
- run: npm ci
|
|
3479
|
+
- run: npm test
|
|
3480
|
+
- run: npm run build`
|
|
3481
|
+
},
|
|
3482
|
+
{
|
|
3483
|
+
name: "env-config",
|
|
3484
|
+
category: "configuration",
|
|
3485
|
+
description: "Environment variable configuration pattern with validation",
|
|
3486
|
+
content: `import { z } from "zod";
|
|
3487
|
+
|
|
3488
|
+
const envSchema = z.object({
|
|
3489
|
+
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
3490
|
+
PORT: z.coerce.number().default(3000),
|
|
3491
|
+
DATABASE_URL: z.string().url(),
|
|
3492
|
+
API_KEY: z.string().min(1),
|
|
3493
|
+
});
|
|
3494
|
+
|
|
3495
|
+
export const env = envSchema.parse(process.env);`
|
|
3496
|
+
},
|
|
3497
|
+
{
|
|
3498
|
+
name: "error-handling",
|
|
3499
|
+
category: "patterns",
|
|
3500
|
+
description: "TypeScript error handling patterns with Result type",
|
|
3501
|
+
content: `type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
|
|
3502
|
+
|
|
3503
|
+
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
|
|
3504
|
+
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
|
|
3505
|
+
|
|
3506
|
+
// Usage:
|
|
3507
|
+
async function fetchUser(id: string): Promise<Result<User>> {
|
|
3508
|
+
try {
|
|
3509
|
+
const user = await db.users.findUnique({ where: { id } });
|
|
3510
|
+
if (!user) return err(new Error("User not found"));
|
|
3511
|
+
return ok(user);
|
|
3512
|
+
} catch (e) {
|
|
3513
|
+
return err(e instanceof Error ? e : new Error(String(e)));
|
|
3514
|
+
}
|
|
3515
|
+
}`
|
|
3516
|
+
},
|
|
3517
|
+
{
|
|
3518
|
+
name: "rate-limiter",
|
|
3519
|
+
category: "security",
|
|
3520
|
+
description: "Token bucket rate limiter implementation",
|
|
3521
|
+
content: `class RateLimiter {
|
|
3522
|
+
private tokens: Map<string, { count: number; resetAt: number }> = new Map();
|
|
3523
|
+
|
|
3524
|
+
constructor(private maxRequests: number, private windowMs: number) {}
|
|
3525
|
+
|
|
3526
|
+
allow(key: string): boolean {
|
|
3527
|
+
const now = Date.now();
|
|
3528
|
+
const entry = this.tokens.get(key);
|
|
3529
|
+
if (!entry || now > entry.resetAt) {
|
|
3530
|
+
this.tokens.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
3531
|
+
return true;
|
|
3532
|
+
}
|
|
3533
|
+
if (entry.count >= this.maxRequests) return false;
|
|
3534
|
+
entry.count++;
|
|
3535
|
+
return true;
|
|
3536
|
+
}
|
|
3537
|
+
}`
|
|
3538
|
+
},
|
|
3539
|
+
{
|
|
3540
|
+
name: "prisma-setup",
|
|
3541
|
+
category: "database",
|
|
3542
|
+
description: "Prisma ORM setup with connection pooling",
|
|
3543
|
+
content: `import { PrismaClient } from "@prisma/client";
|
|
3544
|
+
|
|
3545
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
|
3546
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
|
3547
|
+
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
|
3548
|
+
});
|
|
3549
|
+
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;`
|
|
3550
|
+
},
|
|
3551
|
+
{
|
|
3552
|
+
name: "zod-validation",
|
|
3553
|
+
category: "validation",
|
|
3554
|
+
description: "Zod schema patterns for API input validation",
|
|
3555
|
+
content: `import { z } from "zod";
|
|
3556
|
+
|
|
3557
|
+
const CreateUserSchema = z.object({
|
|
3558
|
+
email: z.string().email(),
|
|
3559
|
+
name: z.string().min(1).max(100),
|
|
3560
|
+
age: z.number().int().min(13).max(150).optional(),
|
|
3561
|
+
role: z.enum(["user", "admin"]).default("user"),
|
|
3562
|
+
tags: z.array(z.string()).max(10).default([]),
|
|
3563
|
+
});
|
|
3564
|
+
|
|
3565
|
+
type CreateUser = z.infer<typeof CreateUserSchema>;
|
|
3566
|
+
|
|
3567
|
+
// Express middleware:
|
|
3568
|
+
function validate<T>(schema: z.ZodSchema<T>) {
|
|
3569
|
+
return (req, res, next) => {
|
|
3570
|
+
const result = schema.safeParse(req.body);
|
|
3571
|
+
if (!result.success) return res.status(400).json({ errors: result.error.flatten() });
|
|
3572
|
+
req.body = result.data;
|
|
3573
|
+
next();
|
|
3574
|
+
};
|
|
3575
|
+
}`
|
|
3576
|
+
},
|
|
3577
|
+
{
|
|
3578
|
+
name: "testing-patterns",
|
|
3579
|
+
category: "testing",
|
|
3580
|
+
description: "Test organization and assertion patterns",
|
|
3581
|
+
content: `// Arrange-Act-Assert pattern
|
|
3582
|
+
describe("UserService", () => {
|
|
3583
|
+
it("creates user with valid email", async () => {
|
|
3584
|
+
// Arrange
|
|
3585
|
+
const input = { email: "test@example.com", name: "Test" };
|
|
3586
|
+
|
|
3587
|
+
// Act
|
|
3588
|
+
const user = await userService.create(input);
|
|
3589
|
+
|
|
3590
|
+
// Assert
|
|
3591
|
+
expect(user.id).toBeDefined();
|
|
3592
|
+
expect(user.email).toBe(input.email);
|
|
3593
|
+
});
|
|
3594
|
+
|
|
3595
|
+
it("rejects duplicate email", async () => {
|
|
3596
|
+
await userService.create({ email: "dup@test.com", name: "First" });
|
|
3597
|
+
await expect(userService.create({ email: "dup@test.com", name: "Second" }))
|
|
3598
|
+
.rejects.toThrow("already exists");
|
|
3599
|
+
});
|
|
3600
|
+
});`
|
|
3601
|
+
},
|
|
3602
|
+
{
|
|
3603
|
+
name: "git-hooks",
|
|
3604
|
+
category: "git",
|
|
3605
|
+
description: "Pre-commit and commit-msg hooks with lint-staged",
|
|
3606
|
+
content: `// package.json
|
|
3607
|
+
{
|
|
3608
|
+
"lint-staged": {
|
|
3609
|
+
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
|
|
3610
|
+
"*.{json,md}": ["prettier --write"]
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// .husky/pre-commit
|
|
3615
|
+
npx lint-staged
|
|
3616
|
+
|
|
3617
|
+
// .husky/commit-msg
|
|
3618
|
+
npx commitlint --edit $1
|
|
3619
|
+
|
|
3620
|
+
// commitlint.config.js
|
|
3621
|
+
module.exports = { extends: ["@commitlint/config-conventional"] };`
|
|
3622
|
+
}
|
|
3623
|
+
];
|
|
3624
|
+
function matchKnowledge(userInput) {
|
|
3625
|
+
const input = userInput.toLowerCase();
|
|
3626
|
+
const keywordMap = {
|
|
3627
|
+
"security header": "security-headers",
|
|
3628
|
+
"csp": "security-headers",
|
|
3629
|
+
"content-security": "security-headers",
|
|
3630
|
+
"dockerfile": "docker-node",
|
|
3631
|
+
"docker": "docker-node",
|
|
3632
|
+
"github action": "github-actions-node",
|
|
3633
|
+
"ci/cd": "github-actions-node",
|
|
3634
|
+
"ci pipeline": "github-actions-node",
|
|
3635
|
+
"env config": "env-config",
|
|
3636
|
+
"environment variable": "env-config",
|
|
3637
|
+
"error handling": "error-handling",
|
|
3638
|
+
"result type": "error-handling",
|
|
3639
|
+
"rate limit": "rate-limiter",
|
|
3640
|
+
"throttle": "rate-limiter",
|
|
3641
|
+
"prisma": "prisma-setup",
|
|
3642
|
+
"zod": "zod-validation",
|
|
3643
|
+
"validation": "zod-validation",
|
|
3644
|
+
"test pattern": "testing-patterns",
|
|
3645
|
+
"arrange act assert": "testing-patterns",
|
|
3646
|
+
"git hook": "git-hooks",
|
|
3647
|
+
"pre-commit": "git-hooks",
|
|
3648
|
+
"lint-staged": "git-hooks",
|
|
3649
|
+
"husky": "git-hooks"
|
|
3650
|
+
};
|
|
3651
|
+
for (const [keyword, itemName] of Object.entries(keywordMap)) {
|
|
3652
|
+
if (input.includes(keyword)) {
|
|
3653
|
+
return KNOWLEDGE_LIBRARY.find((i) => i.name === itemName) || null;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
return null;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
1829
3659
|
// src/memory-extractor.ts
|
|
1830
3660
|
var VALID_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology", "decision", "correction"]);
|
|
1831
3661
|
var MIN_RESPONSE_LENGTH = 50;
|
|
@@ -1876,7 +3706,7 @@ function parseExtractionResult(raw) {
|
|
|
1876
3706
|
return [];
|
|
1877
3707
|
}
|
|
1878
3708
|
}
|
|
1879
|
-
async function extractMemories(userMessage, assistantResponse, client,
|
|
3709
|
+
async function extractMemories(userMessage, assistantResponse, client, state) {
|
|
1880
3710
|
if (!shouldExtract(assistantResponse, state.turnsSinceLastExtraction, state.lastExtractionCount)) {
|
|
1881
3711
|
state.turnsSinceLastExtraction++;
|
|
1882
3712
|
return 0;
|
|
@@ -1900,24 +3730,18 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
1900
3730
|
let stored = 0;
|
|
1901
3731
|
for (const candidate of candidates) {
|
|
1902
3732
|
try {
|
|
1903
|
-
const existing = await
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
const parsed = JSON.parse(existing);
|
|
1910
|
-
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].score > 0.85) {
|
|
1911
|
-
log.debug("extractor", "Skipping duplicate: " + candidate.content);
|
|
1912
|
-
continue;
|
|
1913
|
-
}
|
|
1914
|
-
} catch {
|
|
3733
|
+
const existing = await memoryRecall(candidate.content, { limit: 1 });
|
|
3734
|
+
if (existing.total > 0 && existing.memories.length > 0) {
|
|
3735
|
+
const topScore = existing.memories[0]?.score;
|
|
3736
|
+
if (topScore && topScore > 0.85) {
|
|
3737
|
+
log.debug("extractor", "Skipping duplicate: " + candidate.content);
|
|
3738
|
+
continue;
|
|
1915
3739
|
}
|
|
1916
3740
|
}
|
|
1917
3741
|
} catch {
|
|
1918
3742
|
}
|
|
1919
3743
|
try {
|
|
1920
|
-
await
|
|
3744
|
+
await memoryStore({
|
|
1921
3745
|
content: candidate.content,
|
|
1922
3746
|
type: candidate.type,
|
|
1923
3747
|
tags: candidate.tags,
|
|
@@ -1927,6 +3751,12 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
1927
3751
|
});
|
|
1928
3752
|
stored++;
|
|
1929
3753
|
log.debug("extractor", "Stored " + candidate.type + ": " + candidate.content);
|
|
3754
|
+
if (candidate.type === "pattern" || candidate.type === "preference") {
|
|
3755
|
+
const skillMatch = matchPatternToSkill(candidate.content, candidate.tags);
|
|
3756
|
+
if (skillMatch) {
|
|
3757
|
+
enrichSkill(skillMatch, candidate.content);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
1930
3760
|
} catch (err) {
|
|
1931
3761
|
log.warn("extractor", "Failed to store: " + candidate.content, err);
|
|
1932
3762
|
}
|
|
@@ -1940,6 +3770,147 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
1940
3770
|
}
|
|
1941
3771
|
}
|
|
1942
3772
|
|
|
3773
|
+
// src/background.ts
|
|
3774
|
+
import pc5 from "picocolors";
|
|
3775
|
+
var BACKGROUND_ELIGIBLE = /* @__PURE__ */ new Set([
|
|
3776
|
+
"run_tests",
|
|
3777
|
+
"npm_test",
|
|
3778
|
+
"build",
|
|
3779
|
+
"npm_build",
|
|
3780
|
+
"file_search",
|
|
3781
|
+
"code_search",
|
|
3782
|
+
"grep_search",
|
|
3783
|
+
"git_clone",
|
|
3784
|
+
"docker_build",
|
|
3785
|
+
"docker_run"
|
|
3786
|
+
]);
|
|
3787
|
+
var NEVER_BACKGROUND = /* @__PURE__ */ new Set([
|
|
3788
|
+
"memory_recall",
|
|
3789
|
+
"memory_store",
|
|
3790
|
+
"memory_log",
|
|
3791
|
+
"memory_context",
|
|
3792
|
+
"memory_detail",
|
|
3793
|
+
"identity_read",
|
|
3794
|
+
"identity_summary",
|
|
3795
|
+
"identity_update_session",
|
|
3796
|
+
"identity_update_dynamics",
|
|
3797
|
+
"rules_check",
|
|
3798
|
+
"rules_list",
|
|
3799
|
+
"workflow_list",
|
|
3800
|
+
"workflow_get",
|
|
3801
|
+
"skill_list",
|
|
3802
|
+
"skill_search",
|
|
3803
|
+
"eval_status",
|
|
3804
|
+
"eval_log",
|
|
3805
|
+
"reminder_check",
|
|
3806
|
+
"reminder_set",
|
|
3807
|
+
"file_read",
|
|
3808
|
+
"doc_convert",
|
|
3809
|
+
"file_list",
|
|
3810
|
+
"avatar_prompt"
|
|
3811
|
+
]);
|
|
3812
|
+
function shouldRunInBackground(toolName) {
|
|
3813
|
+
if (NEVER_BACKGROUND.has(toolName)) return false;
|
|
3814
|
+
if (BACKGROUND_ELIGIBLE.has(toolName)) return true;
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
var BackgroundTaskManager = class {
|
|
3818
|
+
tasks = /* @__PURE__ */ new Map();
|
|
3819
|
+
taskCounter = 0;
|
|
3820
|
+
/**
|
|
3821
|
+
* Launch a tool call in the background.
|
|
3822
|
+
*/
|
|
3823
|
+
launch(toolName, toolUseId, mcpManager, toolInput) {
|
|
3824
|
+
const id = `bg-${++this.taskCounter}`;
|
|
3825
|
+
const task = {
|
|
3826
|
+
id,
|
|
3827
|
+
toolName,
|
|
3828
|
+
toolUseId,
|
|
3829
|
+
startedAt: Date.now(),
|
|
3830
|
+
done: false,
|
|
3831
|
+
promise: mcpManager.callTool(toolName, toolInput).then(
|
|
3832
|
+
(result) => {
|
|
3833
|
+
task.result = result;
|
|
3834
|
+
task.done = true;
|
|
3835
|
+
return result;
|
|
3836
|
+
},
|
|
3837
|
+
(error) => {
|
|
3838
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
3839
|
+
task.done = true;
|
|
3840
|
+
return `Error: ${task.error}`;
|
|
3841
|
+
}
|
|
3842
|
+
)
|
|
3843
|
+
};
|
|
3844
|
+
this.tasks.set(id, task);
|
|
3845
|
+
process.stdout.write(pc5.dim(` [${toolName} running in background (${id})...]
|
|
3846
|
+
`));
|
|
3847
|
+
return task;
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Check for completed background tasks and return their results.
|
|
3851
|
+
*/
|
|
3852
|
+
collectCompleted() {
|
|
3853
|
+
const completed = [];
|
|
3854
|
+
for (const [id, task] of this.tasks) {
|
|
3855
|
+
if (task.done) {
|
|
3856
|
+
completed.push(task);
|
|
3857
|
+
this.tasks.delete(id);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
return completed;
|
|
3861
|
+
}
|
|
3862
|
+
/**
|
|
3863
|
+
* Display completed background task results to the user.
|
|
3864
|
+
*/
|
|
3865
|
+
displayCompleted() {
|
|
3866
|
+
const completed = this.collectCompleted();
|
|
3867
|
+
const outputs = [];
|
|
3868
|
+
for (const task of completed) {
|
|
3869
|
+
const elapsed = ((Date.now() - task.startedAt) / 1e3).toFixed(1);
|
|
3870
|
+
if (task.error) {
|
|
3871
|
+
process.stdout.write(pc5.yellow(`
|
|
3872
|
+
[${task.id}] ${task.toolName} failed after ${elapsed}s: ${task.error}
|
|
3873
|
+
`));
|
|
3874
|
+
outputs.push(`[Background task ${task.toolName} failed: ${task.error}]`);
|
|
3875
|
+
} else {
|
|
3876
|
+
process.stdout.write(pc5.green(`
|
|
3877
|
+
[${task.id}] ${task.toolName} completed in ${elapsed}s
|
|
3878
|
+
`));
|
|
3879
|
+
const preview = (task.result || "").slice(0, 200);
|
|
3880
|
+
if (preview) {
|
|
3881
|
+
process.stdout.write(pc5.dim(` ${preview}${(task.result || "").length > 200 ? "..." : ""}
|
|
3882
|
+
`));
|
|
3883
|
+
}
|
|
3884
|
+
outputs.push(`[Background task ${task.toolName} completed: ${task.result}]`);
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
return outputs;
|
|
3888
|
+
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Wait for all pending background tasks to complete.
|
|
3891
|
+
*/
|
|
3892
|
+
async waitAll() {
|
|
3893
|
+
const pending = [...this.tasks.values()].filter((t) => !t.done);
|
|
3894
|
+
if (pending.length === 0) return;
|
|
3895
|
+
process.stdout.write(pc5.dim(`
|
|
3896
|
+
Waiting for ${pending.length} background task(s)...
|
|
3897
|
+
`));
|
|
3898
|
+
await Promise.allSettled(pending.map((t) => t.promise));
|
|
3899
|
+
}
|
|
3900
|
+
/**
|
|
3901
|
+
* Number of currently running tasks.
|
|
3902
|
+
*/
|
|
3903
|
+
get pendingCount() {
|
|
3904
|
+
return [...this.tasks.values()].filter((t) => !t.done).length;
|
|
3905
|
+
}
|
|
3906
|
+
/**
|
|
3907
|
+
* Check if any tasks have completed (non-blocking).
|
|
3908
|
+
*/
|
|
3909
|
+
get hasCompleted() {
|
|
3910
|
+
return [...this.tasks.values()].some((t) => t.done);
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
|
|
1943
3914
|
// src/errors.ts
|
|
1944
3915
|
var ERROR_MAPPINGS = [
|
|
1945
3916
|
{ pattern: /rate.?limit|429/i, message: "Rate limited. I'll retry automatically." },
|
|
@@ -1961,9 +3932,9 @@ function humanizeError(message) {
|
|
|
1961
3932
|
}
|
|
1962
3933
|
|
|
1963
3934
|
// src/hints.ts
|
|
1964
|
-
import
|
|
1965
|
-
import
|
|
1966
|
-
import
|
|
3935
|
+
import fs12 from "fs";
|
|
3936
|
+
import path12 from "path";
|
|
3937
|
+
import os11 from "os";
|
|
1967
3938
|
var HINTS = [
|
|
1968
3939
|
{
|
|
1969
3940
|
id: "eval",
|
|
@@ -2001,11 +3972,11 @@ function getHint(state, ctx) {
|
|
|
2001
3972
|
}
|
|
2002
3973
|
return null;
|
|
2003
3974
|
}
|
|
2004
|
-
var HINTS_FILE =
|
|
3975
|
+
var HINTS_FILE = path12.join(os11.homedir(), ".aman-agent", "hints-seen.json");
|
|
2005
3976
|
function loadShownHints() {
|
|
2006
3977
|
try {
|
|
2007
|
-
if (
|
|
2008
|
-
const data = JSON.parse(
|
|
3978
|
+
if (fs12.existsSync(HINTS_FILE)) {
|
|
3979
|
+
const data = JSON.parse(fs12.readFileSync(HINTS_FILE, "utf-8"));
|
|
2009
3980
|
return new Set(Array.isArray(data) ? data : []);
|
|
2010
3981
|
}
|
|
2011
3982
|
} catch {
|
|
@@ -2014,31 +3985,27 @@ function loadShownHints() {
|
|
|
2014
3985
|
}
|
|
2015
3986
|
function saveShownHints(shown) {
|
|
2016
3987
|
try {
|
|
2017
|
-
const dir =
|
|
2018
|
-
|
|
2019
|
-
|
|
3988
|
+
const dir = path12.dirname(HINTS_FILE);
|
|
3989
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
3990
|
+
fs12.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
|
|
2020
3991
|
} catch {
|
|
2021
3992
|
}
|
|
2022
3993
|
}
|
|
2023
3994
|
|
|
2024
3995
|
// src/agent.ts
|
|
2025
3996
|
marked.use(markedTerminal());
|
|
2026
|
-
async function recallForMessage(input
|
|
3997
|
+
async function recallForMessage(input) {
|
|
2027
3998
|
try {
|
|
2028
|
-
const result = await
|
|
2029
|
-
|
|
2030
|
-
limit: 5,
|
|
2031
|
-
compact: true
|
|
2032
|
-
});
|
|
2033
|
-
if (!result || result.startsWith("Error") || result.includes("No memories found")) {
|
|
3999
|
+
const result = await memoryRecall(input, { limit: 5, compact: true });
|
|
4000
|
+
if (result.total === 0) {
|
|
2034
4001
|
return null;
|
|
2035
4002
|
}
|
|
2036
|
-
const tokenEstimate = Math.round(result.split(/\s+/).filter(Boolean).length * 1.3);
|
|
4003
|
+
const tokenEstimate = result.tokenEstimate ?? Math.round(result.text.split(/\s+/).filter(Boolean).length * 1.3);
|
|
2037
4004
|
return {
|
|
2038
4005
|
text: `
|
|
2039
4006
|
|
|
2040
4007
|
<relevant-memories>
|
|
2041
|
-
${result}
|
|
4008
|
+
${result.text}
|
|
2042
4009
|
</relevant-memories>`,
|
|
2043
4010
|
tokenEstimate
|
|
2044
4011
|
};
|
|
@@ -2056,12 +4023,47 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
2056
4023
|
const messages = [];
|
|
2057
4024
|
const sessionId = generateSessionId();
|
|
2058
4025
|
const extractorState = { turnsSinceLastExtraction: 0, lastExtractionCount: 0 };
|
|
4026
|
+
const bgTasks = new BackgroundTaskManager();
|
|
4027
|
+
const profiles = listProfiles();
|
|
4028
|
+
const teams = listTeams();
|
|
4029
|
+
if (tools && (profiles.length > 0 || teams.length > 0)) {
|
|
4030
|
+
const virtualTools = [];
|
|
4031
|
+
if (profiles.length > 0) {
|
|
4032
|
+
virtualTools.push({
|
|
4033
|
+
name: "delegate_task",
|
|
4034
|
+
description: `Delegate a task to a specialist sub-agent with a different profile. Available profiles: ${profiles.map((p3) => `${p3.name} (${p3.personality})`).join(", ")}. IMPORTANT: Always ask the user for permission before delegating.`,
|
|
4035
|
+
input_schema: {
|
|
4036
|
+
type: "object",
|
|
4037
|
+
properties: {
|
|
4038
|
+
profile: { type: "string", description: "Profile name to delegate to" },
|
|
4039
|
+
task: { type: "string", description: "The task description for the sub-agent" }
|
|
4040
|
+
},
|
|
4041
|
+
required: ["profile", "task"]
|
|
4042
|
+
}
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
if (teams.length > 0) {
|
|
4046
|
+
virtualTools.push({
|
|
4047
|
+
name: "team_run",
|
|
4048
|
+
description: `Run a task with a named agent team. Available teams: ${teams.map((t) => `${t.name} (${t.workflow}: ${t.members.map((m) => m.profile).join("\u2192")})`).join(", ")}. IMPORTANT: Always ask the user for permission before running a team.`,
|
|
4049
|
+
input_schema: {
|
|
4050
|
+
type: "object",
|
|
4051
|
+
properties: {
|
|
4052
|
+
team: { type: "string", description: "Team name" },
|
|
4053
|
+
task: { type: "string", description: "The task for the team" }
|
|
4054
|
+
},
|
|
4055
|
+
required: ["team", "task"]
|
|
4056
|
+
}
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
tools = [...tools, ...virtualTools];
|
|
4060
|
+
}
|
|
2059
4061
|
const hintState = {
|
|
2060
4062
|
turnCount: 0,
|
|
2061
4063
|
shownHints: loadShownHints(),
|
|
2062
4064
|
hintShownThisSession: false
|
|
2063
4065
|
};
|
|
2064
|
-
const
|
|
4066
|
+
const isRetryable2 = (err) => err.message.includes("Rate limit") || err.message.includes("rate limit") || err.message.includes("ECONNRESET") || err.message.includes("ETIMEDOUT") || err.message.includes("fetch failed");
|
|
2065
4067
|
let responseBuffer = "";
|
|
2066
4068
|
const onChunkHandler = (chunk) => {
|
|
2067
4069
|
if (chunk.type === "text" && chunk.text) {
|
|
@@ -2092,6 +4094,10 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
2092
4094
|
output: process.stdout
|
|
2093
4095
|
});
|
|
2094
4096
|
rl.on("SIGINT", async () => {
|
|
4097
|
+
if (bgTasks.pendingCount > 0) {
|
|
4098
|
+
await bgTasks.waitAll();
|
|
4099
|
+
bgTasks.displayCompleted();
|
|
4100
|
+
}
|
|
2095
4101
|
if (mcpManager && hooksConfig) {
|
|
2096
4102
|
try {
|
|
2097
4103
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
@@ -2100,20 +4106,20 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
2100
4106
|
log.debug("agent", "session end hook failed on SIGINT", err);
|
|
2101
4107
|
}
|
|
2102
4108
|
}
|
|
2103
|
-
console.log(
|
|
4109
|
+
console.log(pc6.dim("\nGoodbye.\n"));
|
|
2104
4110
|
rl.close();
|
|
2105
4111
|
process.exit(0);
|
|
2106
4112
|
});
|
|
2107
4113
|
const prompt = () => {
|
|
2108
4114
|
return new Promise((resolve) => {
|
|
2109
|
-
rl.question(
|
|
4115
|
+
rl.question(pc6.green("\nYou > "), (answer) => {
|
|
2110
4116
|
resolve(answer);
|
|
2111
4117
|
});
|
|
2112
4118
|
});
|
|
2113
4119
|
};
|
|
2114
4120
|
console.log(
|
|
2115
4121
|
`
|
|
2116
|
-
Type a message, ${
|
|
4122
|
+
Type a message, ${pc6.dim("/help")} for commands, or ${pc6.dim("/quit")} to exit.
|
|
2117
4123
|
`
|
|
2118
4124
|
);
|
|
2119
4125
|
if (mcpManager && hooksConfig) {
|
|
@@ -2122,14 +4128,14 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2122
4128
|
const session = await onSessionStart(hookCtx);
|
|
2123
4129
|
if (!session.firstRun) {
|
|
2124
4130
|
if (session.resumeTopic) {
|
|
2125
|
-
console.log(
|
|
4131
|
+
console.log(pc6.dim(` Welcome back. Last time we talked about ${session.resumeTopic}`));
|
|
2126
4132
|
} else {
|
|
2127
|
-
console.log(
|
|
4133
|
+
console.log(pc6.dim(" Welcome back."));
|
|
2128
4134
|
}
|
|
2129
4135
|
}
|
|
2130
4136
|
if (session.visibleReminders && session.visibleReminders.length > 0) {
|
|
2131
4137
|
for (const reminder of session.visibleReminders) {
|
|
2132
|
-
console.log(
|
|
4138
|
+
console.log(pc6.yellow(` Reminder: ${reminder}`));
|
|
2133
4139
|
}
|
|
2134
4140
|
}
|
|
2135
4141
|
if (session.contextInjection) {
|
|
@@ -2145,9 +4151,15 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2145
4151
|
}
|
|
2146
4152
|
}
|
|
2147
4153
|
while (true) {
|
|
4154
|
+
if (bgTasks.hasCompleted) {
|
|
4155
|
+
const bgOutputs = bgTasks.displayCompleted();
|
|
4156
|
+
for (const output of bgOutputs) {
|
|
4157
|
+
messages.push({ role: "user", content: output });
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
2148
4160
|
const input = await prompt();
|
|
2149
4161
|
if (!input.trim()) continue;
|
|
2150
|
-
const cmdResult = await handleCommand(input, { model, mcpManager });
|
|
4162
|
+
const cmdResult = await handleCommand(input, { model, mcpManager, llmClient: client, tools });
|
|
2151
4163
|
if (cmdResult.handled) {
|
|
2152
4164
|
if (cmdResult.quit) {
|
|
2153
4165
|
if (mcpManager && hooksConfig) {
|
|
@@ -2158,15 +4170,15 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2158
4170
|
log.debug("agent", "session end hook failed on quit", err);
|
|
2159
4171
|
}
|
|
2160
4172
|
}
|
|
2161
|
-
console.log(
|
|
4173
|
+
console.log(pc6.dim("\nGoodbye.\n"));
|
|
2162
4174
|
rl.close();
|
|
2163
4175
|
return;
|
|
2164
4176
|
}
|
|
2165
4177
|
if (cmdResult.exportConversation) {
|
|
2166
4178
|
try {
|
|
2167
|
-
const exportDir =
|
|
2168
|
-
|
|
2169
|
-
const exportPath =
|
|
4179
|
+
const exportDir = path13.join(os12.homedir(), ".aman-agent", "exports");
|
|
4180
|
+
fs13.mkdirSync(exportDir, { recursive: true });
|
|
4181
|
+
const exportPath = path13.join(exportDir, `${sessionId}.md`);
|
|
2170
4182
|
const lines = [
|
|
2171
4183
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
2172
4184
|
`**Model:** ${model}`,
|
|
@@ -2180,19 +4192,19 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2180
4192
|
lines.push(`${label} ${msg.content}`, "");
|
|
2181
4193
|
}
|
|
2182
4194
|
}
|
|
2183
|
-
|
|
2184
|
-
console.log(
|
|
4195
|
+
fs13.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
4196
|
+
console.log(pc6.green(`Exported to ${exportPath}`));
|
|
2185
4197
|
} catch {
|
|
2186
|
-
console.log(
|
|
4198
|
+
console.log(pc6.red("Failed to export conversation."));
|
|
2187
4199
|
}
|
|
2188
4200
|
continue;
|
|
2189
4201
|
}
|
|
2190
|
-
if (cmdResult.saveConversation
|
|
4202
|
+
if (cmdResult.saveConversation) {
|
|
2191
4203
|
try {
|
|
2192
|
-
await saveConversationToMemory(
|
|
2193
|
-
console.log(
|
|
4204
|
+
await saveConversationToMemory(messages, sessionId);
|
|
4205
|
+
console.log(pc6.green("Conversation saved to memory."));
|
|
2194
4206
|
} catch {
|
|
2195
|
-
console.log(
|
|
4207
|
+
console.log(pc6.red("Failed to save conversation."));
|
|
2196
4208
|
}
|
|
2197
4209
|
continue;
|
|
2198
4210
|
}
|
|
@@ -2211,7 +4223,7 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2211
4223
|
const wfMatch = await onWorkflowMatch(input, hookCtx);
|
|
2212
4224
|
if (wfMatch) {
|
|
2213
4225
|
const useIt = await new Promise((resolve) => {
|
|
2214
|
-
rl.question(
|
|
4226
|
+
rl.question(pc6.dim(` Workflow "${wfMatch.name}" matches. Use it? (y/N) `), (answer) => resolve(answer.toLowerCase() === "y"));
|
|
2215
4227
|
});
|
|
2216
4228
|
if (useIt) {
|
|
2217
4229
|
activeSystemPrompt = systemPrompt + `
|
|
@@ -2219,13 +4231,37 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
2219
4231
|
<active-workflow>
|
|
2220
4232
|
${wfMatch.steps}
|
|
2221
4233
|
</active-workflow>`;
|
|
2222
|
-
console.log(
|
|
4234
|
+
console.log(pc6.dim(` Using "${wfMatch.name}" workflow.`));
|
|
2223
4235
|
}
|
|
2224
4236
|
}
|
|
2225
4237
|
} catch (err) {
|
|
2226
4238
|
log.debug("agent", "workflow match failed", err);
|
|
2227
4239
|
}
|
|
2228
4240
|
}
|
|
4241
|
+
const activePlan = getActivePlan();
|
|
4242
|
+
if (activePlan) {
|
|
4243
|
+
activeSystemPrompt += "\n\n" + formatPlanForPrompt(activePlan);
|
|
4244
|
+
}
|
|
4245
|
+
if (mcpManager) {
|
|
4246
|
+
try {
|
|
4247
|
+
const skillContext = await autoTriggerSkills(input, mcpManager);
|
|
4248
|
+
if (skillContext) {
|
|
4249
|
+
activeSystemPrompt += "\n\n" + skillContext;
|
|
4250
|
+
}
|
|
4251
|
+
} catch (err) {
|
|
4252
|
+
log.debug("agent", "skill auto-trigger failed", err);
|
|
4253
|
+
}
|
|
4254
|
+
const knowledgeItem = matchKnowledge(input);
|
|
4255
|
+
if (knowledgeItem) {
|
|
4256
|
+
activeSystemPrompt += `
|
|
4257
|
+
|
|
4258
|
+
<knowledge name="${knowledgeItem.name}" category="${knowledgeItem.category}">
|
|
4259
|
+
${knowledgeItem.description}
|
|
4260
|
+
|
|
4261
|
+
${knowledgeItem.content}
|
|
4262
|
+
</knowledge>`;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
2229
4265
|
await trimConversation(messages, client);
|
|
2230
4266
|
const textExts = /* @__PURE__ */ new Set([
|
|
2231
4267
|
".txt",
|
|
@@ -2282,33 +4318,33 @@ ${wfMatch.steps}
|
|
|
2282
4318
|
for (const match of filePathMatches) {
|
|
2283
4319
|
let filePath = match[1];
|
|
2284
4320
|
if (filePath.startsWith("~/")) {
|
|
2285
|
-
filePath =
|
|
4321
|
+
filePath = path13.join(os12.homedir(), filePath.slice(2));
|
|
2286
4322
|
}
|
|
2287
|
-
if (!
|
|
2288
|
-
const ext =
|
|
4323
|
+
if (!fs13.existsSync(filePath) || !fs13.statSync(filePath).isFile()) continue;
|
|
4324
|
+
const ext = path13.extname(filePath).toLowerCase();
|
|
2289
4325
|
if (imageExts.has(ext)) {
|
|
2290
4326
|
try {
|
|
2291
|
-
const stat =
|
|
4327
|
+
const stat = fs13.statSync(filePath);
|
|
2292
4328
|
if (stat.size > maxImageBytes) {
|
|
2293
|
-
process.stdout.write(
|
|
4329
|
+
process.stdout.write(pc6.yellow(` [skipped: ${path13.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
2294
4330
|
`));
|
|
2295
4331
|
continue;
|
|
2296
4332
|
}
|
|
2297
|
-
const data =
|
|
4333
|
+
const data = fs13.readFileSync(filePath).toString("base64");
|
|
2298
4334
|
const mediaType = mimeMap[ext] || "image/png";
|
|
2299
4335
|
imageBlocks.push({
|
|
2300
4336
|
type: "image",
|
|
2301
4337
|
source: { type: "base64", media_type: mediaType, data }
|
|
2302
4338
|
});
|
|
2303
|
-
process.stdout.write(
|
|
4339
|
+
process.stdout.write(pc6.dim(` [attached image: ${path13.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
|
|
2304
4340
|
`));
|
|
2305
4341
|
} catch {
|
|
2306
|
-
process.stdout.write(
|
|
4342
|
+
process.stdout.write(pc6.dim(` [could not read image: ${filePath}]
|
|
2307
4343
|
`));
|
|
2308
4344
|
}
|
|
2309
4345
|
} else if (textExts.has(ext) || ext === "") {
|
|
2310
4346
|
try {
|
|
2311
|
-
const content =
|
|
4347
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
2312
4348
|
const maxChars = 5e4;
|
|
2313
4349
|
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
2314
4350
|
|
|
@@ -2318,16 +4354,16 @@ ${wfMatch.steps}
|
|
|
2318
4354
|
<file path="${filePath}" size="${content.length} chars">
|
|
2319
4355
|
${trimmed}
|
|
2320
4356
|
</file>`;
|
|
2321
|
-
process.stdout.write(
|
|
4357
|
+
process.stdout.write(pc6.dim(` [attached: ${path13.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
|
|
2322
4358
|
`));
|
|
2323
4359
|
} catch {
|
|
2324
|
-
process.stdout.write(
|
|
4360
|
+
process.stdout.write(pc6.dim(` [could not read: ${filePath}]
|
|
2325
4361
|
`));
|
|
2326
4362
|
}
|
|
2327
4363
|
} else if (docExts.has(ext)) {
|
|
2328
4364
|
if (mcpManager) {
|
|
2329
4365
|
try {
|
|
2330
|
-
process.stdout.write(
|
|
4366
|
+
process.stdout.write(pc6.dim(` [converting: ${path13.basename(filePath)}...]
|
|
2331
4367
|
`));
|
|
2332
4368
|
const converted = await mcpManager.callTool("doc_convert", { path: filePath });
|
|
2333
4369
|
if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
|
|
@@ -2336,7 +4372,7 @@ ${trimmed}
|
|
|
2336
4372
|
<file path="${filePath}" format="${ext}">
|
|
2337
4373
|
${converted.slice(0, 5e4)}
|
|
2338
4374
|
</file>`;
|
|
2339
|
-
process.stdout.write(
|
|
4375
|
+
process.stdout.write(pc6.dim(` [attached: ${path13.basename(filePath)} (converted from ${ext})]
|
|
2340
4376
|
`));
|
|
2341
4377
|
} else {
|
|
2342
4378
|
textContent += `
|
|
@@ -2344,15 +4380,15 @@ ${converted.slice(0, 5e4)}
|
|
|
2344
4380
|
<file-error path="${filePath}">
|
|
2345
4381
|
${converted}
|
|
2346
4382
|
</file-error>`;
|
|
2347
|
-
process.stdout.write(
|
|
4383
|
+
process.stdout.write(pc6.yellow(` [conversion note: ${converted.split("\n")[0]}]
|
|
2348
4384
|
`));
|
|
2349
4385
|
}
|
|
2350
4386
|
} catch {
|
|
2351
|
-
process.stdout.write(
|
|
4387
|
+
process.stdout.write(pc6.dim(` [could not convert: ${path13.basename(filePath)}]
|
|
2352
4388
|
`));
|
|
2353
4389
|
}
|
|
2354
4390
|
} else {
|
|
2355
|
-
process.stdout.write(
|
|
4391
|
+
process.stdout.write(pc6.yellow(` Binary file (${ext}) \u2014 install Docling for document support: pip install docling
|
|
2356
4392
|
`));
|
|
2357
4393
|
}
|
|
2358
4394
|
}
|
|
@@ -2361,17 +4397,17 @@ ${converted}
|
|
|
2361
4397
|
for (const match of urlImageMatches) {
|
|
2362
4398
|
const url = match[0];
|
|
2363
4399
|
try {
|
|
2364
|
-
process.stdout.write(
|
|
4400
|
+
process.stdout.write(pc6.dim(` [fetching image: ${url.slice(0, 60)}...]
|
|
2365
4401
|
`));
|
|
2366
4402
|
const response = await fetch(url);
|
|
2367
4403
|
if (!response.ok) {
|
|
2368
|
-
process.stdout.write(
|
|
4404
|
+
process.stdout.write(pc6.yellow(` [could not fetch: HTTP ${response.status}]
|
|
2369
4405
|
`));
|
|
2370
4406
|
continue;
|
|
2371
4407
|
}
|
|
2372
4408
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
2373
4409
|
if (buffer.length > maxImageBytes) {
|
|
2374
|
-
process.stdout.write(
|
|
4410
|
+
process.stdout.write(pc6.yellow(` [skipped: image URL exceeds 20MB limit]
|
|
2375
4411
|
`));
|
|
2376
4412
|
continue;
|
|
2377
4413
|
}
|
|
@@ -2385,10 +4421,10 @@ ${converted}
|
|
|
2385
4421
|
type: "image",
|
|
2386
4422
|
source: { type: "base64", media_type: mediaType, data: buffer.toString("base64") }
|
|
2387
4423
|
});
|
|
2388
|
-
process.stdout.write(
|
|
4424
|
+
process.stdout.write(pc6.dim(` [attached image URL: (${(buffer.length / 1024).toFixed(1)}KB)]
|
|
2389
4425
|
`));
|
|
2390
4426
|
} catch {
|
|
2391
|
-
process.stdout.write(
|
|
4427
|
+
process.stdout.write(pc6.dim(` [could not fetch image: ${url}]
|
|
2392
4428
|
`));
|
|
2393
4429
|
}
|
|
2394
4430
|
}
|
|
@@ -2403,22 +4439,46 @@ ${converted}
|
|
|
2403
4439
|
}
|
|
2404
4440
|
let augmentedSystemPrompt = activeSystemPrompt;
|
|
2405
4441
|
let memoryTokens = 0;
|
|
2406
|
-
|
|
2407
|
-
const
|
|
2408
|
-
if (
|
|
2409
|
-
augmentedSystemPrompt = activeSystemPrompt +
|
|
2410
|
-
memoryTokens =
|
|
4442
|
+
{
|
|
4443
|
+
const recall2 = await recallForMessage(input);
|
|
4444
|
+
if (recall2) {
|
|
4445
|
+
augmentedSystemPrompt = activeSystemPrompt + recall2.text;
|
|
4446
|
+
memoryTokens = recall2.tokenEstimate;
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
const userTurnCount = messages.filter((m) => m.role === "user").length;
|
|
4450
|
+
if (mcpManager && hooksConfig?.personalityAdapt !== false && userTurnCount > 0 && userTurnCount % 5 === 0) {
|
|
4451
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
4452
|
+
let period;
|
|
4453
|
+
if (hour < 6) period = "late-night";
|
|
4454
|
+
else if (hour < 12) period = "morning";
|
|
4455
|
+
else if (hour < 17) period = "afternoon";
|
|
4456
|
+
else if (hour < 21) period = "evening";
|
|
4457
|
+
else period = "night";
|
|
4458
|
+
const recentUserMsgs = messages.filter((m) => m.role === "user" && typeof m.content === "string").slice(-5).map((m) => m.content);
|
|
4459
|
+
const sessionMinutes = Math.round((Date.now() - getSessionStartTime()) / 6e4);
|
|
4460
|
+
const state = computePersonality({
|
|
4461
|
+
timePeriod: period,
|
|
4462
|
+
sessionMinutes,
|
|
4463
|
+
turnCount: userTurnCount,
|
|
4464
|
+
recentMessages: recentUserMsgs
|
|
4465
|
+
});
|
|
4466
|
+
syncPersonalityToCore(state, mcpManager).catch(() => {
|
|
4467
|
+
});
|
|
4468
|
+
const nudge = formatWellbeingNudge(state);
|
|
4469
|
+
if (nudge) {
|
|
4470
|
+
augmentedSystemPrompt += "\n" + nudge;
|
|
2411
4471
|
}
|
|
2412
4472
|
}
|
|
2413
4473
|
const divider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - aiName.length - 2);
|
|
2414
4474
|
process.stdout.write(`
|
|
2415
|
-
${
|
|
4475
|
+
${pc6.cyan(pc6.bold(aiName))} ${pc6.dim(divider)}
|
|
2416
4476
|
|
|
2417
4477
|
`);
|
|
2418
4478
|
try {
|
|
2419
4479
|
let response = await withRetry(
|
|
2420
4480
|
() => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
|
|
2421
|
-
{ maxAttempts: 3, baseDelay: 1e3, retryable:
|
|
4481
|
+
{ maxAttempts: 3, baseDelay: 1e3, retryable: isRetryable2 }
|
|
2422
4482
|
);
|
|
2423
4483
|
messages.push(response.message);
|
|
2424
4484
|
while (response.toolUses.length > 0 && mcpManager) {
|
|
@@ -2428,7 +4488,7 @@ ${converted}
|
|
|
2428
4488
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
2429
4489
|
const check = await onBeforeToolExec(toolUse.name, toolUse.input, hookCtx);
|
|
2430
4490
|
if (!check.allow) {
|
|
2431
|
-
process.stdout.write(
|
|
4491
|
+
process.stdout.write(pc6.red(` [BLOCKED: ${check.reason}]
|
|
2432
4492
|
`));
|
|
2433
4493
|
return {
|
|
2434
4494
|
type: "tool_result",
|
|
@@ -2438,17 +4498,57 @@ ${converted}
|
|
|
2438
4498
|
};
|
|
2439
4499
|
}
|
|
2440
4500
|
}
|
|
2441
|
-
|
|
4501
|
+
if (toolUse.name === "delegate_task" && mcpManager) {
|
|
4502
|
+
const input2 = toolUse.input;
|
|
4503
|
+
process.stdout.write(pc6.dim(`
|
|
4504
|
+
[delegating to ${input2.profile}...]
|
|
4505
|
+
|
|
4506
|
+
`));
|
|
4507
|
+
const result2 = await delegateTask(input2.task, input2.profile, client, mcpManager, { tools });
|
|
4508
|
+
const output = result2.success ? `[${input2.profile}] completed:
|
|
4509
|
+
|
|
4510
|
+
${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
4511
|
+
return {
|
|
4512
|
+
type: "tool_result",
|
|
4513
|
+
tool_use_id: toolUse.id,
|
|
4514
|
+
content: output
|
|
4515
|
+
};
|
|
4516
|
+
}
|
|
4517
|
+
if (toolUse.name === "team_run" && mcpManager) {
|
|
4518
|
+
const input2 = toolUse.input;
|
|
4519
|
+
const team = loadTeam(input2.team);
|
|
4520
|
+
if (!team) {
|
|
4521
|
+
return {
|
|
4522
|
+
type: "tool_result",
|
|
4523
|
+
tool_use_id: toolUse.id,
|
|
4524
|
+
content: `Team not found: ${input2.team}`,
|
|
4525
|
+
is_error: true
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
const result2 = await runTeam(team, input2.task, client, mcpManager, tools);
|
|
4529
|
+
return {
|
|
4530
|
+
type: "tool_result",
|
|
4531
|
+
tool_use_id: toolUse.id,
|
|
4532
|
+
content: result2.success ? formatTeamResult(result2) : `Team execution failed: ${result2.finalOutput}`
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
if (shouldRunInBackground(toolUse.name)) {
|
|
4536
|
+
const task = bgTasks.launch(toolUse.name, toolUse.id, mcpManager, toolUse.input);
|
|
4537
|
+
return {
|
|
4538
|
+
type: "tool_result",
|
|
4539
|
+
tool_use_id: toolUse.id,
|
|
4540
|
+
content: `[${toolUse.name} launched in background (${task.id}). Results will appear when ready. Continue with other work.]`
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
process.stdout.write(pc6.dim(` [using ${toolUse.name}...]
|
|
2442
4544
|
`));
|
|
2443
4545
|
const result = await mcpManager.callTool(toolUse.name, toolUse.input);
|
|
2444
4546
|
const skipLogging = ["memory_log", "memory_recall", "memory_context", "memory_detail", "reminder_check"].includes(toolUse.name);
|
|
2445
4547
|
if (!skipLogging) {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
}).catch(() => {
|
|
2451
|
-
});
|
|
4548
|
+
try {
|
|
4549
|
+
memoryLog(sessionId, "system", `[tool:${toolUse.name}] input=${JSON.stringify(toolUse.input).slice(0, 500)} result=${result.slice(0, 500)}`);
|
|
4550
|
+
} catch {
|
|
4551
|
+
}
|
|
2452
4552
|
}
|
|
2453
4553
|
return {
|
|
2454
4554
|
type: "tool_result",
|
|
@@ -2463,7 +4563,7 @@ ${converted}
|
|
|
2463
4563
|
});
|
|
2464
4564
|
response = await withRetry(
|
|
2465
4565
|
() => client.chat(augmentedSystemPrompt, messages, onChunkHandler, tools),
|
|
2466
|
-
{ maxAttempts: 3, baseDelay: 1e3, retryable:
|
|
4566
|
+
{ maxAttempts: 3, baseDelay: 1e3, retryable: isRetryable2 }
|
|
2467
4567
|
);
|
|
2468
4568
|
messages.push(response.message);
|
|
2469
4569
|
}
|
|
@@ -2471,20 +4571,19 @@ ${converted}
|
|
|
2471
4571
|
if (memoryTokens > 0) footerParts.push(`memories: ~${memoryTokens} tokens`);
|
|
2472
4572
|
const footer = footerParts.length > 0 ? ` ${footerParts.join(" | ")}` : "";
|
|
2473
4573
|
const footerDivider = "\u2500".repeat(Math.min(process.stdout.columns || 60, 60) - footer.length - 1);
|
|
2474
|
-
process.stdout.write(
|
|
4574
|
+
process.stdout.write(pc6.dim(` ${footerDivider}${footer}
|
|
2475
4575
|
`));
|
|
2476
|
-
if (
|
|
4576
|
+
if (hooksConfig?.extractMemories) {
|
|
2477
4577
|
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("");
|
|
2478
4578
|
if (assistantText) {
|
|
2479
4579
|
const count = await extractMemories(
|
|
2480
4580
|
input,
|
|
2481
4581
|
assistantText,
|
|
2482
4582
|
client,
|
|
2483
|
-
mcpManager,
|
|
2484
4583
|
extractorState
|
|
2485
4584
|
);
|
|
2486
4585
|
if (count > 0) {
|
|
2487
|
-
process.stdout.write(
|
|
4586
|
+
process.stdout.write(pc6.dim(` [${count} memory${count > 1 ? "ies" : ""} stored]
|
|
2488
4587
|
`));
|
|
2489
4588
|
}
|
|
2490
4589
|
}
|
|
@@ -2493,11 +4592,11 @@ ${converted}
|
|
|
2493
4592
|
}
|
|
2494
4593
|
if (hooksConfig?.featureHints) {
|
|
2495
4594
|
hintState.turnCount++;
|
|
2496
|
-
const hasWorkflows =
|
|
4595
|
+
const hasWorkflows = fs13.existsSync(path13.join(os12.homedir(), ".aflow", "flow.md"));
|
|
2497
4596
|
const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
|
|
2498
4597
|
const hint = getHint(hintState, { hasWorkflows, memoryCount });
|
|
2499
4598
|
if (hint) {
|
|
2500
|
-
process.stdout.write(
|
|
4599
|
+
process.stdout.write(pc6.dim(` ${hint}
|
|
2501
4600
|
`));
|
|
2502
4601
|
saveShownHints(hintState.shownHints);
|
|
2503
4602
|
}
|
|
@@ -2505,21 +4604,17 @@ ${converted}
|
|
|
2505
4604
|
} catch (error) {
|
|
2506
4605
|
const rawMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
2507
4606
|
const friendly = humanizeError(rawMessage);
|
|
2508
|
-
console.error(
|
|
4607
|
+
console.error(pc6.red(`
|
|
2509
4608
|
${friendly}`));
|
|
2510
4609
|
}
|
|
2511
4610
|
}
|
|
2512
4611
|
}
|
|
2513
|
-
async function saveConversationToMemory(
|
|
4612
|
+
async function saveConversationToMemory(messages, sessionId) {
|
|
2514
4613
|
const recentMessages = messages.slice(-50);
|
|
2515
4614
|
for (const msg of recentMessages) {
|
|
2516
4615
|
if (typeof msg.content !== "string") continue;
|
|
2517
4616
|
try {
|
|
2518
|
-
|
|
2519
|
-
session_id: sessionId,
|
|
2520
|
-
role: msg.role,
|
|
2521
|
-
content: msg.content.slice(0, 5e3)
|
|
2522
|
-
});
|
|
4617
|
+
memoryLog(sessionId, msg.role, msg.content.slice(0, 5e3));
|
|
2523
4618
|
} catch (err) {
|
|
2524
4619
|
log.debug("agent", "memory_log write failed", err);
|
|
2525
4620
|
}
|
|
@@ -2527,9 +4622,9 @@ async function saveConversationToMemory(mcpManager, messages, sessionId) {
|
|
|
2527
4622
|
}
|
|
2528
4623
|
|
|
2529
4624
|
// src/index.ts
|
|
2530
|
-
import
|
|
2531
|
-
import
|
|
2532
|
-
import
|
|
4625
|
+
import fs14 from "fs";
|
|
4626
|
+
import path14 from "path";
|
|
4627
|
+
import os13 from "os";
|
|
2533
4628
|
|
|
2534
4629
|
// src/presets.ts
|
|
2535
4630
|
var PRESETS = {
|
|
@@ -2638,9 +4733,9 @@ ${wfSections}`;
|
|
|
2638
4733
|
|
|
2639
4734
|
// src/index.ts
|
|
2640
4735
|
async function autoDetectConfig() {
|
|
2641
|
-
const reconfigMarker =
|
|
2642
|
-
if (
|
|
2643
|
-
|
|
4736
|
+
const reconfigMarker = path14.join(os13.homedir(), ".aman-agent", ".reconfig");
|
|
4737
|
+
if (fs14.existsSync(reconfigMarker)) {
|
|
4738
|
+
fs14.unlinkSync(reconfigMarker);
|
|
2644
4739
|
return null;
|
|
2645
4740
|
}
|
|
2646
4741
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -2669,11 +4764,11 @@ async function autoDetectConfig() {
|
|
|
2669
4764
|
return null;
|
|
2670
4765
|
}
|
|
2671
4766
|
function bootstrapEcosystem() {
|
|
2672
|
-
const home2 =
|
|
2673
|
-
const corePath =
|
|
2674
|
-
if (
|
|
2675
|
-
|
|
2676
|
-
|
|
4767
|
+
const home2 = os13.homedir();
|
|
4768
|
+
const corePath = path14.join(home2, ".acore", "core.md");
|
|
4769
|
+
if (fs14.existsSync(corePath)) return false;
|
|
4770
|
+
fs14.mkdirSync(path14.join(home2, ".acore"), { recursive: true });
|
|
4771
|
+
fs14.writeFileSync(corePath, [
|
|
2677
4772
|
"# Aman",
|
|
2678
4773
|
"",
|
|
2679
4774
|
"## Personality",
|
|
@@ -2685,11 +4780,11 @@ function bootstrapEcosystem() {
|
|
|
2685
4780
|
"## Session",
|
|
2686
4781
|
"_New companion \u2014 no prior sessions._"
|
|
2687
4782
|
].join("\n"), "utf-8");
|
|
2688
|
-
const rulesDir =
|
|
2689
|
-
const rulesPath =
|
|
2690
|
-
if (!
|
|
2691
|
-
|
|
2692
|
-
|
|
4783
|
+
const rulesDir = path14.join(home2, ".arules");
|
|
4784
|
+
const rulesPath = path14.join(rulesDir, "rules.md");
|
|
4785
|
+
if (!fs14.existsSync(rulesPath)) {
|
|
4786
|
+
fs14.mkdirSync(rulesDir, { recursive: true });
|
|
4787
|
+
fs14.writeFileSync(rulesPath, [
|
|
2693
4788
|
"# Guardrails",
|
|
2694
4789
|
"",
|
|
2695
4790
|
"## safety",
|
|
@@ -2704,16 +4799,16 @@ function bootstrapEcosystem() {
|
|
|
2704
4799
|
return true;
|
|
2705
4800
|
}
|
|
2706
4801
|
var program = new Command();
|
|
2707
|
-
program.name("aman-agent").description("Your AI companion, running locally").version("0.1.0").option("--model <model>", "Override LLM model").option("--budget <tokens>", "Token budget for system prompt (default: 8000)", parseInt).action(async (options) => {
|
|
2708
|
-
p2.intro(
|
|
4802
|
+
program.name("aman-agent").description("Your AI companion, running locally").version("0.1.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) => {
|
|
4803
|
+
p2.intro(pc7.bold("aman agent") + pc7.dim(" \u2014 your AI companion"));
|
|
2709
4804
|
let config = loadConfig();
|
|
2710
4805
|
if (!config) {
|
|
2711
4806
|
const detected = await autoDetectConfig();
|
|
2712
4807
|
if (detected) {
|
|
2713
4808
|
config = detected;
|
|
2714
4809
|
const providerLabel = detected.provider === "anthropic" ? "Anthropic API key" : detected.provider === "openai" ? "OpenAI API key" : "Ollama";
|
|
2715
|
-
p2.log.success(`Auto-detected ${providerLabel}. Using ${
|
|
2716
|
-
p2.log.info(
|
|
4810
|
+
p2.log.success(`Auto-detected ${providerLabel}. Using ${pc7.bold(detected.model)}.`);
|
|
4811
|
+
p2.log.info(pc7.dim("Change anytime with /reconfig"));
|
|
2717
4812
|
saveConfig(config);
|
|
2718
4813
|
} else {
|
|
2719
4814
|
p2.log.info("First-time setup \u2014 configure your LLM connection.");
|
|
@@ -2744,7 +4839,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2744
4839
|
defaultModel = modelInput || "llama3.2";
|
|
2745
4840
|
} else if (provider === "anthropic") {
|
|
2746
4841
|
p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
|
|
2747
|
-
p2.log.info(
|
|
4842
|
+
p2.log.info(pc7.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
|
|
2748
4843
|
apiKey = await p2.text({
|
|
2749
4844
|
message: "API key (starts with sk-ant-)",
|
|
2750
4845
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
@@ -2810,34 +4905,32 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2810
4905
|
const s = p2.spinner();
|
|
2811
4906
|
s.start("Loading ecosystem");
|
|
2812
4907
|
bootstrapEcosystem();
|
|
4908
|
+
const profile = options.profile || process.env.AMAN_PROFILE || void 0;
|
|
4909
|
+
if (profile) {
|
|
4910
|
+
p2.log.info(`Profile: ${pc7.bold(profile)}`);
|
|
4911
|
+
}
|
|
2813
4912
|
const budget = options.budget || void 0;
|
|
2814
|
-
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
4913
|
+
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget, profile);
|
|
2815
4914
|
s.stop("Ecosystem loaded");
|
|
2816
4915
|
if (layers.length === 0) {
|
|
2817
4916
|
p2.log.warning(
|
|
2818
|
-
"No ecosystem configured. Run " +
|
|
4917
|
+
"No ecosystem configured. Run " + pc7.bold("npx @aman_asmuei/aman") + " first."
|
|
2819
4918
|
);
|
|
2820
4919
|
p2.log.info("Starting with empty system prompt.");
|
|
2821
4920
|
} else {
|
|
2822
4921
|
p2.log.success(
|
|
2823
|
-
`Loaded: ${layers.join(", ")} ${
|
|
4922
|
+
`Loaded: ${layers.join(", ")} ${pc7.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
|
|
2824
4923
|
);
|
|
2825
4924
|
if (truncated.length > 0) {
|
|
2826
|
-
p2.log.warning(`Truncated: ${truncated.join(", ")} ${
|
|
4925
|
+
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc7.dim("(over budget)")}`);
|
|
2827
4926
|
}
|
|
2828
4927
|
}
|
|
2829
|
-
const
|
|
2830
|
-
|
|
2831
|
-
if (fs8.existsSync(corePath)) {
|
|
2832
|
-
const content = fs8.readFileSync(corePath, "utf-8");
|
|
2833
|
-
const match = content.match(/^# (.+)$/m);
|
|
2834
|
-
if (match) aiName = match[1];
|
|
2835
|
-
}
|
|
4928
|
+
const aiName = getProfileAiName(profile);
|
|
4929
|
+
await initMemory();
|
|
2836
4930
|
const mcpManager = new McpManager();
|
|
2837
4931
|
const mcpSpinner = p2.spinner();
|
|
2838
4932
|
mcpSpinner.start("Connecting to MCP servers");
|
|
2839
4933
|
await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
|
|
2840
|
-
await mcpManager.connect("amem", "npx", ["-y", "@aman_asmuei/amem"]);
|
|
2841
4934
|
if (config.mcpServers) {
|
|
2842
4935
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
2843
4936
|
if (name === "aman" || name === "amem") continue;
|
|
@@ -2848,25 +4941,16 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2848
4941
|
mcpSpinner.stop("MCP connected");
|
|
2849
4942
|
if (mcpTools.length > 0) {
|
|
2850
4943
|
p2.log.success(`${mcpTools.length} MCP tools available`);
|
|
2851
|
-
|
|
4944
|
+
{
|
|
2852
4945
|
const memSpinner = p2.spinner();
|
|
2853
4946
|
memSpinner.start("Consolidating memory");
|
|
2854
4947
|
try {
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
p2.log.info(
|
|
2862
|
-
`Memory health: ${report.healthScore ?? "?"}% ` + pc4.dim(`(merged ${report.merged}, pruned ${report.pruned}, promoted ${report.promoted})`)
|
|
2863
|
-
);
|
|
2864
|
-
}
|
|
2865
|
-
} catch {
|
|
2866
|
-
memSpinner.stop("Memory consolidated");
|
|
2867
|
-
}
|
|
2868
|
-
} else {
|
|
2869
|
-
memSpinner.stop("Memory consolidated");
|
|
4948
|
+
const report = memoryConsolidate();
|
|
4949
|
+
memSpinner.stop("Memory consolidated");
|
|
4950
|
+
if (report.merged > 0 || report.pruned > 0 || report.promoted > 0) {
|
|
4951
|
+
p2.log.info(
|
|
4952
|
+
`Memory health: ${report.healthScore ?? "?"}% ` + pc7.dim(`(merged ${report.merged}, pruned ${report.pruned}, promoted ${report.promoted})`)
|
|
4953
|
+
);
|
|
2870
4954
|
}
|
|
2871
4955
|
} catch {
|
|
2872
4956
|
memSpinner.stop("Memory consolidation skipped");
|
|
@@ -2890,7 +4974,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2890
4974
|
} else {
|
|
2891
4975
|
client = createOpenAIClient(config.apiKey, model);
|
|
2892
4976
|
}
|
|
2893
|
-
p2.log.success(`${
|
|
4977
|
+
p2.log.success(`${pc7.bold(aiName)} is ready. Model: ${pc7.dim(model)}`);
|
|
2894
4978
|
await runAgent(
|
|
2895
4979
|
client,
|
|
2896
4980
|
systemPrompt,
|
|
@@ -2903,7 +4987,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
2903
4987
|
await mcpManager.disconnect();
|
|
2904
4988
|
});
|
|
2905
4989
|
program.command("init").description("Set up your AI companion with a guided wizard").action(async () => {
|
|
2906
|
-
p2.intro(
|
|
4990
|
+
p2.intro(pc7.bold("aman agent init") + pc7.dim(" \u2014 set up your companion"));
|
|
2907
4991
|
const name = await p2.text({
|
|
2908
4992
|
message: "What should your companion be called?",
|
|
2909
4993
|
placeholder: "Aman",
|
|
@@ -2923,19 +5007,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
2923
5007
|
});
|
|
2924
5008
|
if (p2.isCancel(preset)) process.exit(0);
|
|
2925
5009
|
const result = applyPreset(preset, name || "Aman");
|
|
2926
|
-
const home2 =
|
|
2927
|
-
|
|
2928
|
-
|
|
5010
|
+
const home2 = os13.homedir();
|
|
5011
|
+
fs14.mkdirSync(path14.join(home2, ".acore"), { recursive: true });
|
|
5012
|
+
fs14.writeFileSync(path14.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
|
|
2929
5013
|
p2.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
|
|
2930
5014
|
if (result.rulesMd) {
|
|
2931
|
-
|
|
2932
|
-
|
|
5015
|
+
fs14.mkdirSync(path14.join(home2, ".arules"), { recursive: true });
|
|
5016
|
+
fs14.writeFileSync(path14.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
|
|
2933
5017
|
const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
|
|
2934
5018
|
p2.log.success(`${ruleCount} rules set`);
|
|
2935
5019
|
}
|
|
2936
5020
|
if (result.flowMd) {
|
|
2937
|
-
|
|
2938
|
-
|
|
5021
|
+
fs14.mkdirSync(path14.join(home2, ".aflow"), { recursive: true });
|
|
5022
|
+
fs14.writeFileSync(path14.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
|
|
2939
5023
|
const wfCount = (result.flowMd.match(/^## /gm) || []).length;
|
|
2940
5024
|
p2.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
|
|
2941
5025
|
}
|
|
@@ -2943,16 +5027,16 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
2943
5027
|
p2.outro("Your companion is ready.");
|
|
2944
5028
|
console.log("");
|
|
2945
5029
|
if (isNpx) {
|
|
2946
|
-
console.log(` ${
|
|
5030
|
+
console.log(` ${pc7.bold("Start chatting:")} npx @aman_asmuei/aman-agent`);
|
|
2947
5031
|
console.log("");
|
|
2948
|
-
console.log(` ${
|
|
2949
|
-
console.log(` ${
|
|
5032
|
+
console.log(` ${pc7.dim("Tip: Install globally to use")} ${pc7.bold("aman-agent")} ${pc7.dim("directly:")}`);
|
|
5033
|
+
console.log(` ${pc7.dim(" npm install -g @aman_asmuei/aman-agent")}`);
|
|
2950
5034
|
} else {
|
|
2951
|
-
console.log(` ${
|
|
5035
|
+
console.log(` ${pc7.bold("Start chatting:")} aman-agent`);
|
|
2952
5036
|
}
|
|
2953
5037
|
console.log("");
|
|
2954
|
-
console.log(` ${
|
|
2955
|
-
console.log(` ${
|
|
5038
|
+
console.log(` ${pc7.dim("Add tools:")} npx @aman_asmuei/akit add github`);
|
|
5039
|
+
console.log(` ${pc7.dim("Browse:")} npx @aman_asmuei/akit search <query>`);
|
|
2956
5040
|
console.log("");
|
|
2957
5041
|
});
|
|
2958
5042
|
program.parse();
|