@aman_asmuei/aman-agent 0.3.1 → 0.4.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 +252 -38
- package/dist/index.js +264 -411
- package/dist/index.js.map +1 -1
- package/package.json +1 -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 pc4 from "picocolors";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
import fs from "fs";
|
|
@@ -172,76 +172,83 @@ function createAnthropicClient(apiKey, model) {
|
|
|
172
172
|
const anthropicMessages = toAnthropicMessages(messages);
|
|
173
173
|
const hasTools = tools && tools.length > 0;
|
|
174
174
|
try {
|
|
175
|
+
let fullText = "";
|
|
176
|
+
const toolUseBlocks = [];
|
|
177
|
+
let currentBlockType = null;
|
|
178
|
+
let currentBlockIndex = -1;
|
|
179
|
+
const createParams = {
|
|
180
|
+
model,
|
|
181
|
+
max_tokens: 8192,
|
|
182
|
+
system: systemPrompt,
|
|
183
|
+
messages: anthropicMessages,
|
|
184
|
+
stream: true
|
|
185
|
+
};
|
|
175
186
|
if (hasTools) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
messages: anthropicMessages,
|
|
181
|
-
tools: tools.map((t) => ({
|
|
182
|
-
name: t.name,
|
|
183
|
-
description: t.description,
|
|
184
|
-
input_schema: t.input_schema
|
|
185
|
-
}))
|
|
186
|
-
});
|
|
187
|
-
const toolUses = response.content.filter(
|
|
188
|
-
(block) => block.type === "tool_use"
|
|
189
|
-
).map((block) => ({
|
|
190
|
-
id: block.id,
|
|
191
|
-
name: block.name,
|
|
192
|
-
input: block.input
|
|
187
|
+
createParams.tools = tools.map((t) => ({
|
|
188
|
+
name: t.name,
|
|
189
|
+
description: t.description,
|
|
190
|
+
input_schema: t.input_schema
|
|
193
191
|
}));
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
id: block.id,
|
|
211
|
-
name: block.name,
|
|
212
|
-
input: block.input
|
|
213
|
-
};
|
|
192
|
+
}
|
|
193
|
+
const stream = await client.messages.create(
|
|
194
|
+
createParams
|
|
195
|
+
);
|
|
196
|
+
for await (const event of stream) {
|
|
197
|
+
if (event.type === "content_block_start") {
|
|
198
|
+
currentBlockIndex = event.index;
|
|
199
|
+
if (event.content_block.type === "text") {
|
|
200
|
+
currentBlockType = "text";
|
|
201
|
+
} else if (event.content_block.type === "tool_use") {
|
|
202
|
+
currentBlockType = "tool_use";
|
|
203
|
+
toolUseBlocks.push({
|
|
204
|
+
id: event.content_block.id,
|
|
205
|
+
name: event.content_block.name,
|
|
206
|
+
inputJson: ""
|
|
207
|
+
});
|
|
214
208
|
}
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
message: {
|
|
218
|
-
role: "assistant",
|
|
219
|
-
content: toolUses.length > 0 ? contentBlocks : textContent
|
|
220
|
-
},
|
|
221
|
-
toolUses
|
|
222
|
-
};
|
|
223
|
-
} else {
|
|
224
|
-
let fullText = "";
|
|
225
|
-
const stream = await client.messages.create({
|
|
226
|
-
model,
|
|
227
|
-
max_tokens: 8192,
|
|
228
|
-
system: systemPrompt,
|
|
229
|
-
messages: anthropicMessages,
|
|
230
|
-
stream: true
|
|
231
|
-
});
|
|
232
|
-
for await (const event of stream) {
|
|
233
|
-
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
209
|
+
} else if (event.type === "content_block_delta") {
|
|
210
|
+
if (currentBlockType === "text" && event.delta.type === "text_delta") {
|
|
234
211
|
const text2 = event.delta.text;
|
|
235
212
|
fullText += text2;
|
|
236
213
|
onChunk({ type: "text", text: text2 });
|
|
214
|
+
} else if (currentBlockType === "tool_use" && event.delta.type === "input_json_delta") {
|
|
215
|
+
const lastTool = toolUseBlocks[toolUseBlocks.length - 1];
|
|
216
|
+
if (lastTool) {
|
|
217
|
+
lastTool.inputJson += event.delta.partial_json;
|
|
218
|
+
}
|
|
237
219
|
}
|
|
220
|
+
} else if (event.type === "content_block_stop") {
|
|
221
|
+
currentBlockType = null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const toolUses = toolUseBlocks.map((block) => ({
|
|
225
|
+
id: block.id,
|
|
226
|
+
name: block.name,
|
|
227
|
+
input: block.inputJson ? JSON.parse(block.inputJson) : {}
|
|
228
|
+
}));
|
|
229
|
+
onChunk({ type: "done" });
|
|
230
|
+
if (toolUses.length > 0) {
|
|
231
|
+
const contentBlocks = [];
|
|
232
|
+
if (fullText) {
|
|
233
|
+
contentBlocks.push({ type: "text", text: fullText });
|
|
234
|
+
}
|
|
235
|
+
for (const tu of toolUses) {
|
|
236
|
+
contentBlocks.push({
|
|
237
|
+
type: "tool_use",
|
|
238
|
+
id: tu.id,
|
|
239
|
+
name: tu.name,
|
|
240
|
+
input: tu.input
|
|
241
|
+
});
|
|
238
242
|
}
|
|
239
|
-
onChunk({ type: "done" });
|
|
240
243
|
return {
|
|
241
|
-
message: { role: "assistant", content:
|
|
242
|
-
toolUses
|
|
244
|
+
message: { role: "assistant", content: contentBlocks },
|
|
245
|
+
toolUses
|
|
243
246
|
};
|
|
244
247
|
}
|
|
248
|
+
return {
|
|
249
|
+
message: { role: "assistant", content: fullText },
|
|
250
|
+
toolUses: []
|
|
251
|
+
};
|
|
245
252
|
} catch (error) {
|
|
246
253
|
if (error instanceof Anthropic.AuthenticationError) {
|
|
247
254
|
throw new Error(
|
|
@@ -316,74 +323,79 @@ function createOpenAIClient(apiKey, model) {
|
|
|
316
323
|
const openaiMessages = toOpenAIMessages(systemPrompt, messages);
|
|
317
324
|
const hasTools = tools && tools.length > 0;
|
|
318
325
|
try {
|
|
326
|
+
let fullText = "";
|
|
327
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
328
|
+
const createParams = {
|
|
329
|
+
model,
|
|
330
|
+
max_tokens: 8192,
|
|
331
|
+
messages: openaiMessages,
|
|
332
|
+
stream: true
|
|
333
|
+
};
|
|
319
334
|
if (hasTools) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
name: t.name,
|
|
328
|
-
description: t.description,
|
|
329
|
-
parameters: t.input_schema
|
|
330
|
-
}
|
|
331
|
-
}))
|
|
332
|
-
});
|
|
333
|
-
const choice = response.choices[0];
|
|
334
|
-
const textContent = choice?.message?.content || "";
|
|
335
|
-
const toolCalls = choice?.message?.tool_calls || [];
|
|
336
|
-
const toolUses = toolCalls.map((tc) => ({
|
|
337
|
-
id: tc.id,
|
|
338
|
-
name: tc.function.name,
|
|
339
|
-
input: JSON.parse(tc.function.arguments || "{}")
|
|
335
|
+
createParams.tools = tools.map((t) => ({
|
|
336
|
+
type: "function",
|
|
337
|
+
function: {
|
|
338
|
+
name: t.name,
|
|
339
|
+
description: t.description,
|
|
340
|
+
parameters: t.input_schema
|
|
341
|
+
}
|
|
340
342
|
}));
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
type: "tool_use",
|
|
352
|
-
id: tu.id,
|
|
353
|
-
name: tu.name,
|
|
354
|
-
input: tu.input
|
|
355
|
-
}))
|
|
356
|
-
];
|
|
357
|
-
return {
|
|
358
|
-
message: { role: "assistant", content: contentBlocks },
|
|
359
|
-
toolUses
|
|
360
|
-
};
|
|
343
|
+
}
|
|
344
|
+
const stream = await client.chat.completions.create(
|
|
345
|
+
createParams
|
|
346
|
+
);
|
|
347
|
+
for await (const chunk of stream) {
|
|
348
|
+
const delta = chunk.choices[0]?.delta;
|
|
349
|
+
if (!delta) continue;
|
|
350
|
+
if (delta.content) {
|
|
351
|
+
fullText += delta.content;
|
|
352
|
+
onChunk({ type: "text", text: delta.content });
|
|
361
353
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
354
|
+
if (delta.tool_calls) {
|
|
355
|
+
for (const tc of delta.tool_calls) {
|
|
356
|
+
const idx = tc.index;
|
|
357
|
+
let acc = toolCallAccumulators.get(idx);
|
|
358
|
+
if (!acc) {
|
|
359
|
+
acc = { id: "", name: "", arguments: "" };
|
|
360
|
+
toolCallAccumulators.set(idx, acc);
|
|
361
|
+
}
|
|
362
|
+
if (tc.id) {
|
|
363
|
+
acc.id = tc.id;
|
|
364
|
+
}
|
|
365
|
+
if (tc.function?.name) {
|
|
366
|
+
acc.name = tc.function.name;
|
|
367
|
+
}
|
|
368
|
+
if (tc.function?.arguments) {
|
|
369
|
+
acc.arguments += tc.function.arguments;
|
|
370
|
+
}
|
|
379
371
|
}
|
|
380
372
|
}
|
|
381
|
-
|
|
373
|
+
}
|
|
374
|
+
const toolUses = Array.from(toolCallAccumulators.entries()).sort(([a], [b]) => a - b).map(([, acc]) => ({
|
|
375
|
+
id: acc.id,
|
|
376
|
+
name: acc.name,
|
|
377
|
+
input: JSON.parse(acc.arguments || "{}")
|
|
378
|
+
}));
|
|
379
|
+
onChunk({ type: "done" });
|
|
380
|
+
if (toolUses.length > 0) {
|
|
381
|
+
const contentBlocks = [
|
|
382
|
+
...fullText ? [{ type: "text", text: fullText }] : [],
|
|
383
|
+
...toolUses.map((tu) => ({
|
|
384
|
+
type: "tool_use",
|
|
385
|
+
id: tu.id,
|
|
386
|
+
name: tu.name,
|
|
387
|
+
input: tu.input
|
|
388
|
+
}))
|
|
389
|
+
];
|
|
382
390
|
return {
|
|
383
|
-
message: { role: "assistant", content:
|
|
384
|
-
toolUses
|
|
391
|
+
message: { role: "assistant", content: contentBlocks },
|
|
392
|
+
toolUses
|
|
385
393
|
};
|
|
386
394
|
}
|
|
395
|
+
return {
|
|
396
|
+
message: { role: "assistant", content: fullText },
|
|
397
|
+
toolUses: []
|
|
398
|
+
};
|
|
387
399
|
} catch (error) {
|
|
388
400
|
if (error instanceof OpenAI.AuthenticationError) {
|
|
389
401
|
throw new Error(
|
|
@@ -821,7 +833,7 @@ function handleHelp() {
|
|
|
821
833
|
` ${pc.cyan("/memory")} View recent memories [search|clear ...]`,
|
|
822
834
|
` ${pc.cyan("/status")} Ecosystem dashboard`,
|
|
823
835
|
` ${pc.cyan("/doctor")} Health check all layers`,
|
|
824
|
-
` ${pc.cyan("/
|
|
836
|
+
` ${pc.cyan("/save")} Save conversation to memory`,
|
|
825
837
|
` ${pc.cyan("/model")} Show current LLM model`,
|
|
826
838
|
` ${pc.cyan("/update")} Check for updates`,
|
|
827
839
|
` ${pc.cyan("/reconfig")} Reset LLM config`,
|
|
@@ -830,17 +842,8 @@ function handleHelp() {
|
|
|
830
842
|
].join("\n")
|
|
831
843
|
};
|
|
832
844
|
}
|
|
833
|
-
function
|
|
834
|
-
|
|
835
|
-
if (parts.length < 3) {
|
|
836
|
-
return {
|
|
837
|
-
handled: true,
|
|
838
|
-
output: "Usage: /remind <time> <message>\nExamples: /remind 30m Review PR, /remind 2h Deploy, /remind tomorrow Check metrics"
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
const timeStr = parts[1];
|
|
842
|
-
const message = parts.slice(2).join(" ");
|
|
843
|
-
return { handled: true, remind: { timeStr, message } };
|
|
845
|
+
function handleSave() {
|
|
846
|
+
return { handled: true, saveConversation: true };
|
|
844
847
|
}
|
|
845
848
|
function handleReconfig() {
|
|
846
849
|
const configPath = path4.join(os4.homedir(), ".aman-agent", "config.json");
|
|
@@ -921,8 +924,8 @@ async function handleCommand(input, ctx) {
|
|
|
921
924
|
return handleStatusCommand(ctx);
|
|
922
925
|
case "doctor":
|
|
923
926
|
return handleDoctorCommand(ctx);
|
|
924
|
-
case "
|
|
925
|
-
return
|
|
927
|
+
case "save":
|
|
928
|
+
return handleSave();
|
|
926
929
|
case "update-config":
|
|
927
930
|
case "reconfig":
|
|
928
931
|
return handleReconfig();
|
|
@@ -934,46 +937,8 @@ async function handleCommand(input, ctx) {
|
|
|
934
937
|
}
|
|
935
938
|
}
|
|
936
939
|
|
|
937
|
-
// src/reminders.ts
|
|
938
|
-
import pc2 from "picocolors";
|
|
939
|
-
var activeReminders = [];
|
|
940
|
-
function parseTime(timeStr) {
|
|
941
|
-
const match = timeStr.match(/^(\d+)(m|h)$/);
|
|
942
|
-
if (match) {
|
|
943
|
-
const value = parseInt(match[1]);
|
|
944
|
-
const unit = match[2];
|
|
945
|
-
return unit === "m" ? value * 60 * 1e3 : value * 60 * 60 * 1e3;
|
|
946
|
-
}
|
|
947
|
-
if (timeStr === "tomorrow") return 24 * 60 * 60 * 1e3;
|
|
948
|
-
return null;
|
|
949
|
-
}
|
|
950
|
-
function setReminder(timeStr, message) {
|
|
951
|
-
const ms = parseTime(timeStr);
|
|
952
|
-
if (!ms) return null;
|
|
953
|
-
const reminder = {
|
|
954
|
-
message,
|
|
955
|
-
dueAt: Date.now() + ms
|
|
956
|
-
};
|
|
957
|
-
reminder.timer = setTimeout(() => {
|
|
958
|
-
console.log(`
|
|
959
|
-
${pc2.yellow("\u23F0")} ${pc2.bold("Reminder:")} ${message}`);
|
|
960
|
-
const idx = activeReminders.indexOf(reminder);
|
|
961
|
-
if (idx >= 0) activeReminders.splice(idx, 1);
|
|
962
|
-
}, ms);
|
|
963
|
-
activeReminders.push(reminder);
|
|
964
|
-
const mins = Math.round(ms / 6e4);
|
|
965
|
-
if (mins < 60) return `${mins} minutes`;
|
|
966
|
-
const hours = Math.round(mins / 60);
|
|
967
|
-
return `${hours} hour${hours > 1 ? "s" : ""}`;
|
|
968
|
-
}
|
|
969
|
-
function clearReminders() {
|
|
970
|
-
for (const r of activeReminders) {
|
|
971
|
-
if (r.timer) clearTimeout(r.timer);
|
|
972
|
-
}
|
|
973
|
-
activeReminders.length = 0;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
940
|
// src/hooks.ts
|
|
941
|
+
import pc2 from "picocolors";
|
|
977
942
|
import * as p from "@clack/prompts";
|
|
978
943
|
var isHookCall = false;
|
|
979
944
|
async function onSessionStart(ctx) {
|
|
@@ -1076,33 +1041,24 @@ async function onWorkflowMatch(userInput, ctx) {
|
|
|
1076
1041
|
isHookCall = false;
|
|
1077
1042
|
}
|
|
1078
1043
|
}
|
|
1079
|
-
async function onSessionEnd(ctx, messages) {
|
|
1044
|
+
async function onSessionEnd(ctx, messages, sessionId) {
|
|
1080
1045
|
try {
|
|
1081
|
-
if (ctx.config.
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
{ value: "great", label: "Great" },
|
|
1086
|
-
{ value: "good", label: "Good" },
|
|
1087
|
-
{ value: "okay", label: "Okay" },
|
|
1088
|
-
{ value: "skip", label: "Skip" }
|
|
1089
|
-
],
|
|
1090
|
-
initialValue: "skip"
|
|
1091
|
-
});
|
|
1092
|
-
if (!p.isCancel(rating) && rating !== "skip") {
|
|
1046
|
+
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
1047
|
+
console.log(pc2.dim("\n Saving conversation to memory..."));
|
|
1048
|
+
const textMessages = messages.filter((m) => typeof m.content === "string").slice(-50);
|
|
1049
|
+
for (const msg of textMessages) {
|
|
1093
1050
|
try {
|
|
1094
1051
|
isHookCall = true;
|
|
1095
|
-
await ctx.mcpManager.callTool("
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1052
|
+
await ctx.mcpManager.callTool("memory_log", {
|
|
1053
|
+
session_id: sessionId,
|
|
1054
|
+
role: msg.role,
|
|
1055
|
+
content: msg.content.slice(0, 5e3)
|
|
1099
1056
|
});
|
|
1057
|
+
} catch {
|
|
1100
1058
|
} finally {
|
|
1101
1059
|
isHookCall = false;
|
|
1102
1060
|
}
|
|
1103
1061
|
}
|
|
1104
|
-
}
|
|
1105
|
-
if (ctx.config.autoSessionSave && messages.length > 2) {
|
|
1106
1062
|
let lastUserMsg = "";
|
|
1107
1063
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1108
1064
|
if (messages[i].role === "user" && typeof messages[i].content === "string") {
|
|
@@ -1122,14 +1078,97 @@ async function onSessionEnd(ctx, messages) {
|
|
|
1122
1078
|
isHookCall = false;
|
|
1123
1079
|
}
|
|
1124
1080
|
}
|
|
1081
|
+
console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
1082
|
+
}
|
|
1083
|
+
if (ctx.config.evalPrompt) {
|
|
1084
|
+
const rating = await p.select({
|
|
1085
|
+
message: "Quick rating for this session?",
|
|
1086
|
+
options: [
|
|
1087
|
+
{ value: "great", label: "Great" },
|
|
1088
|
+
{ value: "good", label: "Good" },
|
|
1089
|
+
{ value: "okay", label: "Okay" },
|
|
1090
|
+
{ value: "skip", label: "Skip" }
|
|
1091
|
+
],
|
|
1092
|
+
initialValue: "skip"
|
|
1093
|
+
});
|
|
1094
|
+
if (!p.isCancel(rating) && rating !== "skip") {
|
|
1095
|
+
try {
|
|
1096
|
+
isHookCall = true;
|
|
1097
|
+
await ctx.mcpManager.callTool("eval_log", {
|
|
1098
|
+
rating,
|
|
1099
|
+
highlights: "Quick session rating",
|
|
1100
|
+
improvements: ""
|
|
1101
|
+
});
|
|
1102
|
+
} finally {
|
|
1103
|
+
isHookCall = false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1125
1106
|
}
|
|
1126
1107
|
} catch {
|
|
1127
1108
|
}
|
|
1128
1109
|
}
|
|
1129
1110
|
|
|
1111
|
+
// src/context-manager.ts
|
|
1112
|
+
function estimateMessageTokens(msg) {
|
|
1113
|
+
if (typeof msg.content === "string") {
|
|
1114
|
+
return Math.round(msg.content.split(/\s+/).filter(Boolean).length * 1.3);
|
|
1115
|
+
}
|
|
1116
|
+
let text2 = "";
|
|
1117
|
+
for (const block of msg.content) {
|
|
1118
|
+
if (block.type === "text") text2 += block.text;
|
|
1119
|
+
else if (block.type === "tool_result") text2 += block.content;
|
|
1120
|
+
else if (block.type === "tool_use") text2 += JSON.stringify(block.input);
|
|
1121
|
+
}
|
|
1122
|
+
return Math.round(text2.split(/\s+/).filter(Boolean).length * 1.3);
|
|
1123
|
+
}
|
|
1124
|
+
function estimateTotalTokens(messages) {
|
|
1125
|
+
let total = 0;
|
|
1126
|
+
for (const msg of messages) {
|
|
1127
|
+
total += estimateMessageTokens(msg);
|
|
1128
|
+
}
|
|
1129
|
+
return total;
|
|
1130
|
+
}
|
|
1131
|
+
var MAX_CONVERSATION_TOKENS = 8e4;
|
|
1132
|
+
var KEEP_RECENT = 10;
|
|
1133
|
+
var KEEP_INITIAL = 2;
|
|
1134
|
+
function trimConversation(messages, _client) {
|
|
1135
|
+
const totalTokens = estimateTotalTokens(messages);
|
|
1136
|
+
if (totalTokens < MAX_CONVERSATION_TOKENS || messages.length <= KEEP_INITIAL + KEEP_RECENT) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const initial = messages.slice(0, KEEP_INITIAL);
|
|
1140
|
+
const recent = messages.slice(-KEEP_RECENT);
|
|
1141
|
+
const middle = messages.slice(KEEP_INITIAL, messages.length - KEEP_RECENT);
|
|
1142
|
+
const summaryParts = [];
|
|
1143
|
+
for (const msg of middle) {
|
|
1144
|
+
if (typeof msg.content === "string" && msg.content.length > 0) {
|
|
1145
|
+
const preview = msg.content.slice(0, 150);
|
|
1146
|
+
summaryParts.push(`[${msg.role}]: ${preview}${msg.content.length > 150 ? "..." : ""}`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const summaryText = `<conversation-summary>
|
|
1150
|
+
The following is a summary of ${middle.length} earlier messages that were compressed to save context:
|
|
1151
|
+
|
|
1152
|
+
${summaryParts.slice(0, 20).join("\n")}
|
|
1153
|
+
${summaryParts.length > 20 ? `
|
|
1154
|
+
... and ${summaryParts.length - 20} more messages` : ""}
|
|
1155
|
+
</conversation-summary>`;
|
|
1156
|
+
messages.length = 0;
|
|
1157
|
+
messages.push(...initial);
|
|
1158
|
+
messages.push({ role: "user", content: summaryText });
|
|
1159
|
+
messages.push({ role: "assistant", content: "I have the context from our earlier conversation. Let's continue." });
|
|
1160
|
+
messages.push(...recent);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1130
1163
|
// src/agent.ts
|
|
1164
|
+
function generateSessionId() {
|
|
1165
|
+
const now = /* @__PURE__ */ new Date();
|
|
1166
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1167
|
+
return `session-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
1168
|
+
}
|
|
1131
1169
|
async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager, hooksConfig) {
|
|
1132
1170
|
const messages = [];
|
|
1171
|
+
const sessionId = generateSessionId();
|
|
1133
1172
|
const rl = readline.createInterface({
|
|
1134
1173
|
input: process.stdin,
|
|
1135
1174
|
output: process.stdout
|
|
@@ -1138,7 +1177,7 @@ async function runAgent(client, systemPrompt, aiName, model, tools, mcpManager,
|
|
|
1138
1177
|
if (mcpManager && hooksConfig) {
|
|
1139
1178
|
try {
|
|
1140
1179
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1141
|
-
await onSessionEnd(hookCtx, messages);
|
|
1180
|
+
await onSessionEnd(hookCtx, messages, sessionId);
|
|
1142
1181
|
} catch {
|
|
1143
1182
|
}
|
|
1144
1183
|
}
|
|
@@ -1176,11 +1215,10 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1176
1215
|
const cmdResult = await handleCommand(input, { model, mcpManager });
|
|
1177
1216
|
if (cmdResult.handled) {
|
|
1178
1217
|
if (cmdResult.quit) {
|
|
1179
|
-
clearReminders();
|
|
1180
1218
|
if (mcpManager && hooksConfig) {
|
|
1181
1219
|
try {
|
|
1182
1220
|
const hookCtx = { mcpManager, config: hooksConfig };
|
|
1183
|
-
await onSessionEnd(hookCtx, messages);
|
|
1221
|
+
await onSessionEnd(hookCtx, messages, sessionId);
|
|
1184
1222
|
} catch {
|
|
1185
1223
|
}
|
|
1186
1224
|
}
|
|
@@ -1188,17 +1226,12 @@ Type a message, ${pc3.dim("/help")} for commands, or ${pc3.dim("/quit")} to exit
|
|
|
1188
1226
|
rl.close();
|
|
1189
1227
|
return;
|
|
1190
1228
|
}
|
|
1191
|
-
if (cmdResult.
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
console.log(pc3.dim(`Reminder set for ${duration} from now.`));
|
|
1198
|
-
} else {
|
|
1199
|
-
console.log(
|
|
1200
|
-
pc3.red("Invalid time format. Use: 5m, 30m, 1h, 2h, tomorrow")
|
|
1201
|
-
);
|
|
1229
|
+
if (cmdResult.saveConversation && mcpManager) {
|
|
1230
|
+
try {
|
|
1231
|
+
await saveConversationToMemory(mcpManager, messages, sessionId);
|
|
1232
|
+
console.log(pc3.green("Conversation saved to memory."));
|
|
1233
|
+
} catch {
|
|
1234
|
+
console.log(pc3.red("Failed to save conversation."));
|
|
1202
1235
|
}
|
|
1203
1236
|
continue;
|
|
1204
1237
|
}
|
|
@@ -1231,6 +1264,7 @@ ${wfMatch.steps}
|
|
|
1231
1264
|
} catch {
|
|
1232
1265
|
}
|
|
1233
1266
|
}
|
|
1267
|
+
trimConversation(messages, client);
|
|
1234
1268
|
messages.push({ role: "user", content: input });
|
|
1235
1269
|
process.stdout.write(pc3.cyan(`
|
|
1236
1270
|
${aiName} > `));
|
|
@@ -1308,127 +1342,28 @@ Error: ${message}`));
|
|
|
1308
1342
|
}
|
|
1309
1343
|
}
|
|
1310
1344
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return [];
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
function saveSchedules(tasks) {
|
|
1326
|
-
const dir = path5.dirname(SCHEDULES_PATH);
|
|
1327
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
1328
|
-
fs5.writeFileSync(
|
|
1329
|
-
SCHEDULES_PATH,
|
|
1330
|
-
JSON.stringify(tasks, null, 2) + "\n",
|
|
1331
|
-
"utf-8"
|
|
1332
|
-
);
|
|
1333
|
-
}
|
|
1334
|
-
function addSchedule(task) {
|
|
1335
|
-
const tasks = loadSchedules();
|
|
1336
|
-
const newTask = {
|
|
1337
|
-
...task,
|
|
1338
|
-
id: Date.now().toString(36),
|
|
1339
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1340
|
-
};
|
|
1341
|
-
tasks.push(newTask);
|
|
1342
|
-
saveSchedules(tasks);
|
|
1343
|
-
return newTask;
|
|
1344
|
-
}
|
|
1345
|
-
function removeSchedule(id) {
|
|
1346
|
-
const tasks = loadSchedules();
|
|
1347
|
-
const filtered = tasks.filter((t) => t.id !== id);
|
|
1348
|
-
if (filtered.length === tasks.length) return false;
|
|
1349
|
-
saveSchedules(filtered);
|
|
1350
|
-
return true;
|
|
1351
|
-
}
|
|
1352
|
-
function getDueTasks() {
|
|
1353
|
-
const tasks = loadSchedules();
|
|
1354
|
-
const now = /* @__PURE__ */ new Date();
|
|
1355
|
-
return tasks.filter((task) => {
|
|
1356
|
-
if (!task.lastRun) return true;
|
|
1357
|
-
const lastRun = new Date(task.lastRun);
|
|
1358
|
-
return isDue(task.schedule, lastRun, now);
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
function isDue(schedule, lastRun, now) {
|
|
1362
|
-
const hoursSinceLastRun = (now.getTime() - lastRun.getTime()) / (1e3 * 60 * 60);
|
|
1363
|
-
if (schedule.startsWith("every ")) {
|
|
1364
|
-
const match = schedule.match(/every (\d+)h/);
|
|
1365
|
-
if (match) return hoursSinceLastRun >= parseInt(match[1]);
|
|
1366
|
-
}
|
|
1367
|
-
if (schedule === "daily" || schedule.startsWith("daily ")) {
|
|
1368
|
-
return hoursSinceLastRun >= 20;
|
|
1369
|
-
}
|
|
1370
|
-
if (schedule === "weekdays" || schedule.startsWith("weekdays ")) {
|
|
1371
|
-
const day = now.getDay();
|
|
1372
|
-
return day >= 1 && day <= 5 && hoursSinceLastRun >= 20;
|
|
1373
|
-
}
|
|
1374
|
-
if (schedule.startsWith("weekly")) {
|
|
1375
|
-
return hoursSinceLastRun >= 144;
|
|
1376
|
-
}
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
// src/notifications.ts
|
|
1381
|
-
import fs6 from "fs";
|
|
1382
|
-
import path6 from "path";
|
|
1383
|
-
import os6 from "os";
|
|
1384
|
-
import pc4 from "picocolors";
|
|
1385
|
-
function checkNotifications() {
|
|
1386
|
-
const notifications = [];
|
|
1387
|
-
const dueTasks = getDueTasks();
|
|
1388
|
-
for (const task of dueTasks) {
|
|
1389
|
-
notifications.push({
|
|
1390
|
-
type: "schedule",
|
|
1391
|
-
message: `${task.name} (${task.schedule})`
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
const evalPath = path6.join(os6.homedir(), ".aeval", "eval.md");
|
|
1395
|
-
if (fs6.existsSync(evalPath)) {
|
|
1396
|
-
const content = fs6.readFileSync(evalPath, "utf-8");
|
|
1397
|
-
const dateMatch = content.match(/- Last updated: (.+)$/m);
|
|
1398
|
-
if (dateMatch) {
|
|
1399
|
-
const lastDate = new Date(dateMatch[1]);
|
|
1400
|
-
const daysSince = (Date.now() - lastDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
1401
|
-
if (daysSince > 3) {
|
|
1402
|
-
notifications.push({
|
|
1403
|
-
type: "eval",
|
|
1404
|
-
message: `No session logged in ${Math.floor(daysSince)} days \u2014 run /eval to log one`
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1345
|
+
async function saveConversationToMemory(mcpManager, messages, sessionId) {
|
|
1346
|
+
const recentMessages = messages.slice(-50);
|
|
1347
|
+
for (const msg of recentMessages) {
|
|
1348
|
+
if (typeof msg.content !== "string") continue;
|
|
1349
|
+
try {
|
|
1350
|
+
await mcpManager.callTool("memory_log", {
|
|
1351
|
+
session_id: sessionId,
|
|
1352
|
+
role: msg.role,
|
|
1353
|
+
content: msg.content.slice(0, 5e3)
|
|
1354
|
+
});
|
|
1355
|
+
} catch {
|
|
1407
1356
|
}
|
|
1408
1357
|
}
|
|
1409
|
-
return notifications;
|
|
1410
|
-
}
|
|
1411
|
-
function displayNotifications(notifications) {
|
|
1412
|
-
if (notifications.length === 0) return;
|
|
1413
|
-
console.log(
|
|
1414
|
-
pc4.yellow(
|
|
1415
|
-
`
|
|
1416
|
-
\u26A0 ${notifications.length} notification${notifications.length > 1 ? "s" : ""}:`
|
|
1417
|
-
)
|
|
1418
|
-
);
|
|
1419
|
-
for (const n of notifications) {
|
|
1420
|
-
console.log(` - ${n.message}`);
|
|
1421
|
-
}
|
|
1422
|
-
console.log("");
|
|
1423
1358
|
}
|
|
1424
1359
|
|
|
1425
1360
|
// src/index.ts
|
|
1426
|
-
import
|
|
1427
|
-
import
|
|
1428
|
-
import
|
|
1361
|
+
import fs5 from "fs";
|
|
1362
|
+
import path5 from "path";
|
|
1363
|
+
import os5 from "os";
|
|
1429
1364
|
var program = new Command();
|
|
1430
1365
|
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) => {
|
|
1431
|
-
p2.intro(
|
|
1366
|
+
p2.intro(pc4.bold("aman agent") + pc4.dim(" \u2014 starting your AI companion"));
|
|
1432
1367
|
let config = loadConfig();
|
|
1433
1368
|
if (!config) {
|
|
1434
1369
|
p2.log.info("First-time setup \u2014 configure your LLM connection.");
|
|
@@ -1459,7 +1394,7 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1459
1394
|
defaultModel = modelInput || "llama3.2";
|
|
1460
1395
|
} else if (provider === "anthropic") {
|
|
1461
1396
|
p2.log.info("Get your API key from: https://console.anthropic.com/settings/keys");
|
|
1462
|
-
p2.log.info(
|
|
1397
|
+
p2.log.info(pc4.dim("Note: API access is separate from Claude Pro subscription. You need API credits."));
|
|
1463
1398
|
apiKey = await p2.text({
|
|
1464
1399
|
message: "API key (starts with sk-ant-)",
|
|
1465
1400
|
validate: (v) => v.length === 0 ? "API key is required" : void 0
|
|
@@ -1525,28 +1460,26 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1525
1460
|
const { prompt: systemPrompt, layers, truncated, totalTokens } = assembleSystemPrompt(budget);
|
|
1526
1461
|
if (layers.length === 0) {
|
|
1527
1462
|
p2.log.warning(
|
|
1528
|
-
"No ecosystem configured. Run " +
|
|
1463
|
+
"No ecosystem configured. Run " + pc4.bold("npx @aman_asmuei/aman") + " first."
|
|
1529
1464
|
);
|
|
1530
1465
|
p2.log.info("Starting with empty system prompt.");
|
|
1531
1466
|
} else {
|
|
1532
1467
|
p2.log.success(
|
|
1533
|
-
`Loaded: ${layers.join(", ")} ${
|
|
1468
|
+
`Loaded: ${layers.join(", ")} ${pc4.dim(`(${totalTokens.toLocaleString()} tokens)`)}`
|
|
1534
1469
|
);
|
|
1535
1470
|
if (truncated.length > 0) {
|
|
1536
|
-
p2.log.warning(`Truncated: ${truncated.join(", ")} ${
|
|
1471
|
+
p2.log.warning(`Truncated: ${truncated.join(", ")} ${pc4.dim("(over budget)")}`);
|
|
1537
1472
|
}
|
|
1538
1473
|
}
|
|
1539
|
-
p2.log.info(`Model: ${
|
|
1540
|
-
const corePath =
|
|
1474
|
+
p2.log.info(`Model: ${pc4.dim(model)}`);
|
|
1475
|
+
const corePath = path5.join(os5.homedir(), ".acore", "core.md");
|
|
1541
1476
|
let aiName = "Assistant";
|
|
1542
|
-
if (
|
|
1543
|
-
const content =
|
|
1477
|
+
if (fs5.existsSync(corePath)) {
|
|
1478
|
+
const content = fs5.readFileSync(corePath, "utf-8");
|
|
1544
1479
|
const match = content.match(/^# (.+)$/m);
|
|
1545
1480
|
if (match) aiName = match[1];
|
|
1546
1481
|
}
|
|
1547
|
-
p2.log.success(`${
|
|
1548
|
-
const notifications = checkNotifications();
|
|
1549
|
-
displayNotifications(notifications);
|
|
1482
|
+
p2.log.success(`${pc4.bold(aiName)} is ready.`);
|
|
1550
1483
|
const mcpManager = new McpManager();
|
|
1551
1484
|
p2.log.step("Connecting to MCP servers...");
|
|
1552
1485
|
await mcpManager.connect("aman", "npx", ["-y", "@aman_asmuei/aman-mcp"]);
|
|
@@ -1583,85 +1516,5 @@ program.name("aman-agent").description("Your AI companion, running locally").ver
|
|
|
1583
1516
|
);
|
|
1584
1517
|
await mcpManager.disconnect();
|
|
1585
1518
|
});
|
|
1586
|
-
program.command("schedule").description("Manage scheduled tasks").argument("[action]", "add, list, or remove").argument("[id]", "task ID (for remove)").action(async (action, id) => {
|
|
1587
|
-
if (!action || action === "list") {
|
|
1588
|
-
const tasks = loadSchedules();
|
|
1589
|
-
if (tasks.length === 0) {
|
|
1590
|
-
console.log(pc5.dim("No scheduled tasks."));
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
console.log(pc5.bold("Scheduled tasks:\n"));
|
|
1594
|
-
for (const task of tasks) {
|
|
1595
|
-
const lastRun = task.lastRun ? pc5.dim(` (last run: ${new Date(task.lastRun).toLocaleString()})`) : pc5.dim(" (never run)");
|
|
1596
|
-
console.log(
|
|
1597
|
-
` ${pc5.cyan(task.id)} ${task.name} ${pc5.dim(task.schedule)} [${task.mode}]${lastRun}`
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
return;
|
|
1601
|
-
}
|
|
1602
|
-
if (action === "add") {
|
|
1603
|
-
const name = await p2.text({
|
|
1604
|
-
message: "Task name?",
|
|
1605
|
-
validate: (v) => v.length === 0 ? "Name is required" : void 0
|
|
1606
|
-
});
|
|
1607
|
-
if (p2.isCancel(name)) return;
|
|
1608
|
-
const schedule = await p2.select({
|
|
1609
|
-
message: "Schedule?",
|
|
1610
|
-
options: [
|
|
1611
|
-
{ value: "daily 9am", label: "Daily at 9am" },
|
|
1612
|
-
{ value: "weekdays 9am", label: "Weekdays at 9am" },
|
|
1613
|
-
{ value: "weekly friday 4pm", label: "Weekly Friday 4pm" },
|
|
1614
|
-
{ value: "every 2h", label: "Every 2 hours" },
|
|
1615
|
-
{ value: "every 4h", label: "Every 4 hours" }
|
|
1616
|
-
]
|
|
1617
|
-
});
|
|
1618
|
-
if (p2.isCancel(schedule)) return;
|
|
1619
|
-
const actionType = await p2.select({
|
|
1620
|
-
message: "What should happen?",
|
|
1621
|
-
options: [
|
|
1622
|
-
{ value: "notify", label: "Show notification" },
|
|
1623
|
-
{ value: "auto-run", label: "Run automatically" }
|
|
1624
|
-
]
|
|
1625
|
-
});
|
|
1626
|
-
if (p2.isCancel(actionType)) return;
|
|
1627
|
-
let taskAction = "notify";
|
|
1628
|
-
if (actionType === "auto-run") {
|
|
1629
|
-
const cmd = await p2.text({
|
|
1630
|
-
message: "Command to run?",
|
|
1631
|
-
placeholder: "e.g. run:daily-standup",
|
|
1632
|
-
validate: (v) => v.length === 0 ? "Command is required" : void 0
|
|
1633
|
-
});
|
|
1634
|
-
if (p2.isCancel(cmd)) return;
|
|
1635
|
-
taskAction = cmd;
|
|
1636
|
-
}
|
|
1637
|
-
const task = addSchedule({
|
|
1638
|
-
name,
|
|
1639
|
-
schedule,
|
|
1640
|
-
action: taskAction,
|
|
1641
|
-
mode: actionType
|
|
1642
|
-
});
|
|
1643
|
-
console.log(
|
|
1644
|
-
pc5.green(`
|
|
1645
|
-
Scheduled task created: ${pc5.bold(task.name)} (${task.id})`)
|
|
1646
|
-
);
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
if (action === "remove") {
|
|
1650
|
-
if (!id) {
|
|
1651
|
-
console.log(pc5.red("Usage: aman-agent schedule remove <id>"));
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
const removed = removeSchedule(id);
|
|
1655
|
-
if (removed) {
|
|
1656
|
-
console.log(pc5.green(`Task ${id} removed.`));
|
|
1657
|
-
} else {
|
|
1658
|
-
console.log(pc5.red(`Task ${id} not found.`));
|
|
1659
|
-
}
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
console.log(
|
|
1663
|
-
pc5.red(`Unknown action: ${action}. Use add, list, or remove.`)
|
|
1664
|
-
);
|
|
1665
|
-
});
|
|
1666
1519
|
program.parse();
|
|
1667
1520
|
//# sourceMappingURL=index.js.map
|