@aman_asmuei/aman-agent 0.26.0 → 0.28.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 +25 -11
- package/dist/index.js +1153 -238
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,337 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/user-model.ts
|
|
12
|
+
var user_model_exports = {};
|
|
13
|
+
__export(user_model_exports, {
|
|
14
|
+
aggregateSession: () => aggregateSession,
|
|
15
|
+
computeProfile: () => computeProfile,
|
|
16
|
+
createEmptyModel: () => createEmptyModel,
|
|
17
|
+
defaultModelPath: () => defaultModelPath,
|
|
18
|
+
feedForward: () => feedForward,
|
|
19
|
+
loadUserModel: () => loadUserModel,
|
|
20
|
+
predictBurnout: () => predictBurnout,
|
|
21
|
+
saveUserModel: () => saveUserModel
|
|
22
|
+
});
|
|
23
|
+
import fs13 from "fs/promises";
|
|
24
|
+
import path13 from "path";
|
|
25
|
+
import os12 from "os";
|
|
26
|
+
function defaultModelPath() {
|
|
27
|
+
return path13.join(os12.homedir(), ".acore", "user-model.json");
|
|
28
|
+
}
|
|
29
|
+
function createEmptyModel() {
|
|
30
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31
|
+
return {
|
|
32
|
+
version: 1,
|
|
33
|
+
sessions: [],
|
|
34
|
+
profile: emptyProfile(),
|
|
35
|
+
createdAt: now,
|
|
36
|
+
updatedAt: now
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function emptyProfile() {
|
|
40
|
+
return {
|
|
41
|
+
trustScore: 0.5,
|
|
42
|
+
trustTrajectory: "stable",
|
|
43
|
+
totalSessions: 0,
|
|
44
|
+
preferredTimePeriod: "afternoon",
|
|
45
|
+
energyDistribution: {},
|
|
46
|
+
avgSessionMinutes: 0,
|
|
47
|
+
baselineFrustration: 0,
|
|
48
|
+
baselineExcitement: 0,
|
|
49
|
+
sentimentTrend: "stable",
|
|
50
|
+
frustrationCorrelations: { toolErrors: 0, longSessions: 0, lateNight: 0 },
|
|
51
|
+
avgTurnsPerSession: 0,
|
|
52
|
+
engagementTrend: "stable",
|
|
53
|
+
nudgeStats: {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function loadUserModel(filePath) {
|
|
57
|
+
const fp = filePath ?? defaultModelPath();
|
|
58
|
+
try {
|
|
59
|
+
const raw = await fs13.readFile(fp, "utf-8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (parsed?.version !== 1) return null;
|
|
62
|
+
return parsed;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function saveUserModel(model, filePath) {
|
|
68
|
+
const fp = filePath ?? defaultModelPath();
|
|
69
|
+
const dir = path13.dirname(fp);
|
|
70
|
+
await fs13.mkdir(dir, { recursive: true });
|
|
71
|
+
const tmp = fp + `.tmp-${Date.now()}`;
|
|
72
|
+
await fs13.writeFile(tmp, JSON.stringify(model, null, 2), "utf-8");
|
|
73
|
+
await fs13.rename(tmp, fp);
|
|
74
|
+
}
|
|
75
|
+
function aggregateSession(model, snapshot) {
|
|
76
|
+
const sessions = [...model.sessions, snapshot];
|
|
77
|
+
while (sessions.length > MAX_SESSIONS) {
|
|
78
|
+
sessions.shift();
|
|
79
|
+
}
|
|
80
|
+
const totalSessions = model.profile.totalSessions + 1;
|
|
81
|
+
const profile = computeProfile(sessions, totalSessions);
|
|
82
|
+
return {
|
|
83
|
+
...model,
|
|
84
|
+
sessions,
|
|
85
|
+
profile,
|
|
86
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function computeProfile(sessions, totalSessions) {
|
|
90
|
+
if (sessions.length === 0) return { ...emptyProfile(), totalSessions };
|
|
91
|
+
const n = sessions.length;
|
|
92
|
+
let trustScore = 0.5;
|
|
93
|
+
for (const s of sessions) {
|
|
94
|
+
trustScore = TRUST_ALPHA * ratingSignal(s) + (1 - TRUST_ALPHA) * trustScore;
|
|
95
|
+
}
|
|
96
|
+
const trustTrajectory = computeTrustTrajectory(sessions);
|
|
97
|
+
const baselineFrustration = avg(sessions.map((s) => s.avgFrustration));
|
|
98
|
+
const baselineExcitement = avg(sessions.map((s) => s.avgExcitement));
|
|
99
|
+
const sentimentTrend = computeSentimentTrend(sessions);
|
|
100
|
+
const energyDistribution = {};
|
|
101
|
+
for (const s of sessions) {
|
|
102
|
+
energyDistribution[s.timePeriod] = (energyDistribution[s.timePeriod] || 0) + 1;
|
|
103
|
+
}
|
|
104
|
+
const preferredTimePeriod = Object.entries(energyDistribution).sort(
|
|
105
|
+
(a, b) => b[1] - a[1]
|
|
106
|
+
)[0]?.[0] ?? "afternoon";
|
|
107
|
+
const avgSessionMinutes = avg(sessions.map((s) => s.durationMinutes));
|
|
108
|
+
const avgTurnsPerSession = avg(sessions.map((s) => s.turnCount));
|
|
109
|
+
const engagementTrend = computeLinearTrend(sessions.map((s) => s.turnCount));
|
|
110
|
+
const frustrationCorrelations = n >= MIN_SESSIONS_FOR_CORRELATIONS ? {
|
|
111
|
+
toolErrors: pearsonR(
|
|
112
|
+
sessions.map((s) => s.avgFrustration),
|
|
113
|
+
sessions.map((s) => s.toolErrors)
|
|
114
|
+
),
|
|
115
|
+
longSessions: pearsonR(
|
|
116
|
+
sessions.map((s) => s.avgFrustration),
|
|
117
|
+
sessions.map((s) => s.durationMinutes)
|
|
118
|
+
),
|
|
119
|
+
lateNight: pearsonR(
|
|
120
|
+
sessions.map((s) => s.avgFrustration),
|
|
121
|
+
sessions.map((s) => s.timePeriod === "late-night" || s.timePeriod === "night" ? 1 : 0)
|
|
122
|
+
)
|
|
123
|
+
} : { toolErrors: 0, longSessions: 0, lateNight: 0 };
|
|
124
|
+
const nudgeStats = {};
|
|
125
|
+
for (const s of sessions) {
|
|
126
|
+
const ratingVal = ratingToNumber(s.rating);
|
|
127
|
+
for (const nudge of s.wellbeingNudges) {
|
|
128
|
+
if (!nudgeStats[nudge]) nudgeStats[nudge] = { fired: 0, sessionRatingAfter: 0 };
|
|
129
|
+
nudgeStats[nudge].fired++;
|
|
130
|
+
nudgeStats[nudge].sessionRatingAfter += ratingVal;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const key of Object.keys(nudgeStats)) {
|
|
134
|
+
if (nudgeStats[key].fired > 0) {
|
|
135
|
+
nudgeStats[key].sessionRatingAfter /= nudgeStats[key].fired;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
trustScore,
|
|
140
|
+
trustTrajectory,
|
|
141
|
+
totalSessions,
|
|
142
|
+
preferredTimePeriod,
|
|
143
|
+
energyDistribution,
|
|
144
|
+
avgSessionMinutes,
|
|
145
|
+
baselineFrustration,
|
|
146
|
+
baselineExcitement,
|
|
147
|
+
sentimentTrend,
|
|
148
|
+
frustrationCorrelations,
|
|
149
|
+
avgTurnsPerSession,
|
|
150
|
+
engagementTrend,
|
|
151
|
+
nudgeStats
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function feedForward(model) {
|
|
155
|
+
if (model.profile.totalSessions < MIN_SESSIONS_FOR_FEED_FORWARD) return null;
|
|
156
|
+
const p4 = model.profile;
|
|
157
|
+
const overrides = {
|
|
158
|
+
compactGreeting: false,
|
|
159
|
+
frustrationNudgeThreshold: 0.6,
|
|
160
|
+
defaultToPersonalMode: false
|
|
161
|
+
};
|
|
162
|
+
const nightSessions = (p4.energyDistribution["late-night"] || 0) + (p4.energyDistribution["night"] || 0);
|
|
163
|
+
const totalInWindow = model.sessions.length;
|
|
164
|
+
if (totalInWindow > 0 && nightSessions / totalInWindow >= 0.7 && p4.baselineFrustration < 0.3) {
|
|
165
|
+
overrides.energyOverride = "steady";
|
|
166
|
+
}
|
|
167
|
+
if (p4.trustScore > 0.8) {
|
|
168
|
+
overrides.compactGreeting = true;
|
|
169
|
+
}
|
|
170
|
+
if (p4.frustrationCorrelations.toolErrors > 0.4) {
|
|
171
|
+
overrides.frustrationNudgeThreshold = 0.4;
|
|
172
|
+
}
|
|
173
|
+
if (p4.sentimentTrend === "worsening") {
|
|
174
|
+
overrides.defaultToPersonalMode = true;
|
|
175
|
+
}
|
|
176
|
+
return overrides;
|
|
177
|
+
}
|
|
178
|
+
function predictBurnout(sessions, currentSession) {
|
|
179
|
+
const recent = sessions.slice(-7);
|
|
180
|
+
if (recent.length < 3) {
|
|
181
|
+
return { risk: 0, factors: [] };
|
|
182
|
+
}
|
|
183
|
+
const factors = [];
|
|
184
|
+
let risk = 0;
|
|
185
|
+
const mid = Math.floor(recent.length / 2);
|
|
186
|
+
const firstHalf = recent.slice(0, mid);
|
|
187
|
+
const secondHalf = recent.slice(mid);
|
|
188
|
+
const avgFrustFirst = avg(firstHalf.map((s) => s.avgFrustration));
|
|
189
|
+
const avgFrustSecond = avg(secondHalf.map((s) => s.avgFrustration));
|
|
190
|
+
if (avgFrustSecond > avgFrustFirst + 0.1 && avgFrustSecond > 0.4) {
|
|
191
|
+
risk += 0.25;
|
|
192
|
+
factors.push("rising frustration trend");
|
|
193
|
+
}
|
|
194
|
+
const ratings = recent.filter((s) => s.rating).map((s) => ratingSignal(s));
|
|
195
|
+
if (ratings.length >= 3) {
|
|
196
|
+
const lastThree = ratings.slice(-3);
|
|
197
|
+
const avgLast3 = avg(lastThree);
|
|
198
|
+
if (avgLast3 < 0.5) {
|
|
199
|
+
risk += 0.2;
|
|
200
|
+
factors.push("low recent ratings");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const avgMins = avg(recent.map((s) => s.durationMinutes));
|
|
204
|
+
if (avgMins > 90) {
|
|
205
|
+
risk += 0.15;
|
|
206
|
+
factors.push("consistently long sessions");
|
|
207
|
+
}
|
|
208
|
+
const lateNightCount = recent.filter((s) => s.timePeriod === "late-night" || s.timePeriod === "night").length;
|
|
209
|
+
if (lateNightCount / recent.length > 0.5) {
|
|
210
|
+
risk += 0.15;
|
|
211
|
+
factors.push("frequent late-night sessions");
|
|
212
|
+
}
|
|
213
|
+
const avgBlockers = avg(recent.map((s) => s.blockers));
|
|
214
|
+
if (avgBlockers > 1) {
|
|
215
|
+
risk += 0.15;
|
|
216
|
+
factors.push("frequent blockers");
|
|
217
|
+
}
|
|
218
|
+
if (currentSession) {
|
|
219
|
+
if (currentSession.minutes > 120 && currentSession.frustration > 0.5) {
|
|
220
|
+
risk += 0.1;
|
|
221
|
+
factors.push("current session: long + frustrated");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
risk = clamp(risk, 0, 1);
|
|
225
|
+
let recommendation;
|
|
226
|
+
if (risk > 0.7) {
|
|
227
|
+
recommendation = "Consider taking a longer break. You've been pushing hard \u2014 rest is productive too.";
|
|
228
|
+
} else if (risk > 0.5) {
|
|
229
|
+
recommendation = "Watch for signs of fatigue. A change of pace or shorter sessions might help.";
|
|
230
|
+
}
|
|
231
|
+
return { risk, factors, recommendation };
|
|
232
|
+
}
|
|
233
|
+
function clamp(val, min, max) {
|
|
234
|
+
return Math.max(min, Math.min(max, val));
|
|
235
|
+
}
|
|
236
|
+
function avg(values) {
|
|
237
|
+
if (values.length === 0) return 0;
|
|
238
|
+
return values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
239
|
+
}
|
|
240
|
+
function ratingSignal(session) {
|
|
241
|
+
if (session.rating === "great") return 1;
|
|
242
|
+
if (session.rating === "good") return 0.75;
|
|
243
|
+
if (session.rating === "okay") return 0.5;
|
|
244
|
+
if (session.rating === "frustrating") return 0.25;
|
|
245
|
+
let implicit = 1;
|
|
246
|
+
implicit -= session.avgFrustration * 0.4;
|
|
247
|
+
implicit -= session.toolErrors > 3 ? 0.2 : 0;
|
|
248
|
+
implicit -= session.blockers > 2 ? 0.2 : 0;
|
|
249
|
+
implicit += session.milestones > 0 ? 0.1 : 0;
|
|
250
|
+
return clamp(implicit, 0, 1);
|
|
251
|
+
}
|
|
252
|
+
function ratingToNumber(rating) {
|
|
253
|
+
if (rating === "great") return 1;
|
|
254
|
+
if (rating === "good") return 0.75;
|
|
255
|
+
if (rating === "okay") return 0.5;
|
|
256
|
+
if (rating === "frustrating") return 0.25;
|
|
257
|
+
return 0.5;
|
|
258
|
+
}
|
|
259
|
+
function pearsonR(x, y) {
|
|
260
|
+
const n = x.length;
|
|
261
|
+
if (n < 3) return 0;
|
|
262
|
+
const mx = avg(x);
|
|
263
|
+
const my = avg(y);
|
|
264
|
+
let num = 0;
|
|
265
|
+
let dx2 = 0;
|
|
266
|
+
let dy2 = 0;
|
|
267
|
+
for (let i = 0; i < n; i++) {
|
|
268
|
+
const dx = x[i] - mx;
|
|
269
|
+
const dy = y[i] - my;
|
|
270
|
+
num += dx * dy;
|
|
271
|
+
dx2 += dx * dx;
|
|
272
|
+
dy2 += dy * dy;
|
|
273
|
+
}
|
|
274
|
+
const denom = Math.sqrt(dx2 * dy2);
|
|
275
|
+
if (denom === 0) return 0;
|
|
276
|
+
return num / denom;
|
|
277
|
+
}
|
|
278
|
+
function computeTrustTrajectory(sessions) {
|
|
279
|
+
if (sessions.length < 10) return "stable";
|
|
280
|
+
const recent5 = sessions.slice(-5).map(ratingSignal);
|
|
281
|
+
const prev5 = sessions.slice(-10, -5).map(ratingSignal);
|
|
282
|
+
const recentAvg = avg(recent5);
|
|
283
|
+
const prevAvg = avg(prev5);
|
|
284
|
+
const delta = recentAvg - prevAvg;
|
|
285
|
+
if (delta > 0.1) return "ascending";
|
|
286
|
+
if (delta < -0.1) return "declining";
|
|
287
|
+
return "stable";
|
|
288
|
+
}
|
|
289
|
+
function computeSentimentTrend(sessions) {
|
|
290
|
+
if (sessions.length < 5) return "stable";
|
|
291
|
+
const frustrations = sessions.slice(-10).map((s) => s.avgFrustration);
|
|
292
|
+
const slope = linearSlope(frustrations);
|
|
293
|
+
if (slope > 0.02) return "worsening";
|
|
294
|
+
if (slope < -0.02) return "improving";
|
|
295
|
+
return "stable";
|
|
296
|
+
}
|
|
297
|
+
function computeLinearTrend(values) {
|
|
298
|
+
if (values.length < 5) return "stable";
|
|
299
|
+
const recent = values.slice(-10);
|
|
300
|
+
const slope = linearSlope(recent);
|
|
301
|
+
const mean = avg(recent);
|
|
302
|
+
const relativeSlope = mean > 0 ? slope / mean : slope;
|
|
303
|
+
if (relativeSlope > 0.03) return "increasing";
|
|
304
|
+
if (relativeSlope < -0.03) return "decreasing";
|
|
305
|
+
return "stable";
|
|
306
|
+
}
|
|
307
|
+
function linearSlope(values) {
|
|
308
|
+
const n = values.length;
|
|
309
|
+
if (n < 2) return 0;
|
|
310
|
+
let sumX = 0;
|
|
311
|
+
let sumY = 0;
|
|
312
|
+
let sumXY = 0;
|
|
313
|
+
let sumX2 = 0;
|
|
314
|
+
for (let i = 0; i < n; i++) {
|
|
315
|
+
sumX += i;
|
|
316
|
+
sumY += values[i];
|
|
317
|
+
sumXY += i * values[i];
|
|
318
|
+
sumX2 += i * i;
|
|
319
|
+
}
|
|
320
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
321
|
+
if (denom === 0) return 0;
|
|
322
|
+
return (n * sumXY - sumX * sumY) / denom;
|
|
323
|
+
}
|
|
324
|
+
var MAX_SESSIONS, TRUST_ALPHA, MIN_SESSIONS_FOR_FEED_FORWARD, MIN_SESSIONS_FOR_CORRELATIONS;
|
|
325
|
+
var init_user_model = __esm({
|
|
326
|
+
"src/user-model.ts"() {
|
|
327
|
+
"use strict";
|
|
328
|
+
MAX_SESSIONS = 30;
|
|
329
|
+
TRUST_ALPHA = 0.3;
|
|
330
|
+
MIN_SESSIONS_FOR_FEED_FORWARD = 5;
|
|
331
|
+
MIN_SESSIONS_FOR_CORRELATIONS = 10;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
1
335
|
// src/index.ts
|
|
2
336
|
import { Command } from "commander";
|
|
3
337
|
import * as p3 from "@clack/prompts";
|
|
@@ -1387,18 +1721,18 @@ var McpManager = class {
|
|
|
1387
1721
|
|
|
1388
1722
|
// src/agent.ts
|
|
1389
1723
|
import * as readline from "readline";
|
|
1390
|
-
import
|
|
1391
|
-
import
|
|
1392
|
-
import
|
|
1724
|
+
import fs20 from "fs";
|
|
1725
|
+
import path20 from "path";
|
|
1726
|
+
import os19 from "os";
|
|
1393
1727
|
import pc7 from "picocolors";
|
|
1394
1728
|
import { marked } from "marked";
|
|
1395
1729
|
import { markedTerminal } from "marked-terminal";
|
|
1396
1730
|
import logUpdate from "log-update";
|
|
1397
1731
|
|
|
1398
1732
|
// src/commands.ts
|
|
1399
|
-
import
|
|
1400
|
-
import
|
|
1401
|
-
import
|
|
1733
|
+
import fs17 from "fs";
|
|
1734
|
+
import path17 from "path";
|
|
1735
|
+
import os16 from "os";
|
|
1402
1736
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1403
1737
|
import pc5 from "picocolors";
|
|
1404
1738
|
|
|
@@ -2361,9 +2695,9 @@ import pc3 from "picocolors";
|
|
|
2361
2695
|
// src/hooks.ts
|
|
2362
2696
|
import pc2 from "picocolors";
|
|
2363
2697
|
import * as p2 from "@clack/prompts";
|
|
2364
|
-
import
|
|
2365
|
-
import
|
|
2366
|
-
import
|
|
2698
|
+
import fs14 from "fs";
|
|
2699
|
+
import path14 from "path";
|
|
2700
|
+
import os13 from "os";
|
|
2367
2701
|
|
|
2368
2702
|
// src/personality.ts
|
|
2369
2703
|
var FRUSTRATION_SIGNALS = [
|
|
@@ -2518,19 +2852,38 @@ The user seems tired. Keep responses concise and to the point. If they mention w
|
|
|
2518
2852
|
</wellbeing>`,
|
|
2519
2853
|
"break-long-session": `<wellbeing>
|
|
2520
2854
|
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.
|
|
2855
|
+
</wellbeing>`,
|
|
2856
|
+
"burnout-warning": `<wellbeing>
|
|
2857
|
+
Recent patterns suggest the user may be approaching burnout \u2014 rising frustration, declining satisfaction, long or late sessions. Be extra mindful: keep responses concise, celebrate small wins, gently suggest breaks or scope reduction. Don't mention burnout directly unless they bring it up.
|
|
2521
2858
|
</wellbeing>`
|
|
2522
2859
|
};
|
|
2523
2860
|
function formatWellbeingNudge(state) {
|
|
2524
2861
|
if (!state.wellbeingNudge) return null;
|
|
2525
2862
|
return WELLBEING_NUDGES[state.wellbeingNudge] || null;
|
|
2526
2863
|
}
|
|
2527
|
-
|
|
2864
|
+
function shouldFireNudge(nudgeType, profile) {
|
|
2865
|
+
if (!profile) return true;
|
|
2866
|
+
const stats = profile.nudgeStats[nudgeType];
|
|
2867
|
+
if (!stats || stats.fired < 5) return true;
|
|
2868
|
+
if (stats.sessionRatingAfter < 0.4) {
|
|
2869
|
+
log.debug("personality", `suppressing nudge "${nudgeType}" \u2014 low avg rating ${stats.sessionRatingAfter.toFixed(2)} after ${stats.fired} fires`);
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
return true;
|
|
2873
|
+
}
|
|
2874
|
+
async function syncPersonalityToCore(state, mcpManager, modelMetrics) {
|
|
2528
2875
|
try {
|
|
2529
|
-
|
|
2876
|
+
const payload = {
|
|
2530
2877
|
currentRead: state.currentRead,
|
|
2531
2878
|
energy: state.energy,
|
|
2532
2879
|
activeMode: state.activeMode
|
|
2533
|
-
}
|
|
2880
|
+
};
|
|
2881
|
+
if (modelMetrics) {
|
|
2882
|
+
payload.trust = `${(modelMetrics.trustScore * 100).toFixed(0)}%`;
|
|
2883
|
+
payload.sessions = modelMetrics.totalSessions;
|
|
2884
|
+
payload.sentimentTrend = modelMetrics.sentimentTrend;
|
|
2885
|
+
}
|
|
2886
|
+
await mcpManager.callTool("identity_update_dynamics", payload);
|
|
2534
2887
|
} catch (err) {
|
|
2535
2888
|
log.debug("personality", "identity_update_dynamics failed", err);
|
|
2536
2889
|
}
|
|
@@ -2712,7 +3065,7 @@ CRYSTALLIZATION RULES:
|
|
|
2712
3065
|
- Skip vague things like "use library X" \u2014 that's not procedural knowledge
|
|
2713
3066
|
- Prefer narrow specific procedures over broad generalizations
|
|
2714
3067
|
- Trigger keywords should be highly specific (avoid generic words like "code", "fix", "the")`;
|
|
2715
|
-
async function generatePostmortemReport(sessionId, messages, session, client, obsDir) {
|
|
3068
|
+
async function generatePostmortemReport(sessionId, messages, session, client, obsDir, rejectedSkillNames) {
|
|
2716
3069
|
try {
|
|
2717
3070
|
const events = await readObservationEvents(sessionId, obsDir ?? defaultObservationsDir2());
|
|
2718
3071
|
const toolMap = /* @__PURE__ */ new Map();
|
|
@@ -2748,7 +3101,10 @@ async function generatePostmortemReport(sessionId, messages, session, client, ob
|
|
|
2748
3101
|
});
|
|
2749
3102
|
const obsSnapshot = events.slice(-30).map((e) => `[${e.type}] ${e.summary}`);
|
|
2750
3103
|
const durationMin = Math.round((Date.now() - session.startedAt) / 6e4);
|
|
2751
|
-
const prompt = `${POSTMORTEM_PROMPT}
|
|
3104
|
+
const prompt = `${POSTMORTEM_PROMPT}${rejectedSkillNames && rejectedSkillNames.length > 0 ? `
|
|
3105
|
+
|
|
3106
|
+
PREVIOUSLY REJECTED SKILLS (do NOT suggest these again):
|
|
3107
|
+
${rejectedSkillNames.map((n) => `- ${n}`).join("\n")}` : ""}
|
|
2752
3108
|
|
|
2753
3109
|
Session ID: ${sessionId}
|
|
2754
3110
|
Duration: ${durationMin} minutes
|
|
@@ -3132,7 +3488,8 @@ async function writeSkillToFile(candidate, skillsMdPath, postmortemFilename) {
|
|
|
3132
3488
|
written: false,
|
|
3133
3489
|
filePath: skillsMdPath,
|
|
3134
3490
|
skillName: candidate.name,
|
|
3135
|
-
reason: `collision with "${collision.collidesWith}" (${collision.reason})
|
|
3491
|
+
reason: `collision with "${collision.collidesWith}" (${collision.reason})`,
|
|
3492
|
+
collidesWith: collision.collidesWith
|
|
3136
3493
|
};
|
|
3137
3494
|
}
|
|
3138
3495
|
const skillMarkdown = formatSkillMarkdown(candidate, postmortemFilename);
|
|
@@ -3157,6 +3514,66 @@ async function writeSkillToFile(candidate, skillsMdPath, postmortemFilename) {
|
|
|
3157
3514
|
};
|
|
3158
3515
|
}
|
|
3159
3516
|
}
|
|
3517
|
+
async function mergeSkillInFile(candidate, existingName, skillsMdPath, postmortemFilename) {
|
|
3518
|
+
try {
|
|
3519
|
+
const content = await fs12.readFile(skillsMdPath, "utf-8");
|
|
3520
|
+
const lines = content.split("\n");
|
|
3521
|
+
const heading = toTitleCase(existingName);
|
|
3522
|
+
let startIdx = -1;
|
|
3523
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3524
|
+
if (lines[i].startsWith("# ") && lines[i].slice(2).trim() === heading) {
|
|
3525
|
+
startIdx = i;
|
|
3526
|
+
break;
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
if (startIdx === -1) {
|
|
3530
|
+
return writeSkillToFile(candidate, skillsMdPath, postmortemFilename);
|
|
3531
|
+
}
|
|
3532
|
+
let endIdx = lines.length;
|
|
3533
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
3534
|
+
if (lines[i].startsWith("# ") && !lines[i].startsWith("## ")) {
|
|
3535
|
+
endIdx = i;
|
|
3536
|
+
break;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
const versionPattern = new RegExp(`^# ${heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.v\\d+`);
|
|
3540
|
+
let maxVersion = 0;
|
|
3541
|
+
for (const line of lines) {
|
|
3542
|
+
if (versionPattern.test(line)) {
|
|
3543
|
+
const vMatch = line.match(/\.v(\d+)/);
|
|
3544
|
+
if (vMatch) maxVersion = Math.max(maxVersion, parseInt(vMatch[1], 10));
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
const archiveVersion = maxVersion + 1;
|
|
3548
|
+
const oldBlock = lines.slice(startIdx, endIdx);
|
|
3549
|
+
oldBlock[0] = `# ${heading}.v${archiveVersion}`;
|
|
3550
|
+
const archiveMarker = `<!-- aman-archived version=${archiveVersion} archived-at=${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)} -->`;
|
|
3551
|
+
if (oldBlock.length > 1 && oldBlock[1].includes("aman-auto")) {
|
|
3552
|
+
oldBlock.splice(2, 0, archiveMarker);
|
|
3553
|
+
} else {
|
|
3554
|
+
oldBlock.splice(1, 0, archiveMarker);
|
|
3555
|
+
}
|
|
3556
|
+
const newSkillMarkdown = formatSkillMarkdown(candidate, postmortemFilename);
|
|
3557
|
+
const before = lines.slice(0, startIdx);
|
|
3558
|
+
const after = lines.slice(endIdx);
|
|
3559
|
+
const merged = [...before, ...oldBlock, "", newSkillMarkdown, ...after].join("\n");
|
|
3560
|
+
await fs12.writeFile(skillsMdPath, merged, "utf-8");
|
|
3561
|
+
return {
|
|
3562
|
+
written: true,
|
|
3563
|
+
filePath: skillsMdPath,
|
|
3564
|
+
skillName: candidate.name,
|
|
3565
|
+
reason: `merged with "${existingName}" (archived as .v${archiveVersion})`
|
|
3566
|
+
};
|
|
3567
|
+
} catch (err) {
|
|
3568
|
+
log.warn("crystallization", "mergeSkillInFile failed", err);
|
|
3569
|
+
return {
|
|
3570
|
+
written: false,
|
|
3571
|
+
filePath: skillsMdPath,
|
|
3572
|
+
skillName: candidate.name,
|
|
3573
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3160
3577
|
async function appendCrystallizationLog(entry, logPath) {
|
|
3161
3578
|
try {
|
|
3162
3579
|
await fs12.mkdir(path12.dirname(logPath), { recursive: true });
|
|
@@ -3199,8 +3616,41 @@ async function appendRejection(candidate, postmortemFilename, rejectionsPath) {
|
|
|
3199
3616
|
log.debug("crystallization", "appendRejection failed", err);
|
|
3200
3617
|
}
|
|
3201
3618
|
}
|
|
3619
|
+
async function loadRejectedNames(rejectionsPath) {
|
|
3620
|
+
try {
|
|
3621
|
+
const content = await fs12.readFile(rejectionsPath, "utf-8");
|
|
3622
|
+
const entries = JSON.parse(content);
|
|
3623
|
+
if (!Array.isArray(entries)) return [];
|
|
3624
|
+
return [...new Set(entries.map((e) => e.name))];
|
|
3625
|
+
} catch {
|
|
3626
|
+
return [];
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
async function loadSuggestionCounts(suggestionsPath) {
|
|
3630
|
+
try {
|
|
3631
|
+
const content = await fs12.readFile(suggestionsPath, "utf-8");
|
|
3632
|
+
const parsed = JSON.parse(content);
|
|
3633
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
3634
|
+
return parsed;
|
|
3635
|
+
} catch {
|
|
3636
|
+
return {};
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
async function incrementSuggestionCount(name, suggestionsPath) {
|
|
3640
|
+
try {
|
|
3641
|
+
await fs12.mkdir(path12.dirname(suggestionsPath), { recursive: true });
|
|
3642
|
+
const counts = await loadSuggestionCounts(suggestionsPath);
|
|
3643
|
+
counts[name] = (counts[name] || 0) + 1;
|
|
3644
|
+
await fs12.writeFile(suggestionsPath, JSON.stringify(counts, null, 2), "utf-8");
|
|
3645
|
+
return counts[name];
|
|
3646
|
+
} catch (err) {
|
|
3647
|
+
log.debug("crystallization", "incrementSuggestionCount failed", err);
|
|
3648
|
+
return 0;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3202
3651
|
|
|
3203
3652
|
// src/hooks.ts
|
|
3653
|
+
init_user_model();
|
|
3204
3654
|
function getTimeContext() {
|
|
3205
3655
|
const now = /* @__PURE__ */ new Date();
|
|
3206
3656
|
const hour = now.getHours();
|
|
@@ -3347,11 +3797,45 @@ ${contextInjection}`;
|
|
|
3347
3797
|
sessionMinutes: 0,
|
|
3348
3798
|
turnCount: 0
|
|
3349
3799
|
});
|
|
3800
|
+
try {
|
|
3801
|
+
const model = await loadUserModel();
|
|
3802
|
+
if (model) {
|
|
3803
|
+
const overrides = feedForward(model);
|
|
3804
|
+
if (overrides) {
|
|
3805
|
+
log.debug("hooks", `Feed-forward active (trust=${model.profile.trustScore.toFixed(2)}, sessions=${model.profile.totalSessions})`);
|
|
3806
|
+
if (overrides.energyOverride && (period === "late-night" || period === "night")) {
|
|
3807
|
+
state.energy = overrides.energyOverride;
|
|
3808
|
+
}
|
|
3809
|
+
if (overrides.defaultToPersonalMode && state.activeMode === "Default") {
|
|
3810
|
+
state.activeMode = "Personal";
|
|
3811
|
+
}
|
|
3812
|
+
if (overrides.compactGreeting) {
|
|
3813
|
+
greeting += "\n<user-model-context>High trust user (score: " + model.profile.trustScore.toFixed(2) + ", " + model.profile.totalSessions + " sessions). Keep greeting compact \u2014 they know you well.</user-model-context>";
|
|
3814
|
+
}
|
|
3815
|
+
if (model.profile.sentimentTrend === "worsening") {
|
|
3816
|
+
greeting += "\n<user-model-context>Sentiment trend is worsening across recent sessions. Be more attentive and patient.</user-model-context>";
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
} catch (err) {
|
|
3821
|
+
log.debug("hooks", "user model feed-forward failed", err);
|
|
3822
|
+
}
|
|
3350
3823
|
syncPersonalityToCore(state, ctx.mcpManager).catch(() => {
|
|
3351
3824
|
});
|
|
3352
3825
|
const nudge = formatWellbeingNudge(state);
|
|
3353
|
-
if (nudge) {
|
|
3354
|
-
|
|
3826
|
+
if (nudge && state.wellbeingNudge) {
|
|
3827
|
+
let fireNudge = true;
|
|
3828
|
+
try {
|
|
3829
|
+
const model = await loadUserModel();
|
|
3830
|
+
if (model && model.sessions.length >= 5) {
|
|
3831
|
+
const profile = computeProfile(model.sessions, model.sessions.length);
|
|
3832
|
+
fireNudge = shouldFireNudge(state.wellbeingNudge, profile);
|
|
3833
|
+
}
|
|
3834
|
+
} catch {
|
|
3835
|
+
}
|
|
3836
|
+
if (fireNudge) {
|
|
3837
|
+
greeting += "\n" + nudge;
|
|
3838
|
+
}
|
|
3355
3839
|
}
|
|
3356
3840
|
}
|
|
3357
3841
|
if (greeting) {
|
|
@@ -3468,10 +3952,10 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3468
3952
|
}
|
|
3469
3953
|
console.log(pc2.dim(` Saved ${textMessages.length} messages (session: ${sessionId})`));
|
|
3470
3954
|
}
|
|
3471
|
-
const projectContextPath =
|
|
3472
|
-
if (
|
|
3955
|
+
const projectContextPath = path14.join(process.cwd(), ".acore", "context.md");
|
|
3956
|
+
if (fs14.existsSync(projectContextPath) && messages.length > 2) {
|
|
3473
3957
|
try {
|
|
3474
|
-
let contextContent =
|
|
3958
|
+
let contextContent = fs14.readFileSync(projectContextPath, "utf-8");
|
|
3475
3959
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
3476
3960
|
let lastUserMsg = "";
|
|
3477
3961
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3489,28 +3973,28 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3489
3973
|
- Recent decisions: [see memory]
|
|
3490
3974
|
- Temp notes: [cleared]`;
|
|
3491
3975
|
contextContent = contextContent.replace(sessionPattern, newSession);
|
|
3492
|
-
|
|
3976
|
+
fs14.writeFileSync(projectContextPath, contextContent, "utf-8");
|
|
3493
3977
|
log.debug("hooks", `Updated project context: ${projectContextPath}`);
|
|
3494
3978
|
}
|
|
3495
3979
|
} catch (err) {
|
|
3496
3980
|
log.debug("hooks", "project context update failed", err);
|
|
3497
3981
|
}
|
|
3498
3982
|
}
|
|
3983
|
+
const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
|
|
3984
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
3985
|
+
let period;
|
|
3986
|
+
if (hour < 6) period = "late-night";
|
|
3987
|
+
else if (hour < 12) period = "morning";
|
|
3988
|
+
else if (hour < 17) period = "afternoon";
|
|
3989
|
+
else if (hour < 21) period = "evening";
|
|
3990
|
+
else period = "night";
|
|
3991
|
+
const turnCount = messages.filter((m) => m.role === "user").length;
|
|
3992
|
+
const finalState = computePersonality({
|
|
3993
|
+
timePeriod: period,
|
|
3994
|
+
sessionMinutes,
|
|
3995
|
+
turnCount
|
|
3996
|
+
});
|
|
3499
3997
|
if (ctx.config.personalityAdapt !== false) {
|
|
3500
|
-
const sessionMinutes = Math.round((Date.now() - sessionStartTime) / 6e4);
|
|
3501
|
-
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
3502
|
-
let period;
|
|
3503
|
-
if (hour < 6) period = "late-night";
|
|
3504
|
-
else if (hour < 12) period = "morning";
|
|
3505
|
-
else if (hour < 17) period = "afternoon";
|
|
3506
|
-
else if (hour < 21) period = "evening";
|
|
3507
|
-
else period = "night";
|
|
3508
|
-
const turnCount = messages.filter((m) => m.role === "user").length;
|
|
3509
|
-
const finalState = computePersonality({
|
|
3510
|
-
timePeriod: period,
|
|
3511
|
-
sessionMinutes,
|
|
3512
|
-
turnCount
|
|
3513
|
-
});
|
|
3514
3998
|
try {
|
|
3515
3999
|
isHookCall = true;
|
|
3516
4000
|
await syncPersonalityToCore(finalState, ctx.mcpManager);
|
|
@@ -3518,6 +4002,7 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3518
4002
|
isHookCall = false;
|
|
3519
4003
|
}
|
|
3520
4004
|
}
|
|
4005
|
+
let sessionRating;
|
|
3521
4006
|
if (ctx.config.evalPrompt) {
|
|
3522
4007
|
const rating = await p2.select({
|
|
3523
4008
|
message: "Quick rating for this session?",
|
|
@@ -3530,10 +4015,11 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3530
4015
|
initialValue: "skip"
|
|
3531
4016
|
});
|
|
3532
4017
|
if (!p2.isCancel(rating) && rating !== "skip") {
|
|
4018
|
+
sessionRating = rating;
|
|
3533
4019
|
try {
|
|
3534
4020
|
isHookCall = true;
|
|
3535
4021
|
await ctx.mcpManager.callTool("eval_log", {
|
|
3536
|
-
rating,
|
|
4022
|
+
rating: sessionRating,
|
|
3537
4023
|
highlights: "Quick session rating",
|
|
3538
4024
|
improvements: ""
|
|
3539
4025
|
});
|
|
@@ -3542,15 +4028,68 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3542
4028
|
}
|
|
3543
4029
|
}
|
|
3544
4030
|
}
|
|
4031
|
+
if (turnCount >= 2 && sessionMinutes >= 1) {
|
|
4032
|
+
try {
|
|
4033
|
+
const snapshot = {
|
|
4034
|
+
sessionId,
|
|
4035
|
+
date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
4036
|
+
durationMinutes: sessionMinutes,
|
|
4037
|
+
turnCount,
|
|
4038
|
+
dominantSentiment: finalState.sentiment.dominant,
|
|
4039
|
+
avgFrustration: finalState.sentiment.frustration,
|
|
4040
|
+
avgExcitement: finalState.sentiment.excitement,
|
|
4041
|
+
avgConfusion: finalState.sentiment.confusion,
|
|
4042
|
+
avgFatigue: finalState.sentiment.fatigue,
|
|
4043
|
+
toolCalls: observationSession?.stats.toolCalls ?? 0,
|
|
4044
|
+
toolErrors: observationSession?.stats.toolErrors ?? 0,
|
|
4045
|
+
blockers: observationSession?.stats.blockers ?? 0,
|
|
4046
|
+
milestones: observationSession?.stats.milestones ?? 0,
|
|
4047
|
+
topicShifts: observationSession?.stats.topicShifts ?? 0,
|
|
4048
|
+
peakEnergy: finalState.energy,
|
|
4049
|
+
primaryMode: finalState.activeMode,
|
|
4050
|
+
timePeriod: period,
|
|
4051
|
+
rating: sessionRating,
|
|
4052
|
+
hadPostmortem: false,
|
|
4053
|
+
// updated below if postmortem is generated
|
|
4054
|
+
wellbeingNudges: finalState.wellbeingNudge ? [finalState.wellbeingNudge] : []
|
|
4055
|
+
};
|
|
4056
|
+
const model = await loadUserModel() ?? createEmptyModel();
|
|
4057
|
+
const updated = aggregateSession(model, snapshot);
|
|
4058
|
+
await saveUserModel(updated);
|
|
4059
|
+
log.debug("hooks", `User model updated (session ${updated.profile.totalSessions})`);
|
|
4060
|
+
if (ctx.config.personalityAdapt !== false) {
|
|
4061
|
+
try {
|
|
4062
|
+
isHookCall = true;
|
|
4063
|
+
await syncPersonalityToCore(finalState, ctx.mcpManager, {
|
|
4064
|
+
trustScore: updated.profile.trustScore,
|
|
4065
|
+
totalSessions: updated.profile.totalSessions,
|
|
4066
|
+
sentimentTrend: updated.profile.sentimentTrend
|
|
4067
|
+
});
|
|
4068
|
+
} finally {
|
|
4069
|
+
isHookCall = false;
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
log.debug("hooks", "user model aggregation failed", err);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
3545
4076
|
if (ctx.config.autoPostmortem !== false && observationSession && shouldAutoPostmortem(observationSession, messages)) {
|
|
3546
4077
|
try {
|
|
3547
4078
|
const client = ctx.llmClient;
|
|
3548
4079
|
if (client) {
|
|
4080
|
+
const rejectionsPath = path14.join(
|
|
4081
|
+
os13.homedir(),
|
|
4082
|
+
".aman-agent",
|
|
4083
|
+
"crystallization-rejections.json"
|
|
4084
|
+
);
|
|
4085
|
+
const rejectedNames = await loadRejectedNames(rejectionsPath);
|
|
3549
4086
|
const report = await generatePostmortemReport(
|
|
3550
4087
|
sessionId,
|
|
3551
4088
|
messages,
|
|
3552
4089
|
observationSession,
|
|
3553
|
-
client
|
|
4090
|
+
client,
|
|
4091
|
+
void 0,
|
|
4092
|
+
rejectedNames
|
|
3554
4093
|
);
|
|
3555
4094
|
if (report) {
|
|
3556
4095
|
const filePath = await savePostmortem(report);
|
|
@@ -3568,17 +4107,22 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3568
4107
|
}
|
|
3569
4108
|
}
|
|
3570
4109
|
if (report.crystallizationCandidates && report.crystallizationCandidates.length > 0) {
|
|
3571
|
-
const skillsMdPath =
|
|
3572
|
-
const logPath =
|
|
3573
|
-
|
|
4110
|
+
const skillsMdPath = path14.join(os13.homedir(), ".askill", "skills.md");
|
|
4111
|
+
const logPath = path14.join(
|
|
4112
|
+
os13.homedir(),
|
|
3574
4113
|
".aman-agent",
|
|
3575
4114
|
"crystallization-log.json"
|
|
3576
4115
|
);
|
|
3577
|
-
const
|
|
3578
|
-
|
|
4116
|
+
const rejectionsPath2 = path14.join(
|
|
4117
|
+
os13.homedir(),
|
|
3579
4118
|
".aman-agent",
|
|
3580
4119
|
"crystallization-rejections.json"
|
|
3581
4120
|
);
|
|
4121
|
+
const suggestionsPath = path14.join(
|
|
4122
|
+
os13.homedir(),
|
|
4123
|
+
".aman-agent",
|
|
4124
|
+
"crystallization-suggestions.json"
|
|
4125
|
+
);
|
|
3582
4126
|
const postmortemFilename = `${report.date}-${report.sessionId.slice(0, 4)}.md`;
|
|
3583
4127
|
console.log(
|
|
3584
4128
|
pc2.dim(`
|
|
@@ -3592,14 +4136,17 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3592
4136
|
log.debug("hooks", "candidate failed validation");
|
|
3593
4137
|
continue;
|
|
3594
4138
|
}
|
|
4139
|
+
const suggestCount = await incrementSuggestionCount(candidate.name, suggestionsPath);
|
|
4140
|
+
const reinforced = suggestCount >= 3;
|
|
4141
|
+
const message = reinforced ? `Crystallize "${candidate.name}"? (suggested ${suggestCount}\xD7 across sessions \u2014 high confidence)` : `Crystallize "${candidate.name}" as a reusable skill?`;
|
|
3595
4142
|
const choice = await p2.select({
|
|
3596
|
-
message
|
|
4143
|
+
message,
|
|
3597
4144
|
options: [
|
|
3598
|
-
{ value: "accept", label: "Yes \u2014 write to ~/.askill/skills.md" },
|
|
4145
|
+
{ value: "accept", label: reinforced ? "Yes \u2014 recommended (seen multiple times)" : "Yes \u2014 write to ~/.askill/skills.md" },
|
|
3599
4146
|
{ value: "reject", label: "No \u2014 skip this one" },
|
|
3600
4147
|
{ value: "skip-all", label: "Skip all crystallization for this session" }
|
|
3601
4148
|
],
|
|
3602
|
-
initialValue: "reject"
|
|
4149
|
+
initialValue: reinforced ? "accept" : "reject"
|
|
3603
4150
|
});
|
|
3604
4151
|
if (p2.isCancel(choice) || choice === "skip-all") {
|
|
3605
4152
|
skipAll = true;
|
|
@@ -3627,12 +4174,46 @@ async function onSessionEnd(ctx, messages, sessionId, observationSession) {
|
|
|
3627
4174
|
},
|
|
3628
4175
|
logPath
|
|
3629
4176
|
);
|
|
4177
|
+
} else if (result.collidesWith) {
|
|
4178
|
+
const mergeChoice = await p2.select({
|
|
4179
|
+
message: `"${candidate.name}" collides with existing "${result.collidesWith}". Merge?`,
|
|
4180
|
+
options: [
|
|
4181
|
+
{ value: "merge", label: `Yes \u2014 replace "${result.collidesWith}" with updated version` },
|
|
4182
|
+
{ value: "skip", label: "No \u2014 keep existing" }
|
|
4183
|
+
],
|
|
4184
|
+
initialValue: "merge"
|
|
4185
|
+
});
|
|
4186
|
+
if (!p2.isCancel(mergeChoice) && mergeChoice === "merge") {
|
|
4187
|
+
const mergeResult = await mergeSkillInFile(
|
|
4188
|
+
candidate,
|
|
4189
|
+
result.collidesWith,
|
|
4190
|
+
skillsMdPath,
|
|
4191
|
+
postmortemFilename
|
|
4192
|
+
);
|
|
4193
|
+
if (mergeResult.written) {
|
|
4194
|
+
console.log(pc2.green(` \u2713 Merged: ${candidate.name} (replaced "${result.collidesWith}")`));
|
|
4195
|
+
await appendCrystallizationLog(
|
|
4196
|
+
{
|
|
4197
|
+
name: candidate.name,
|
|
4198
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4199
|
+
fromPostmortem: postmortemFilename,
|
|
4200
|
+
confidence: candidate.confidence,
|
|
4201
|
+
triggers: candidate.triggers
|
|
4202
|
+
},
|
|
4203
|
+
logPath
|
|
4204
|
+
);
|
|
4205
|
+
} else {
|
|
4206
|
+
console.log(pc2.yellow(` \u2298 Merge failed: ${mergeResult.reason}`));
|
|
4207
|
+
}
|
|
4208
|
+
} else {
|
|
4209
|
+
console.log(pc2.dim(` Kept existing: ${result.collidesWith}`));
|
|
4210
|
+
}
|
|
3630
4211
|
} else {
|
|
3631
4212
|
console.log(pc2.yellow(` \u2298 Could not crystallize: ${result.reason}`));
|
|
3632
4213
|
}
|
|
3633
4214
|
} else {
|
|
3634
4215
|
console.log(pc2.dim(` Skipped: ${candidate.name}`));
|
|
3635
|
-
await appendRejection(candidate, postmortemFilename,
|
|
4216
|
+
await appendRejection(candidate, postmortemFilename, rejectionsPath2);
|
|
3636
4217
|
}
|
|
3637
4218
|
}
|
|
3638
4219
|
}
|
|
@@ -3795,43 +4376,43 @@ async function delegatePipeline(steps, initialInput, client, mcpManager, options
|
|
|
3795
4376
|
}
|
|
3796
4377
|
|
|
3797
4378
|
// src/teams.ts
|
|
3798
|
-
import
|
|
3799
|
-
import
|
|
3800
|
-
import
|
|
4379
|
+
import fs15 from "fs";
|
|
4380
|
+
import path15 from "path";
|
|
4381
|
+
import os14 from "os";
|
|
3801
4382
|
import pc4 from "picocolors";
|
|
3802
4383
|
function getTeamsDir() {
|
|
3803
|
-
return
|
|
4384
|
+
return path15.join(os14.homedir(), ".acore", "teams");
|
|
3804
4385
|
}
|
|
3805
4386
|
function ensureTeamsDir() {
|
|
3806
4387
|
const dir = getTeamsDir();
|
|
3807
|
-
if (!
|
|
4388
|
+
if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
|
|
3808
4389
|
return dir;
|
|
3809
4390
|
}
|
|
3810
4391
|
function teamPath(name) {
|
|
3811
4392
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
3812
|
-
return
|
|
4393
|
+
return path15.join(ensureTeamsDir(), `${slug}.json`);
|
|
3813
4394
|
}
|
|
3814
4395
|
function createTeam(team) {
|
|
3815
4396
|
const fp = teamPath(team.name);
|
|
3816
|
-
|
|
4397
|
+
fs15.writeFileSync(fp, JSON.stringify(team, null, 2), "utf-8");
|
|
3817
4398
|
}
|
|
3818
4399
|
function loadTeam(name) {
|
|
3819
4400
|
const fp = teamPath(name);
|
|
3820
|
-
if (!
|
|
4401
|
+
if (!fs15.existsSync(fp)) return null;
|
|
3821
4402
|
try {
|
|
3822
|
-
return JSON.parse(
|
|
4403
|
+
return JSON.parse(fs15.readFileSync(fp, "utf-8"));
|
|
3823
4404
|
} catch {
|
|
3824
4405
|
return null;
|
|
3825
4406
|
}
|
|
3826
4407
|
}
|
|
3827
4408
|
function listTeams() {
|
|
3828
4409
|
const dir = getTeamsDir();
|
|
3829
|
-
if (!
|
|
4410
|
+
if (!fs15.existsSync(dir)) return [];
|
|
3830
4411
|
const teams = [];
|
|
3831
|
-
for (const file of
|
|
4412
|
+
for (const file of fs15.readdirSync(dir)) {
|
|
3832
4413
|
if (!file.endsWith(".json")) continue;
|
|
3833
4414
|
try {
|
|
3834
|
-
const content =
|
|
4415
|
+
const content = fs15.readFileSync(path15.join(dir, file), "utf-8");
|
|
3835
4416
|
teams.push(JSON.parse(content));
|
|
3836
4417
|
} catch {
|
|
3837
4418
|
}
|
|
@@ -3840,8 +4421,8 @@ function listTeams() {
|
|
|
3840
4421
|
}
|
|
3841
4422
|
function deleteTeam(name) {
|
|
3842
4423
|
const fp = teamPath(name);
|
|
3843
|
-
if (!
|
|
3844
|
-
|
|
4424
|
+
if (!fs15.existsSync(fp)) return false;
|
|
4425
|
+
fs15.unlinkSync(fp);
|
|
3845
4426
|
return true;
|
|
3846
4427
|
}
|
|
3847
4428
|
async function runTeam(team, task, client, mcpManager, tools) {
|
|
@@ -4067,23 +4648,23 @@ var BUILT_IN_TEAMS = [
|
|
|
4067
4648
|
];
|
|
4068
4649
|
|
|
4069
4650
|
// src/plans.ts
|
|
4070
|
-
import
|
|
4071
|
-
import
|
|
4072
|
-
import
|
|
4651
|
+
import fs16 from "fs";
|
|
4652
|
+
import path16 from "path";
|
|
4653
|
+
import os15 from "os";
|
|
4073
4654
|
function getPlansDir() {
|
|
4074
|
-
const localDir =
|
|
4075
|
-
const localAcore =
|
|
4076
|
-
if (
|
|
4077
|
-
return
|
|
4655
|
+
const localDir = path16.join(process.cwd(), ".acore", "plans");
|
|
4656
|
+
const localAcore = path16.join(process.cwd(), ".acore");
|
|
4657
|
+
if (fs16.existsSync(localAcore)) return localDir;
|
|
4658
|
+
return path16.join(os15.homedir(), ".acore", "plans");
|
|
4078
4659
|
}
|
|
4079
4660
|
function ensurePlansDir() {
|
|
4080
4661
|
const dir = getPlansDir();
|
|
4081
|
-
if (!
|
|
4662
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
4082
4663
|
return dir;
|
|
4083
4664
|
}
|
|
4084
4665
|
function planPath(name) {
|
|
4085
4666
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4086
|
-
return
|
|
4667
|
+
return path16.join(ensurePlansDir(), `${slug}.md`);
|
|
4087
4668
|
}
|
|
4088
4669
|
function serializePlan(plan) {
|
|
4089
4670
|
const lines = [];
|
|
@@ -4109,7 +4690,7 @@ function parsePlan(content, filePath) {
|
|
|
4109
4690
|
const createdMatch = content.match(/\*\*Created:\*\*\s*(.+)/);
|
|
4110
4691
|
const updatedMatch = content.match(/\*\*Updated:\*\*\s*(.+)/);
|
|
4111
4692
|
const activeMatch = content.match(/\*\*Active:\*\*\s*(.+)/);
|
|
4112
|
-
const name = nameMatch?.[1]?.trim() ||
|
|
4693
|
+
const name = nameMatch?.[1]?.trim() || path16.basename(filePath, ".md");
|
|
4113
4694
|
const goal = goalMatch?.[1]?.trim() || "";
|
|
4114
4695
|
const createdAt = createdMatch?.[1]?.trim() || "";
|
|
4115
4696
|
const updatedAt = updatedMatch?.[1]?.trim() || "";
|
|
@@ -4151,22 +4732,22 @@ function createPlan(name, goal, steps) {
|
|
|
4151
4732
|
}
|
|
4152
4733
|
function savePlan(plan) {
|
|
4153
4734
|
const fp = planPath(plan.name);
|
|
4154
|
-
|
|
4735
|
+
fs16.writeFileSync(fp, serializePlan(plan), "utf-8");
|
|
4155
4736
|
}
|
|
4156
4737
|
function loadPlan(name) {
|
|
4157
4738
|
const fp = planPath(name);
|
|
4158
|
-
if (!
|
|
4159
|
-
const content =
|
|
4739
|
+
if (!fs16.existsSync(fp)) return null;
|
|
4740
|
+
const content = fs16.readFileSync(fp, "utf-8");
|
|
4160
4741
|
return parsePlan(content, fp);
|
|
4161
4742
|
}
|
|
4162
4743
|
function listPlans() {
|
|
4163
4744
|
const dir = getPlansDir();
|
|
4164
|
-
if (!
|
|
4745
|
+
if (!fs16.existsSync(dir)) return [];
|
|
4165
4746
|
const plans = [];
|
|
4166
|
-
for (const file of
|
|
4747
|
+
for (const file of fs16.readdirSync(dir)) {
|
|
4167
4748
|
if (!file.endsWith(".md")) continue;
|
|
4168
|
-
const fp =
|
|
4169
|
-
const content =
|
|
4749
|
+
const fp = path16.join(dir, file);
|
|
4750
|
+
const content = fs16.readFileSync(fp, "utf-8");
|
|
4170
4751
|
const plan = parsePlan(content, fp);
|
|
4171
4752
|
if (plan) plans.push(plan);
|
|
4172
4753
|
}
|
|
@@ -4261,6 +4842,7 @@ function progressBar(pct) {
|
|
|
4261
4842
|
}
|
|
4262
4843
|
|
|
4263
4844
|
// src/commands.ts
|
|
4845
|
+
init_user_model();
|
|
4264
4846
|
import {
|
|
4265
4847
|
getIdentity as acoreGetIdentity,
|
|
4266
4848
|
updateSection as acoreUpdateSection,
|
|
@@ -4275,10 +4857,10 @@ import {
|
|
|
4275
4857
|
} from "@aman_asmuei/arules-core";
|
|
4276
4858
|
var AGENT_SCOPE = process.env.AMAN_AGENT_SCOPE ?? "dev:agent";
|
|
4277
4859
|
function readEcosystemFile(filePath, label) {
|
|
4278
|
-
if (!
|
|
4860
|
+
if (!fs17.existsSync(filePath)) {
|
|
4279
4861
|
return pc5.dim(`No ${label} file found at ${filePath}`);
|
|
4280
4862
|
}
|
|
4281
|
-
return
|
|
4863
|
+
return fs17.readFileSync(filePath, "utf-8").trim();
|
|
4282
4864
|
}
|
|
4283
4865
|
function parseCommand(input) {
|
|
4284
4866
|
const trimmed = input.trim();
|
|
@@ -4356,24 +4938,76 @@ async function handleIdentityCommand(action, args, _ctx) {
|
|
|
4356
4938
|
}
|
|
4357
4939
|
}
|
|
4358
4940
|
if (action === "dynamics") {
|
|
4941
|
+
if (args.includes("--json")) {
|
|
4942
|
+
const model2 = await loadUserModel();
|
|
4943
|
+
if (!model2) return { handled: true, output: pc5.dim("No user model yet. Complete a few sessions first.") };
|
|
4944
|
+
return { handled: true, output: JSON.stringify(model2, null, 2) };
|
|
4945
|
+
}
|
|
4946
|
+
if (args.includes("--reset")) {
|
|
4947
|
+
const modelPath = defaultModelPath();
|
|
4948
|
+
if (fs17.existsSync(modelPath)) {
|
|
4949
|
+
fs17.unlinkSync(modelPath);
|
|
4950
|
+
return { handled: true, output: pc5.green("User model reset. Starting fresh.") };
|
|
4951
|
+
}
|
|
4952
|
+
return { handled: true, output: pc5.dim("No user model to reset.") };
|
|
4953
|
+
}
|
|
4359
4954
|
const updates = {};
|
|
4360
4955
|
for (const arg of args) {
|
|
4361
4956
|
const eq = arg.indexOf("=");
|
|
4362
4957
|
if (eq > 0) updates[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
4363
4958
|
}
|
|
4364
|
-
if (
|
|
4365
|
-
|
|
4959
|
+
if (Object.keys(updates).length > 0) {
|
|
4960
|
+
try {
|
|
4961
|
+
await acoreUpdateDynamics({
|
|
4962
|
+
energy: updates.energy,
|
|
4963
|
+
activeMode: updates.mode,
|
|
4964
|
+
currentRead: updates.read
|
|
4965
|
+
}, AGENT_SCOPE);
|
|
4966
|
+
return { handled: true, output: `Dynamics updated: ${Object.entries(updates).map(([k, v]) => `${k}=${v}`).join(", ")}` };
|
|
4967
|
+
} catch (err) {
|
|
4968
|
+
return { handled: true, output: pc5.red(`Dynamics error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4969
|
+
}
|
|
4366
4970
|
}
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
activeMode: updates.mode,
|
|
4371
|
-
currentRead: updates.read
|
|
4372
|
-
}, AGENT_SCOPE);
|
|
4373
|
-
return { handled: true, output: `Dynamics updated: ${Object.entries(updates).map(([k, v]) => `${k}=${v}`).join(", ")}` };
|
|
4374
|
-
} catch (err) {
|
|
4375
|
-
return { handled: true, output: pc5.red(`Dynamics error: ${err instanceof Error ? err.message : String(err)}`) };
|
|
4971
|
+
const model = await loadUserModel();
|
|
4972
|
+
if (!model) {
|
|
4973
|
+
return { handled: true, output: pc5.dim("No user model yet. Complete a few sessions to start building your profile.") };
|
|
4376
4974
|
}
|
|
4975
|
+
const p4 = model.profile;
|
|
4976
|
+
const trustBar = "\u2588".repeat(Math.round(p4.trustScore * 10)) + "\u2591".repeat(10 - Math.round(p4.trustScore * 10));
|
|
4977
|
+
const frustBar = "\u2588".repeat(Math.round(p4.baselineFrustration * 10)) + "\u2591".repeat(10 - Math.round(p4.baselineFrustration * 10));
|
|
4978
|
+
const lines = [
|
|
4979
|
+
pc5.bold(" Dynamic User Model"),
|
|
4980
|
+
"",
|
|
4981
|
+
` ${pc5.cyan("Trust")} ${trustBar} ${(p4.trustScore * 100).toFixed(0)}% ${p4.trustTrajectory === "ascending" ? pc5.green("\u2191") : p4.trustTrajectory === "declining" ? pc5.red("\u2193") : "\u2192"}`,
|
|
4982
|
+
` ${pc5.cyan("Sessions")} ${p4.totalSessions} total (${model.sessions.length} in window)`,
|
|
4983
|
+
` ${pc5.cyan("Sentiment")} ${frustBar} frustration baseline ${p4.sentimentTrend === "improving" ? pc5.green("improving") : p4.sentimentTrend === "worsening" ? pc5.red("worsening") : "stable"}`,
|
|
4984
|
+
"",
|
|
4985
|
+
` ${pc5.cyan("Preferred")} ${p4.preferredTimePeriod} (${Object.entries(p4.energyDistribution).map(([k, v]) => `${k}: ${v}`).join(", ")})`,
|
|
4986
|
+
` ${pc5.cyan("Avg session")} ${p4.avgSessionMinutes.toFixed(0)} min, ${p4.avgTurnsPerSession.toFixed(0)} turns ${p4.engagementTrend === "increasing" ? pc5.green("\u2191") : p4.engagementTrend === "decreasing" ? pc5.red("\u2193") : "\u2192"}`
|
|
4987
|
+
];
|
|
4988
|
+
if (p4.totalSessions >= 10) {
|
|
4989
|
+
const corrs = [];
|
|
4990
|
+
if (Math.abs(p4.frustrationCorrelations.toolErrors) > 0.3) {
|
|
4991
|
+
corrs.push(`tool errors (${p4.frustrationCorrelations.toolErrors.toFixed(2)})`);
|
|
4992
|
+
}
|
|
4993
|
+
if (Math.abs(p4.frustrationCorrelations.longSessions) > 0.3) {
|
|
4994
|
+
corrs.push(`long sessions (${p4.frustrationCorrelations.longSessions.toFixed(2)})`);
|
|
4995
|
+
}
|
|
4996
|
+
if (Math.abs(p4.frustrationCorrelations.lateNight) > 0.3) {
|
|
4997
|
+
corrs.push(`late night (${p4.frustrationCorrelations.lateNight.toFixed(2)})`);
|
|
4998
|
+
}
|
|
4999
|
+
if (corrs.length > 0) {
|
|
5000
|
+
lines.push(` ${pc5.cyan("Frustration")} correlates with: ${corrs.join(", ")}`);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
const nudgeKeys = Object.keys(p4.nudgeStats);
|
|
5004
|
+
if (nudgeKeys.length > 0) {
|
|
5005
|
+
lines.push("");
|
|
5006
|
+
lines.push(` ${pc5.cyan("Nudges")} ${nudgeKeys.map((k) => `${k}: ${p4.nudgeStats[k].fired}\xD7`).join(", ")}`);
|
|
5007
|
+
}
|
|
5008
|
+
lines.push("");
|
|
5009
|
+
lines.push(pc5.dim(` Use --json for raw data, --reset to start fresh`));
|
|
5010
|
+
return { handled: true, output: lines.join("\n") };
|
|
4377
5011
|
}
|
|
4378
5012
|
if (action === "summary") {
|
|
4379
5013
|
try {
|
|
@@ -4397,7 +5031,10 @@ async function handleIdentityCommand(action, args, _ctx) {
|
|
|
4397
5031
|
pc5.bold("Identity commands:"),
|
|
4398
5032
|
` ${pc5.cyan("/identity")} View current identity`,
|
|
4399
5033
|
` ${pc5.cyan("/identity update")} <section> Update a section`,
|
|
5034
|
+
` ${pc5.cyan("/identity dynamics")} View user model (trust, sentiment, patterns)`,
|
|
4400
5035
|
` ${pc5.cyan("/identity dynamics")} key=val Update dynamic fields (energy, mode, read)`,
|
|
5036
|
+
` ${pc5.cyan("/identity dynamics")} --json Raw JSON user model`,
|
|
5037
|
+
` ${pc5.cyan("/identity dynamics")} --reset Reset user model`,
|
|
4401
5038
|
` ${pc5.cyan("/identity summary")} Show structured identity summary`
|
|
4402
5039
|
].join("\n")
|
|
4403
5040
|
};
|
|
@@ -4553,9 +5190,9 @@ ${result.violations.map((v) => ` - ${v}`).join("\n")}`)
|
|
|
4553
5190
|
};
|
|
4554
5191
|
}
|
|
4555
5192
|
async function handleWorkflowsCommand(action, args, ctx) {
|
|
4556
|
-
const home2 =
|
|
5193
|
+
const home2 = os16.homedir();
|
|
4557
5194
|
if (!action) {
|
|
4558
|
-
const content = readEcosystemFile(
|
|
5195
|
+
const content = readEcosystemFile(path17.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
4559
5196
|
return { handled: true, output: content };
|
|
4560
5197
|
}
|
|
4561
5198
|
if (action === "add") {
|
|
@@ -4577,7 +5214,7 @@ async function handleWorkflowsCommand(action, args, ctx) {
|
|
|
4577
5214
|
return { handled: true, output: pc5.yellow("Usage: /workflows get <name>") };
|
|
4578
5215
|
}
|
|
4579
5216
|
const name = args.join(" ").toLowerCase();
|
|
4580
|
-
const raw = readEcosystemFile(
|
|
5217
|
+
const raw = readEcosystemFile(path17.join(home2, ".aflow", "flow.md"), "workflows (aflow)");
|
|
4581
5218
|
if (raw.startsWith("No ")) {
|
|
4582
5219
|
return { handled: true, output: raw };
|
|
4583
5220
|
}
|
|
@@ -4636,12 +5273,12 @@ async function handleToolsCommand(action, args, _ctx) {
|
|
|
4636
5273
|
return { handled: true, output: pc5.yellow("Usage: /tools search <query...>") };
|
|
4637
5274
|
}
|
|
4638
5275
|
const query = args.join(" ").toLowerCase();
|
|
4639
|
-
const home2 =
|
|
4640
|
-
const toolsFile =
|
|
4641
|
-
if (!
|
|
5276
|
+
const home2 = os16.homedir();
|
|
5277
|
+
const toolsFile = path17.join(home2, ".akit", "tools.md");
|
|
5278
|
+
if (!fs17.existsSync(toolsFile)) {
|
|
4642
5279
|
return { handled: true, output: pc5.dim(`No tools file found. Use 'npx @aman_asmuei/akit search ${args.join(" ")}' to search the registry.`) };
|
|
4643
5280
|
}
|
|
4644
|
-
const raw =
|
|
5281
|
+
const raw = fs17.readFileSync(toolsFile, "utf-8").trim();
|
|
4645
5282
|
const lines = raw.split("\n");
|
|
4646
5283
|
const matches = lines.filter((l) => l.toLowerCase().includes(query));
|
|
4647
5284
|
if (matches.length === 0) {
|
|
@@ -4652,9 +5289,9 @@ async function handleToolsCommand(action, args, _ctx) {
|
|
|
4652
5289
|
return handleAkitCommand(action, args);
|
|
4653
5290
|
}
|
|
4654
5291
|
async function handleSkillsCommand(action, args, ctx) {
|
|
4655
|
-
const home2 =
|
|
5292
|
+
const home2 = os16.homedir();
|
|
4656
5293
|
if (!action) {
|
|
4657
|
-
const content = readEcosystemFile(
|
|
5294
|
+
const content = readEcosystemFile(path17.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
4658
5295
|
return { handled: true, output: content };
|
|
4659
5296
|
}
|
|
4660
5297
|
if (action === "install") {
|
|
@@ -4676,8 +5313,8 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4676
5313
|
return { handled: true, output: pc5.yellow("Usage: /skills search <query...>") };
|
|
4677
5314
|
}
|
|
4678
5315
|
const query = args.join(" ").toLowerCase();
|
|
4679
|
-
const home3 =
|
|
4680
|
-
const raw = readEcosystemFile(
|
|
5316
|
+
const home3 = os16.homedir();
|
|
5317
|
+
const raw = readEcosystemFile(path17.join(home3, ".askill", "skills.md"), "skills (askill)");
|
|
4681
5318
|
if (raw.startsWith("No ")) {
|
|
4682
5319
|
return { handled: true, output: raw };
|
|
4683
5320
|
}
|
|
@@ -4691,17 +5328,40 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4691
5328
|
if (action === "list") {
|
|
4692
5329
|
const autoOnly = args.includes("--auto");
|
|
4693
5330
|
if (autoOnly) {
|
|
4694
|
-
const logPath =
|
|
5331
|
+
const logPath = path17.join(os16.homedir(), ".aman-agent", "crystallization-log.json");
|
|
4695
5332
|
try {
|
|
4696
|
-
const content2 =
|
|
5333
|
+
const content2 = fs17.readFileSync(logPath, "utf-8");
|
|
4697
5334
|
const entries = JSON.parse(content2);
|
|
4698
5335
|
if (entries.length === 0) {
|
|
4699
5336
|
return { handled: true, output: pc5.dim("No crystallized skills yet.") };
|
|
4700
5337
|
}
|
|
5338
|
+
const suggestionsPath = path17.join(os16.homedir(), ".aman-agent", "crystallization-suggestions.json");
|
|
5339
|
+
let sugCounts = {};
|
|
5340
|
+
try {
|
|
5341
|
+
const sc = fs17.readFileSync(suggestionsPath, "utf-8");
|
|
5342
|
+
sugCounts = JSON.parse(sc);
|
|
5343
|
+
} catch {
|
|
5344
|
+
}
|
|
5345
|
+
let versionCounts = {};
|
|
5346
|
+
try {
|
|
5347
|
+
const skillsContent = fs17.readFileSync(path17.join(os16.homedir(), ".askill", "skills.md"), "utf-8");
|
|
5348
|
+
const versionRe = /^# (.+)\.v(\d+)$/gm;
|
|
5349
|
+
let vMatch;
|
|
5350
|
+
while ((vMatch = versionRe.exec(skillsContent)) !== null) {
|
|
5351
|
+
const skillHeading = vMatch[1].toLowerCase().replace(/ /g, "-");
|
|
5352
|
+
const ver = parseInt(vMatch[2], 10);
|
|
5353
|
+
versionCounts[skillHeading] = Math.max(versionCounts[skillHeading] || 0, ver);
|
|
5354
|
+
}
|
|
5355
|
+
} catch {
|
|
5356
|
+
}
|
|
4701
5357
|
const lines = [pc5.bold(`Crystallized skills (${entries.length}):`)];
|
|
4702
5358
|
for (const entry of entries) {
|
|
4703
5359
|
const date = entry.createdAt.slice(0, 10);
|
|
4704
|
-
|
|
5360
|
+
const count = sugCounts[entry.name];
|
|
5361
|
+
const reinforced = count && count >= 3 ? pc5.green(` \u2605 reinforced (${count}\xD7)`) : "";
|
|
5362
|
+
const versions = versionCounts[entry.name];
|
|
5363
|
+
const versionLabel = versions ? pc5.dim(` [v${versions + 1}]`) : "";
|
|
5364
|
+
lines.push(` ${pc5.cyan(entry.name)} (${date}, conf ${entry.confidence})${reinforced}${versionLabel}`);
|
|
4705
5365
|
lines.push(pc5.dim(` triggers: ${entry.triggers.join(", ")}`));
|
|
4706
5366
|
}
|
|
4707
5367
|
return { handled: true, output: lines.join("\n") };
|
|
@@ -4709,13 +5369,13 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4709
5369
|
return { handled: true, output: pc5.dim("No crystallized skills yet.") };
|
|
4710
5370
|
}
|
|
4711
5371
|
}
|
|
4712
|
-
const content = readEcosystemFile(
|
|
5372
|
+
const content = readEcosystemFile(path17.join(home2, ".askill", "skills.md"), "skills (askill)");
|
|
4713
5373
|
return { handled: true, output: content };
|
|
4714
5374
|
}
|
|
4715
5375
|
if (action === "crystallize") {
|
|
4716
|
-
const pmDir =
|
|
5376
|
+
const pmDir = path17.join(os16.homedir(), ".acore", "postmortems");
|
|
4717
5377
|
try {
|
|
4718
|
-
const files =
|
|
5378
|
+
const files = fs17.readdirSync(pmDir);
|
|
4719
5379
|
const jsonFiles = files.filter((f) => f.endsWith(".json")).sort().reverse();
|
|
4720
5380
|
if (jsonFiles.length === 0) {
|
|
4721
5381
|
return {
|
|
@@ -4724,7 +5384,7 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4724
5384
|
};
|
|
4725
5385
|
}
|
|
4726
5386
|
const latest = jsonFiles[0];
|
|
4727
|
-
const content =
|
|
5387
|
+
const content = fs17.readFileSync(path17.join(pmDir, latest), "utf-8");
|
|
4728
5388
|
const report = JSON.parse(content);
|
|
4729
5389
|
if (!report.crystallizationCandidates || report.crystallizationCandidates.length === 0) {
|
|
4730
5390
|
return {
|
|
@@ -4732,8 +5392,8 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4732
5392
|
output: pc5.dim(`No crystallization candidates in the most recent post-mortem (${latest}). Run a longer session or wait for the next auto-postmortem.`)
|
|
4733
5393
|
};
|
|
4734
5394
|
}
|
|
4735
|
-
const skillsMdPath =
|
|
4736
|
-
const logPath =
|
|
5395
|
+
const skillsMdPath = path17.join(os16.homedir(), ".askill", "skills.md");
|
|
5396
|
+
const logPath = path17.join(os16.homedir(), ".aman-agent", "crystallization-log.json");
|
|
4737
5397
|
const postmortemFilename = latest.replace(/\.json$/, ".md");
|
|
4738
5398
|
const lines = [
|
|
4739
5399
|
pc5.bold(`Found ${report.crystallizationCandidates.length} candidate(s) in ${latest}:`)
|
|
@@ -4790,9 +5450,9 @@ async function handleSkillsCommand(action, args, ctx) {
|
|
|
4790
5450
|
return { handled: true, output: pc5.yellow(`Unknown action: /skills ${action}. Try /skills --help`) };
|
|
4791
5451
|
}
|
|
4792
5452
|
async function handleEvalCommand(action, args, ctx) {
|
|
4793
|
-
const home2 =
|
|
5453
|
+
const home2 = os16.homedir();
|
|
4794
5454
|
if (!action) {
|
|
4795
|
-
const content = readEcosystemFile(
|
|
5455
|
+
const content = readEcosystemFile(path17.join(home2, ".aeval", "eval.md"), "evaluation (aeval)");
|
|
4796
5456
|
return { handled: true, output: content };
|
|
4797
5457
|
}
|
|
4798
5458
|
if (action === "milestone") {
|
|
@@ -4804,11 +5464,11 @@ async function handleEvalCommand(action, args, ctx) {
|
|
|
4804
5464
|
return { handled: true, output };
|
|
4805
5465
|
}
|
|
4806
5466
|
if (action === "report") {
|
|
4807
|
-
const evalFile =
|
|
4808
|
-
if (!
|
|
5467
|
+
const evalFile = path17.join(home2, ".aeval", "eval.md");
|
|
5468
|
+
if (!fs17.existsSync(evalFile)) {
|
|
4809
5469
|
return { handled: true, output: pc5.dim("No eval report found. Log milestones with /eval milestone <text>.") };
|
|
4810
5470
|
}
|
|
4811
|
-
const content =
|
|
5471
|
+
const content = fs17.readFileSync(evalFile, "utf-8").trim();
|
|
4812
5472
|
return { handled: true, output: [pc5.bold("Eval Report"), "", content].join("\n") };
|
|
4813
5473
|
}
|
|
4814
5474
|
return { handled: true, output: pc5.yellow(`Unknown action: /eval ${action}. Use /eval, /eval report, or /eval milestone <text>.`) };
|
|
@@ -5332,10 +5992,10 @@ function handleSave() {
|
|
|
5332
5992
|
}
|
|
5333
5993
|
function handleReset(action) {
|
|
5334
5994
|
const dirs = {
|
|
5335
|
-
config:
|
|
5336
|
-
memory:
|
|
5337
|
-
identity:
|
|
5338
|
-
rules:
|
|
5995
|
+
config: path17.join(os16.homedir(), ".aman-agent"),
|
|
5996
|
+
memory: path17.join(os16.homedir(), ".amem"),
|
|
5997
|
+
identity: path17.join(os16.homedir(), ".acore"),
|
|
5998
|
+
rules: path17.join(os16.homedir(), ".arules")
|
|
5339
5999
|
};
|
|
5340
6000
|
if (action === "help" || !action) {
|
|
5341
6001
|
return {
|
|
@@ -5360,15 +6020,15 @@ function handleReset(action) {
|
|
|
5360
6020
|
const removed = [];
|
|
5361
6021
|
for (const target of targets) {
|
|
5362
6022
|
const dir = dirs[target];
|
|
5363
|
-
if (
|
|
5364
|
-
|
|
6023
|
+
if (fs17.existsSync(dir)) {
|
|
6024
|
+
fs17.rmSync(dir, { recursive: true, force: true });
|
|
5365
6025
|
removed.push(target);
|
|
5366
6026
|
}
|
|
5367
6027
|
}
|
|
5368
6028
|
if (targets.includes("config")) {
|
|
5369
6029
|
const configDir = dirs.config;
|
|
5370
|
-
|
|
5371
|
-
|
|
6030
|
+
fs17.mkdirSync(configDir, { recursive: true });
|
|
6031
|
+
fs17.writeFileSync(path17.join(configDir, ".reconfig"), "", "utf-8");
|
|
5372
6032
|
}
|
|
5373
6033
|
if (removed.length === 0) {
|
|
5374
6034
|
return { handled: true, output: pc5.dim("Nothing to reset \u2014 directories don't exist.") };
|
|
@@ -5385,7 +6045,7 @@ function handleReset(action) {
|
|
|
5385
6045
|
function handleUpdate() {
|
|
5386
6046
|
try {
|
|
5387
6047
|
const current = execFileSync3("npm", ["view", "@aman_asmuei/aman-agent", "version"], { encoding: "utf-8" }).trim();
|
|
5388
|
-
const local = true ? "0.
|
|
6048
|
+
const local = true ? "0.28.0" : "unknown";
|
|
5389
6049
|
if (current === local) {
|
|
5390
6050
|
return { handled: true, output: `${pc5.green("Up to date")} \u2014 v${local}` };
|
|
5391
6051
|
}
|
|
@@ -5429,11 +6089,11 @@ function handleExportCommand() {
|
|
|
5429
6089
|
return { handled: true, exportConversation: true };
|
|
5430
6090
|
}
|
|
5431
6091
|
function handleDebugCommand() {
|
|
5432
|
-
const logPath =
|
|
5433
|
-
if (!
|
|
6092
|
+
const logPath = path17.join(os16.homedir(), ".aman-agent", "debug.log");
|
|
6093
|
+
if (!fs17.existsSync(logPath)) {
|
|
5434
6094
|
return { handled: true, output: pc5.dim("No debug log found.") };
|
|
5435
6095
|
}
|
|
5436
|
-
const content =
|
|
6096
|
+
const content = fs17.readFileSync(logPath, "utf-8");
|
|
5437
6097
|
const lines = content.trim().split("\n");
|
|
5438
6098
|
const last20 = lines.slice(-20).join("\n");
|
|
5439
6099
|
return { handled: true, output: pc5.bold("Debug Log (last 20 entries):\n") + pc5.dim(last20) };
|
|
@@ -5631,7 +6291,7 @@ ${result.response}`
|
|
|
5631
6291
|
};
|
|
5632
6292
|
}
|
|
5633
6293
|
function handleProfileCommand(action, args) {
|
|
5634
|
-
const profilesDir =
|
|
6294
|
+
const profilesDir = path17.join(os16.homedir(), ".acore", "profiles");
|
|
5635
6295
|
if (action === "me") {
|
|
5636
6296
|
const user = loadUserIdentity();
|
|
5637
6297
|
if (!user) {
|
|
@@ -5699,8 +6359,8 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
5699
6359
|
};
|
|
5700
6360
|
}
|
|
5701
6361
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
5702
|
-
const profileDir =
|
|
5703
|
-
if (
|
|
6362
|
+
const profileDir = path17.join(profilesDir, slug);
|
|
6363
|
+
if (fs17.existsSync(profileDir)) {
|
|
5704
6364
|
return { handled: true, output: pc5.yellow(`Profile already exists: ${slug}`) };
|
|
5705
6365
|
}
|
|
5706
6366
|
const builtIn = BUILT_IN_PROFILES.find((t) => t.name === slug);
|
|
@@ -5716,16 +6376,16 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
5716
6376
|
Use: aman-agent --profile ${slug}`
|
|
5717
6377
|
};
|
|
5718
6378
|
}
|
|
5719
|
-
|
|
5720
|
-
const globalCore =
|
|
5721
|
-
if (
|
|
5722
|
-
let content =
|
|
6379
|
+
fs17.mkdirSync(profileDir, { recursive: true });
|
|
6380
|
+
const globalCore = path17.join(os16.homedir(), ".acore", "core.md");
|
|
6381
|
+
if (fs17.existsSync(globalCore)) {
|
|
6382
|
+
let content = fs17.readFileSync(globalCore, "utf-8");
|
|
5723
6383
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
5724
6384
|
content = content.replace(/^# .+$/m, `# ${aiName}`);
|
|
5725
|
-
|
|
6385
|
+
fs17.writeFileSync(path17.join(profileDir, "core.md"), content, "utf-8");
|
|
5726
6386
|
} else {
|
|
5727
6387
|
const aiName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
5728
|
-
|
|
6388
|
+
fs17.writeFileSync(path17.join(profileDir, "core.md"), `# ${aiName}
|
|
5729
6389
|
|
|
5730
6390
|
## Identity
|
|
5731
6391
|
- Role: ${aiName} is your AI companion
|
|
@@ -5738,7 +6398,7 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
5738
6398
|
return {
|
|
5739
6399
|
handled: true,
|
|
5740
6400
|
output: pc5.green(`Profile created: ${slug}`) + `
|
|
5741
|
-
Edit: ${
|
|
6401
|
+
Edit: ${path17.join(profileDir, "core.md")}
|
|
5742
6402
|
Use: aman-agent --profile ${slug}
|
|
5743
6403
|
|
|
5744
6404
|
${pc5.dim("Add rules.md or skills.md for profile-specific overrides.")}`
|
|
@@ -5747,9 +6407,9 @@ ${pc5.dim("Edit with: /profile edit")}` };
|
|
|
5747
6407
|
case "show": {
|
|
5748
6408
|
const name = args[0];
|
|
5749
6409
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile show <name>") };
|
|
5750
|
-
const profileDir =
|
|
5751
|
-
if (!
|
|
5752
|
-
const files =
|
|
6410
|
+
const profileDir = path17.join(profilesDir, name);
|
|
6411
|
+
if (!fs17.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
|
|
6412
|
+
const files = fs17.readdirSync(profileDir).filter((f) => f.endsWith(".md"));
|
|
5753
6413
|
const lines = files.map((f) => ` ${f}`);
|
|
5754
6414
|
return { handled: true, output: `Profile: ${pc5.bold(name)}
|
|
5755
6415
|
Files:
|
|
@@ -5758,9 +6418,9 @@ ${lines.join("\n")}` };
|
|
|
5758
6418
|
case "delete": {
|
|
5759
6419
|
const name = args[0];
|
|
5760
6420
|
if (!name) return { handled: true, output: pc5.yellow("Usage: /profile delete <name>") };
|
|
5761
|
-
const profileDir =
|
|
5762
|
-
if (!
|
|
5763
|
-
|
|
6421
|
+
const profileDir = path17.join(profilesDir, name);
|
|
6422
|
+
if (!fs17.existsSync(profileDir)) return { handled: true, output: pc5.red(`Profile not found: ${name}`) };
|
|
6423
|
+
fs17.rmSync(profileDir, { recursive: true });
|
|
5764
6424
|
return { handled: true, output: pc5.dim(`Profile deleted: ${name}`) };
|
|
5765
6425
|
}
|
|
5766
6426
|
case "help":
|
|
@@ -5978,10 +6638,10 @@ function handleShowcaseCommand(action, args) {
|
|
|
5978
6638
|
Or place it as a sibling directory to aman-agent.`
|
|
5979
6639
|
};
|
|
5980
6640
|
}
|
|
5981
|
-
const corePath =
|
|
6641
|
+
const corePath = path17.join(os16.homedir(), ".acore", "core.md");
|
|
5982
6642
|
let currentShowcase = null;
|
|
5983
|
-
if (
|
|
5984
|
-
const content =
|
|
6643
|
+
if (fs17.existsSync(corePath)) {
|
|
6644
|
+
const content = fs17.readFileSync(corePath, "utf-8");
|
|
5985
6645
|
const nameMatch = content.match(/^# (.+)/m);
|
|
5986
6646
|
if (nameMatch) {
|
|
5987
6647
|
const coreName = nameMatch[1].trim().toLowerCase();
|
|
@@ -6368,10 +7028,10 @@ ${summaryParts.slice(0, 20).join("\n")}
|
|
|
6368
7028
|
}
|
|
6369
7029
|
|
|
6370
7030
|
// src/skill-engine.ts
|
|
6371
|
-
import
|
|
7031
|
+
import fs18 from "fs";
|
|
6372
7032
|
import fsp from "fs/promises";
|
|
6373
|
-
import
|
|
6374
|
-
import
|
|
7033
|
+
import path18 from "path";
|
|
7034
|
+
import os17 from "os";
|
|
6375
7035
|
var SKILL_TRIGGERS = {
|
|
6376
7036
|
testing: ["test", "spec", "coverage", "tdd", "jest", "vitest", "mocha", "assert", "mock", "stub", "fixture", "e2e", "integration test", "unit test"],
|
|
6377
7037
|
"api-design": ["api", "endpoint", "rest", "graphql", "route", "controller", "middleware", "http", "request", "response", "status code", "pagination"],
|
|
@@ -6400,20 +7060,20 @@ async function loadRuntimeTriggers(skillsMdPath) {
|
|
|
6400
7060
|
return /* @__PURE__ */ new Map();
|
|
6401
7061
|
}
|
|
6402
7062
|
}
|
|
6403
|
-
var LEVEL_FILE =
|
|
7063
|
+
var LEVEL_FILE = path18.join(os17.homedir(), ".aman-agent", "skill-levels.json");
|
|
6404
7064
|
function loadSkillLevels() {
|
|
6405
7065
|
try {
|
|
6406
|
-
if (
|
|
6407
|
-
return JSON.parse(
|
|
7066
|
+
if (fs18.existsSync(LEVEL_FILE)) {
|
|
7067
|
+
return JSON.parse(fs18.readFileSync(LEVEL_FILE, "utf-8"));
|
|
6408
7068
|
}
|
|
6409
7069
|
} catch {
|
|
6410
7070
|
}
|
|
6411
7071
|
return {};
|
|
6412
7072
|
}
|
|
6413
7073
|
function saveSkillLevels(levels) {
|
|
6414
|
-
const dir =
|
|
6415
|
-
if (!
|
|
6416
|
-
|
|
7074
|
+
const dir = path18.dirname(LEVEL_FILE);
|
|
7075
|
+
if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
|
|
7076
|
+
fs18.writeFileSync(LEVEL_FILE, JSON.stringify(levels, null, 2), "utf-8");
|
|
6417
7077
|
}
|
|
6418
7078
|
function computeLevel(activations) {
|
|
6419
7079
|
if (activations >= 50) return { level: 5, label: "Expert" };
|
|
@@ -6455,6 +7115,169 @@ function matchSkills(userInput, installedSkillNames, runtimeTriggers = /* @__PUR
|
|
|
6455
7115
|
}
|
|
6456
7116
|
return Array.from(matched);
|
|
6457
7117
|
}
|
|
7118
|
+
var SEMANTIC_STOPWORDS = /* @__PURE__ */ new Set([
|
|
7119
|
+
"the",
|
|
7120
|
+
"a",
|
|
7121
|
+
"an",
|
|
7122
|
+
"is",
|
|
7123
|
+
"are",
|
|
7124
|
+
"was",
|
|
7125
|
+
"were",
|
|
7126
|
+
"be",
|
|
7127
|
+
"been",
|
|
7128
|
+
"being",
|
|
7129
|
+
"have",
|
|
7130
|
+
"has",
|
|
7131
|
+
"had",
|
|
7132
|
+
"do",
|
|
7133
|
+
"does",
|
|
7134
|
+
"did",
|
|
7135
|
+
"will",
|
|
7136
|
+
"would",
|
|
7137
|
+
"shall",
|
|
7138
|
+
"should",
|
|
7139
|
+
"may",
|
|
7140
|
+
"might",
|
|
7141
|
+
"must",
|
|
7142
|
+
"can",
|
|
7143
|
+
"could",
|
|
7144
|
+
"to",
|
|
7145
|
+
"of",
|
|
7146
|
+
"in",
|
|
7147
|
+
"for",
|
|
7148
|
+
"on",
|
|
7149
|
+
"with",
|
|
7150
|
+
"at",
|
|
7151
|
+
"by",
|
|
7152
|
+
"from",
|
|
7153
|
+
"as",
|
|
7154
|
+
"into",
|
|
7155
|
+
"through",
|
|
7156
|
+
"during",
|
|
7157
|
+
"before",
|
|
7158
|
+
"after",
|
|
7159
|
+
"above",
|
|
7160
|
+
"below",
|
|
7161
|
+
"between",
|
|
7162
|
+
"and",
|
|
7163
|
+
"but",
|
|
7164
|
+
"or",
|
|
7165
|
+
"nor",
|
|
7166
|
+
"not",
|
|
7167
|
+
"so",
|
|
7168
|
+
"yet",
|
|
7169
|
+
"both",
|
|
7170
|
+
"either",
|
|
7171
|
+
"neither",
|
|
7172
|
+
"each",
|
|
7173
|
+
"every",
|
|
7174
|
+
"all",
|
|
7175
|
+
"any",
|
|
7176
|
+
"few",
|
|
7177
|
+
"more",
|
|
7178
|
+
"most",
|
|
7179
|
+
"other",
|
|
7180
|
+
"some",
|
|
7181
|
+
"such",
|
|
7182
|
+
"no",
|
|
7183
|
+
"only",
|
|
7184
|
+
"own",
|
|
7185
|
+
"same",
|
|
7186
|
+
"than",
|
|
7187
|
+
"too",
|
|
7188
|
+
"very",
|
|
7189
|
+
"just",
|
|
7190
|
+
"because",
|
|
7191
|
+
"if",
|
|
7192
|
+
"when",
|
|
7193
|
+
"while",
|
|
7194
|
+
"how",
|
|
7195
|
+
"what",
|
|
7196
|
+
"which",
|
|
7197
|
+
"who",
|
|
7198
|
+
"whom",
|
|
7199
|
+
"this",
|
|
7200
|
+
"that",
|
|
7201
|
+
"these",
|
|
7202
|
+
"those",
|
|
7203
|
+
"i",
|
|
7204
|
+
"me",
|
|
7205
|
+
"my",
|
|
7206
|
+
"we",
|
|
7207
|
+
"us",
|
|
7208
|
+
"our",
|
|
7209
|
+
"you",
|
|
7210
|
+
"your",
|
|
7211
|
+
"he",
|
|
7212
|
+
"him",
|
|
7213
|
+
"his",
|
|
7214
|
+
"she",
|
|
7215
|
+
"her",
|
|
7216
|
+
"it",
|
|
7217
|
+
"its",
|
|
7218
|
+
"they",
|
|
7219
|
+
"them",
|
|
7220
|
+
"their"
|
|
7221
|
+
]);
|
|
7222
|
+
function tokenize(text3) {
|
|
7223
|
+
return text3.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !SEMANTIC_STOPWORDS.has(w));
|
|
7224
|
+
}
|
|
7225
|
+
function termFrequency(tokens) {
|
|
7226
|
+
const tf = /* @__PURE__ */ new Map();
|
|
7227
|
+
for (const t of tokens) {
|
|
7228
|
+
tf.set(t, (tf.get(t) || 0) + 1);
|
|
7229
|
+
}
|
|
7230
|
+
for (const [k, v] of tf) {
|
|
7231
|
+
tf.set(k, v / tokens.length);
|
|
7232
|
+
}
|
|
7233
|
+
return tf;
|
|
7234
|
+
}
|
|
7235
|
+
function cosineSimilarity2(a, b) {
|
|
7236
|
+
let dot = 0;
|
|
7237
|
+
let normA = 0;
|
|
7238
|
+
let normB = 0;
|
|
7239
|
+
for (const [k, v] of a) {
|
|
7240
|
+
normA += v * v;
|
|
7241
|
+
const bv = b.get(k);
|
|
7242
|
+
if (bv !== void 0) dot += v * bv;
|
|
7243
|
+
}
|
|
7244
|
+
for (const [, v] of b) {
|
|
7245
|
+
normB += v * v;
|
|
7246
|
+
}
|
|
7247
|
+
if (normA === 0 || normB === 0) return 0;
|
|
7248
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
7249
|
+
}
|
|
7250
|
+
function semanticSimilarity(userInput, triggers) {
|
|
7251
|
+
const inputTokens = tokenize(userInput);
|
|
7252
|
+
if (inputTokens.length === 0) return 0;
|
|
7253
|
+
const triggerTokens = triggers.flatMap((t) => tokenize(t));
|
|
7254
|
+
if (triggerTokens.length === 0) return 0;
|
|
7255
|
+
const inputTf = termFrequency(inputTokens);
|
|
7256
|
+
const triggerTf = termFrequency(triggerTokens);
|
|
7257
|
+
return cosineSimilarity2(inputTf, triggerTf);
|
|
7258
|
+
}
|
|
7259
|
+
var SEMANTIC_THRESHOLD = 0.15;
|
|
7260
|
+
function matchSkillsSemantic(userInput, installedSkillNames, runtimeTriggers = /* @__PURE__ */ new Map()) {
|
|
7261
|
+
const exact = matchSkills(userInput, installedSkillNames, runtimeTriggers);
|
|
7262
|
+
const matched = new Set(exact);
|
|
7263
|
+
for (const skillName of installedSkillNames) {
|
|
7264
|
+
if (matched.has(skillName)) continue;
|
|
7265
|
+
const triggers = SKILL_TRIGGERS[skillName];
|
|
7266
|
+
if (!triggers) continue;
|
|
7267
|
+
const sim = semanticSimilarity(userInput, triggers);
|
|
7268
|
+
if (sim >= SEMANTIC_THRESHOLD) {
|
|
7269
|
+
matched.add(skillName);
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7272
|
+
for (const [skillName, triggers] of runtimeTriggers) {
|
|
7273
|
+
if (matched.has(skillName)) continue;
|
|
7274
|
+
const sim = semanticSimilarity(userInput, triggers);
|
|
7275
|
+
if (sim >= SEMANTIC_THRESHOLD) {
|
|
7276
|
+
matched.add(skillName);
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
return Array.from(matched);
|
|
7280
|
+
}
|
|
6458
7281
|
function formatSkillContext(skillName, skillContent, level) {
|
|
6459
7282
|
let depthHint;
|
|
6460
7283
|
if (level.level >= 4) {
|
|
@@ -6477,10 +7300,10 @@ async function autoTriggerSkills(userInput, mcpManager) {
|
|
|
6477
7300
|
const result = await mcpManager.callTool("skill_list", {});
|
|
6478
7301
|
const skills = JSON.parse(result);
|
|
6479
7302
|
const installed = skills.filter((s) => s.installed).map((s) => s.name);
|
|
6480
|
-
const skillsMdPath =
|
|
7303
|
+
const skillsMdPath = path18.join(os17.homedir(), ".askill", "skills.md");
|
|
6481
7304
|
const runtimeTriggers = await loadRuntimeTriggers(skillsMdPath);
|
|
6482
7305
|
if (installed.length === 0 && runtimeTriggers.size === 0) return "";
|
|
6483
|
-
const matched =
|
|
7306
|
+
const matched = matchSkillsSemantic(userInput, installed, runtimeTriggers);
|
|
6484
7307
|
if (matched.length === 0) return "";
|
|
6485
7308
|
const blocks = [];
|
|
6486
7309
|
for (const skillName of matched.slice(0, 2)) {
|
|
@@ -6756,16 +7579,23 @@ import { reflect as reflect2, isReflectionDue as isReflectionDue2 } from "@aman_
|
|
|
6756
7579
|
var VALID_TYPES = /* @__PURE__ */ new Set(["preference", "fact", "pattern", "topology", "decision", "correction"]);
|
|
6757
7580
|
var MIN_RESPONSE_LENGTH = 50;
|
|
6758
7581
|
var MIN_TURNS_BETWEEN_EMPTY = 3;
|
|
6759
|
-
var EXTRACTION_PROMPT = `Analyze this conversation turn. Extract any information worth remembering long-term.
|
|
7582
|
+
var EXTRACTION_PROMPT = `Analyze this conversation turn. Extract any information worth remembering long-term, and assess the user's emotional tone.
|
|
6760
7583
|
|
|
6761
|
-
Return a JSON
|
|
6762
|
-
|
|
6763
|
-
"
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
7584
|
+
Return a JSON object with two fields:
|
|
7585
|
+
{
|
|
7586
|
+
"memories": [{
|
|
7587
|
+
"content": "what to remember \u2014 be specific and self-contained",
|
|
7588
|
+
"type": "preference|fact|pattern|decision|correction|topology",
|
|
7589
|
+
"tags": ["relevant", "tags"],
|
|
7590
|
+
"confidence": 0.0-1.0,
|
|
7591
|
+
"scope": "global"
|
|
7592
|
+
}],
|
|
7593
|
+
"sentiment": {
|
|
7594
|
+
"tone": "neutral|positive|frustrated|confused|excited|fatigued",
|
|
7595
|
+
"confidence": 0.0-1.0,
|
|
7596
|
+
"context": "brief reason for sentiment read"
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
6769
7599
|
|
|
6770
7600
|
Type guide:
|
|
6771
7601
|
- "preference" = user likes/dislikes/preferences
|
|
@@ -6779,7 +7609,8 @@ Rules:
|
|
|
6779
7609
|
- Only extract genuinely useful LONG-TERM information
|
|
6780
7610
|
- Skip ephemeral things ("user asked about X" is NOT useful)
|
|
6781
7611
|
- Be conservative \u2014 90% of turns produce nothing worth storing
|
|
6782
|
-
-
|
|
7612
|
+
- Sentiment should reflect the USER's tone, not the assistant's
|
|
7613
|
+
- Return ONLY the JSON object, no other text`;
|
|
6783
7614
|
function shouldExtract(assistantResponse, turnsSinceLastExtraction, lastExtractionCount) {
|
|
6784
7615
|
if (assistantResponse.length < MIN_RESPONSE_LENGTH) return false;
|
|
6785
7616
|
if (lastExtractionCount > 0 && turnsSinceLastExtraction >= 1) return true;
|
|
@@ -6794,12 +7625,32 @@ function parseExtractionResult(raw) {
|
|
|
6794
7625
|
cleaned = codeBlockMatch[1].trim();
|
|
6795
7626
|
}
|
|
6796
7627
|
const parsed = JSON.parse(cleaned);
|
|
6797
|
-
if (!Array.isArray(parsed))
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
7628
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "memories" in parsed) {
|
|
7629
|
+
const memories = Array.isArray(parsed.memories) ? parsed.memories.filter(
|
|
7630
|
+
(item) => typeof item.content === "string" && item.content.length > 0 && typeof item.type === "string" && VALID_TYPES.has(item.type)
|
|
7631
|
+
) : [];
|
|
7632
|
+
let sentiment;
|
|
7633
|
+
if (parsed.sentiment && typeof parsed.sentiment === "object") {
|
|
7634
|
+
const s = parsed.sentiment;
|
|
7635
|
+
if (typeof s.tone === "string" && typeof s.confidence === "number") {
|
|
7636
|
+
sentiment = {
|
|
7637
|
+
tone: s.tone,
|
|
7638
|
+
confidence: s.confidence,
|
|
7639
|
+
context: typeof s.context === "string" ? s.context : void 0
|
|
7640
|
+
};
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
return { memories, sentiment };
|
|
7644
|
+
}
|
|
7645
|
+
if (Array.isArray(parsed)) {
|
|
7646
|
+
const memories = parsed.filter(
|
|
7647
|
+
(item) => typeof item.content === "string" && item.content.length > 0 && typeof item.type === "string" && VALID_TYPES.has(item.type)
|
|
7648
|
+
);
|
|
7649
|
+
return { memories };
|
|
7650
|
+
}
|
|
7651
|
+
return { memories: [] };
|
|
6801
7652
|
} catch {
|
|
6802
|
-
return [];
|
|
7653
|
+
return { memories: [] };
|
|
6803
7654
|
}
|
|
6804
7655
|
}
|
|
6805
7656
|
async function extractMemories(userMessage, assistantResponse, client, state) {
|
|
@@ -6819,12 +7670,16 @@ Assistant: ${assistantResponse.slice(0, 2e3)}`;
|
|
|
6819
7670
|
if (chunk.type === "text" && chunk.text) fullText += chunk.text;
|
|
6820
7671
|
}
|
|
6821
7672
|
);
|
|
6822
|
-
const
|
|
7673
|
+
const result = parseExtractionResult(fullText);
|
|
6823
7674
|
state.turnsSinceLastExtraction = 0;
|
|
6824
|
-
state.lastExtractionCount =
|
|
6825
|
-
if (
|
|
7675
|
+
state.lastExtractionCount = result.memories.length;
|
|
7676
|
+
if (result.sentiment) {
|
|
7677
|
+
state.lastLlmSentiment = result.sentiment;
|
|
7678
|
+
log.debug("extractor", `LLM sentiment: ${result.sentiment.tone} (${result.sentiment.confidence})`);
|
|
7679
|
+
}
|
|
7680
|
+
if (result.memories.length === 0) return 0;
|
|
6826
7681
|
let stored = 0;
|
|
6827
|
-
for (const candidate of
|
|
7682
|
+
for (const candidate of result.memories) {
|
|
6828
7683
|
try {
|
|
6829
7684
|
const existing = await memoryRecall(candidate.content, { limit: 1 });
|
|
6830
7685
|
if (existing.total > 0 && existing.memories.length > 0) {
|
|
@@ -7036,9 +7891,9 @@ function humanizeError(message) {
|
|
|
7036
7891
|
}
|
|
7037
7892
|
|
|
7038
7893
|
// src/hints.ts
|
|
7039
|
-
import
|
|
7040
|
-
import
|
|
7041
|
-
import
|
|
7894
|
+
import fs19 from "fs";
|
|
7895
|
+
import path19 from "path";
|
|
7896
|
+
import os18 from "os";
|
|
7042
7897
|
var HINTS = [
|
|
7043
7898
|
{
|
|
7044
7899
|
id: "eval",
|
|
@@ -7076,11 +7931,11 @@ function getHint(state, ctx) {
|
|
|
7076
7931
|
}
|
|
7077
7932
|
return null;
|
|
7078
7933
|
}
|
|
7079
|
-
var HINTS_FILE =
|
|
7934
|
+
var HINTS_FILE = path19.join(os18.homedir(), ".aman-agent", "hints-seen.json");
|
|
7080
7935
|
function loadShownHints() {
|
|
7081
7936
|
try {
|
|
7082
|
-
if (
|
|
7083
|
-
const data = JSON.parse(
|
|
7937
|
+
if (fs19.existsSync(HINTS_FILE)) {
|
|
7938
|
+
const data = JSON.parse(fs19.readFileSync(HINTS_FILE, "utf-8"));
|
|
7084
7939
|
return new Set(Array.isArray(data) ? data : []);
|
|
7085
7940
|
}
|
|
7086
7941
|
} catch {
|
|
@@ -7089,9 +7944,9 @@ function loadShownHints() {
|
|
|
7089
7944
|
}
|
|
7090
7945
|
function saveShownHints(shown) {
|
|
7091
7946
|
try {
|
|
7092
|
-
const dir =
|
|
7093
|
-
|
|
7094
|
-
|
|
7947
|
+
const dir = path19.dirname(HINTS_FILE);
|
|
7948
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
7949
|
+
fs19.writeFileSync(HINTS_FILE, JSON.stringify([...shown]), "utf-8");
|
|
7095
7950
|
} catch {
|
|
7096
7951
|
}
|
|
7097
7952
|
}
|
|
@@ -7358,9 +8213,9 @@ ${task.result}`
|
|
|
7358
8213
|
}
|
|
7359
8214
|
if (cmdResult.exportConversation) {
|
|
7360
8215
|
try {
|
|
7361
|
-
const exportDir =
|
|
7362
|
-
|
|
7363
|
-
const exportPath =
|
|
8216
|
+
const exportDir = path20.join(os19.homedir(), ".aman-agent", "exports");
|
|
8217
|
+
fs20.mkdirSync(exportDir, { recursive: true });
|
|
8218
|
+
const exportPath = path20.join(exportDir, `${sessionId}.md`);
|
|
7364
8219
|
const lines = [
|
|
7365
8220
|
`# Conversation \u2014 ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
|
|
7366
8221
|
`**Model:** ${model}`,
|
|
@@ -7374,7 +8229,7 @@ ${task.result}`
|
|
|
7374
8229
|
lines.push(`${label} ${msg.content}`, "");
|
|
7375
8230
|
}
|
|
7376
8231
|
}
|
|
7377
|
-
|
|
8232
|
+
fs20.writeFileSync(exportPath, lines.join("\n"), "utf-8");
|
|
7378
8233
|
console.log(pc7.green(`Exported to ${exportPath}`));
|
|
7379
8234
|
} catch {
|
|
7380
8235
|
console.log(pc7.red("Failed to export conversation."));
|
|
@@ -7500,25 +8355,25 @@ ${knowledgeItem.content}
|
|
|
7500
8355
|
for (const match of filePathMatches) {
|
|
7501
8356
|
let filePath = match[1];
|
|
7502
8357
|
if (filePath.startsWith("~/")) {
|
|
7503
|
-
filePath =
|
|
8358
|
+
filePath = path20.join(os19.homedir(), filePath.slice(2));
|
|
7504
8359
|
}
|
|
7505
|
-
if (!
|
|
7506
|
-
const ext =
|
|
8360
|
+
if (!fs20.existsSync(filePath) || !fs20.statSync(filePath).isFile()) continue;
|
|
8361
|
+
const ext = path20.extname(filePath).toLowerCase();
|
|
7507
8362
|
if (imageExts.has(ext)) {
|
|
7508
8363
|
try {
|
|
7509
|
-
const stat =
|
|
8364
|
+
const stat = fs20.statSync(filePath);
|
|
7510
8365
|
if (stat.size > maxImageBytes) {
|
|
7511
|
-
process.stdout.write(pc7.yellow(` [skipped: ${
|
|
8366
|
+
process.stdout.write(pc7.yellow(` [skipped: ${path20.basename(filePath)} \u2014 exceeds 20MB limit]
|
|
7512
8367
|
`));
|
|
7513
8368
|
continue;
|
|
7514
8369
|
}
|
|
7515
|
-
const data =
|
|
8370
|
+
const data = fs20.readFileSync(filePath).toString("base64");
|
|
7516
8371
|
const mediaType = mimeMap[ext] || "image/png";
|
|
7517
8372
|
imageBlocks.push({
|
|
7518
8373
|
type: "image",
|
|
7519
8374
|
source: { type: "base64", media_type: mediaType, data }
|
|
7520
8375
|
});
|
|
7521
|
-
process.stdout.write(pc7.dim(` [attached image: ${
|
|
8376
|
+
process.stdout.write(pc7.dim(` [attached image: ${path20.basename(filePath)} (${(stat.size / 1024).toFixed(1)}KB)]
|
|
7522
8377
|
`));
|
|
7523
8378
|
} catch {
|
|
7524
8379
|
process.stdout.write(pc7.dim(` [could not read image: ${filePath}]
|
|
@@ -7526,7 +8381,7 @@ ${knowledgeItem.content}
|
|
|
7526
8381
|
}
|
|
7527
8382
|
} else if (textExts.has(ext) || ext === "") {
|
|
7528
8383
|
try {
|
|
7529
|
-
const content =
|
|
8384
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
7530
8385
|
const maxChars = 5e4;
|
|
7531
8386
|
const trimmed = content.length > maxChars ? content.slice(0, maxChars) + `
|
|
7532
8387
|
|
|
@@ -7536,7 +8391,7 @@ ${knowledgeItem.content}
|
|
|
7536
8391
|
<file path="${filePath}" size="${content.length} chars">
|
|
7537
8392
|
${trimmed}
|
|
7538
8393
|
</file>`;
|
|
7539
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
8394
|
+
process.stdout.write(pc7.dim(` [attached: ${path20.basename(filePath)} (${(content.length / 1024).toFixed(1)}KB)]
|
|
7540
8395
|
`));
|
|
7541
8396
|
} catch {
|
|
7542
8397
|
process.stdout.write(pc7.dim(` [could not read: ${filePath}]
|
|
@@ -7545,7 +8400,7 @@ ${trimmed}
|
|
|
7545
8400
|
} else if (docExts.has(ext)) {
|
|
7546
8401
|
if (mcpManager) {
|
|
7547
8402
|
try {
|
|
7548
|
-
process.stdout.write(pc7.dim(` [converting: ${
|
|
8403
|
+
process.stdout.write(pc7.dim(` [converting: ${path20.basename(filePath)}...]
|
|
7549
8404
|
`));
|
|
7550
8405
|
const converted = await mcpManager.callTool("doc_convert", { path: filePath });
|
|
7551
8406
|
if (converted && !converted.startsWith("Error") && !converted.includes("Could not convert")) {
|
|
@@ -7554,7 +8409,7 @@ ${trimmed}
|
|
|
7554
8409
|
<file path="${filePath}" format="${ext}">
|
|
7555
8410
|
${converted.slice(0, 5e4)}
|
|
7556
8411
|
</file>`;
|
|
7557
|
-
process.stdout.write(pc7.dim(` [attached: ${
|
|
8412
|
+
process.stdout.write(pc7.dim(` [attached: ${path20.basename(filePath)} (converted from ${ext})]
|
|
7558
8413
|
`));
|
|
7559
8414
|
} else {
|
|
7560
8415
|
textContent += `
|
|
@@ -7566,7 +8421,7 @@ ${converted}
|
|
|
7566
8421
|
`));
|
|
7567
8422
|
}
|
|
7568
8423
|
} catch {
|
|
7569
|
-
process.stdout.write(pc7.dim(` [could not convert: ${
|
|
8424
|
+
process.stdout.write(pc7.dim(` [could not convert: ${path20.basename(filePath)}]
|
|
7570
8425
|
`));
|
|
7571
8426
|
}
|
|
7572
8427
|
} else {
|
|
@@ -7675,8 +8530,68 @@ ${converted}
|
|
|
7675
8530
|
}
|
|
7676
8531
|
}
|
|
7677
8532
|
const nudge = formatWellbeingNudge(state);
|
|
7678
|
-
if (nudge) {
|
|
7679
|
-
|
|
8533
|
+
if (nudge && state.wellbeingNudge) {
|
|
8534
|
+
let fireNudge = true;
|
|
8535
|
+
try {
|
|
8536
|
+
const { loadUserModel: loadUserModel2, computeProfile: computeProfile2 } = await Promise.resolve().then(() => (init_user_model(), user_model_exports));
|
|
8537
|
+
const model2 = await loadUserModel2();
|
|
8538
|
+
if (model2 && model2.sessions.length >= 5) {
|
|
8539
|
+
const profile = computeProfile2(model2.sessions, model2.sessions.length);
|
|
8540
|
+
fireNudge = shouldFireNudge(state.wellbeingNudge, profile);
|
|
8541
|
+
}
|
|
8542
|
+
} catch {
|
|
8543
|
+
}
|
|
8544
|
+
if (fireNudge) {
|
|
8545
|
+
augmentedSystemPrompt += "\n" + nudge;
|
|
8546
|
+
}
|
|
8547
|
+
}
|
|
8548
|
+
try {
|
|
8549
|
+
const { loadUserModel: loadUserModel2, computeProfile: computeProfile2 } = await Promise.resolve().then(() => (init_user_model(), user_model_exports));
|
|
8550
|
+
const model2 = await loadUserModel2();
|
|
8551
|
+
if (model2 && model2.sessions.length >= 10) {
|
|
8552
|
+
const profile = computeProfile2(model2.sessions, model2.sessions.length);
|
|
8553
|
+
const preemptive = [];
|
|
8554
|
+
const hour2 = (/* @__PURE__ */ new Date()).getHours();
|
|
8555
|
+
const isLate = hour2 >= 21 || hour2 < 6;
|
|
8556
|
+
if (isLate && profile.frustrationCorrelations.lateNight > 0.4) {
|
|
8557
|
+
preemptive.push(
|
|
8558
|
+
"Based on past patterns, late-night sessions tend to increase frustration for this user. Be extra concise, proactive about blockers, and gently suggest wrapping up if frustration rises."
|
|
8559
|
+
);
|
|
8560
|
+
}
|
|
8561
|
+
const sessionMins = Math.round((Date.now() - getSessionStartTime()) / 6e4);
|
|
8562
|
+
if (sessionMins > 60 && profile.frustrationCorrelations.longSessions > 0.4) {
|
|
8563
|
+
preemptive.push(
|
|
8564
|
+
"This session is getting long and past patterns show long sessions correlate with frustration. Proactively suggest natural breakpoints."
|
|
8565
|
+
);
|
|
8566
|
+
}
|
|
8567
|
+
if (preemptive.length > 0) {
|
|
8568
|
+
augmentedSystemPrompt += `
|
|
8569
|
+
<feed-forward-v2>
|
|
8570
|
+
${preemptive.join("\n")}
|
|
8571
|
+
</feed-forward-v2>`;
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8574
|
+
} catch {
|
|
8575
|
+
}
|
|
8576
|
+
try {
|
|
8577
|
+
const { loadUserModel: loadUserModel2, predictBurnout: predictBurnout2 } = await Promise.resolve().then(() => (init_user_model(), user_model_exports));
|
|
8578
|
+
const model2 = await loadUserModel2();
|
|
8579
|
+
if (model2 && model2.sessions.length >= 5) {
|
|
8580
|
+
const sessionMins = Math.round((Date.now() - getSessionStartTime()) / 6e4);
|
|
8581
|
+
const burnout = predictBurnout2(model2.sessions, {
|
|
8582
|
+
minutes: sessionMins,
|
|
8583
|
+
frustration: state.sentiment.frustration,
|
|
8584
|
+
timePeriod: period
|
|
8585
|
+
});
|
|
8586
|
+
if (burnout.risk > 0.7) {
|
|
8587
|
+
const burnoutState = { ...state, wellbeingNudge: "burnout-warning" };
|
|
8588
|
+
const burnoutNudge = formatWellbeingNudge(burnoutState);
|
|
8589
|
+
if (burnoutNudge) {
|
|
8590
|
+
augmentedSystemPrompt += "\n" + burnoutNudge;
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
} catch {
|
|
7680
8595
|
}
|
|
7681
8596
|
}
|
|
7682
8597
|
const MAX_SYSTEM_TOKENS = 16e3;
|
|
@@ -7870,7 +8785,7 @@ ${result2.response}` : `[${input2.profile}] failed: ${result2.error}`;
|
|
|
7870
8785
|
}
|
|
7871
8786
|
if (hooksConfig?.featureHints) {
|
|
7872
8787
|
hintState.turnCount++;
|
|
7873
|
-
const hasWorkflows =
|
|
8788
|
+
const hasWorkflows = fs20.existsSync(path20.join(os19.homedir(), ".aflow", "flow.md"));
|
|
7874
8789
|
const memoryCount = memoryTokens > 0 ? Math.floor(memoryTokens / 5) : 0;
|
|
7875
8790
|
const hint = getHint(hintState, { hasWorkflows, memoryCount });
|
|
7876
8791
|
if (hint) {
|
|
@@ -7916,9 +8831,9 @@ async function saveConversationToMemory(messages, sessionId) {
|
|
|
7916
8831
|
}
|
|
7917
8832
|
|
|
7918
8833
|
// src/index.ts
|
|
7919
|
-
import
|
|
7920
|
-
import
|
|
7921
|
-
import
|
|
8834
|
+
import fs21 from "fs";
|
|
8835
|
+
import path21 from "path";
|
|
8836
|
+
import os20 from "os";
|
|
7922
8837
|
|
|
7923
8838
|
// src/presets.ts
|
|
7924
8839
|
var PRESETS = {
|
|
@@ -8027,9 +8942,9 @@ ${wfSections}`;
|
|
|
8027
8942
|
|
|
8028
8943
|
// src/index.ts
|
|
8029
8944
|
async function autoDetectConfig() {
|
|
8030
|
-
const reconfigMarker =
|
|
8031
|
-
if (
|
|
8032
|
-
|
|
8945
|
+
const reconfigMarker = path21.join(os20.homedir(), ".aman-agent", ".reconfig");
|
|
8946
|
+
if (fs21.existsSync(reconfigMarker)) {
|
|
8947
|
+
fs21.unlinkSync(reconfigMarker);
|
|
8033
8948
|
return null;
|
|
8034
8949
|
}
|
|
8035
8950
|
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
@@ -8058,11 +8973,11 @@ async function autoDetectConfig() {
|
|
|
8058
8973
|
return null;
|
|
8059
8974
|
}
|
|
8060
8975
|
function bootstrapEcosystem() {
|
|
8061
|
-
const home2 =
|
|
8062
|
-
const corePath =
|
|
8063
|
-
if (
|
|
8064
|
-
|
|
8065
|
-
|
|
8976
|
+
const home2 = os20.homedir();
|
|
8977
|
+
const corePath = path21.join(home2, ".acore", "core.md");
|
|
8978
|
+
if (fs21.existsSync(corePath)) return false;
|
|
8979
|
+
fs21.mkdirSync(path21.join(home2, ".acore"), { recursive: true });
|
|
8980
|
+
fs21.writeFileSync(corePath, [
|
|
8066
8981
|
"# Aman",
|
|
8067
8982
|
"",
|
|
8068
8983
|
"## Personality",
|
|
@@ -8074,11 +8989,11 @@ function bootstrapEcosystem() {
|
|
|
8074
8989
|
"## Session",
|
|
8075
8990
|
"_New companion \u2014 no prior sessions._"
|
|
8076
8991
|
].join("\n"), "utf-8");
|
|
8077
|
-
const rulesDir =
|
|
8078
|
-
const rulesPath =
|
|
8079
|
-
if (!
|
|
8080
|
-
|
|
8081
|
-
|
|
8992
|
+
const rulesDir = path21.join(home2, ".arules");
|
|
8993
|
+
const rulesPath = path21.join(rulesDir, "rules.md");
|
|
8994
|
+
if (!fs21.existsSync(rulesPath)) {
|
|
8995
|
+
fs21.mkdirSync(rulesDir, { recursive: true });
|
|
8996
|
+
fs21.writeFileSync(rulesPath, [
|
|
8082
8997
|
"# Guardrails",
|
|
8083
8998
|
"",
|
|
8084
8999
|
"## safety",
|
|
@@ -8090,22 +9005,22 @@ function bootstrapEcosystem() {
|
|
|
8090
9005
|
"- Respect the user's preferences stored in memory"
|
|
8091
9006
|
].join("\n"), "utf-8");
|
|
8092
9007
|
}
|
|
8093
|
-
const flowDir =
|
|
8094
|
-
const flowPath =
|
|
8095
|
-
if (!
|
|
8096
|
-
|
|
8097
|
-
|
|
9008
|
+
const flowDir = path21.join(home2, ".aflow");
|
|
9009
|
+
const flowPath = path21.join(flowDir, "flow.md");
|
|
9010
|
+
if (!fs21.existsSync(flowPath)) {
|
|
9011
|
+
fs21.mkdirSync(flowDir, { recursive: true });
|
|
9012
|
+
fs21.writeFileSync(flowPath, "# Workflows\n\n_No workflows defined yet. Use /workflows add to create one._\n", "utf-8");
|
|
8098
9013
|
}
|
|
8099
|
-
const skillDir =
|
|
8100
|
-
const skillPath =
|
|
8101
|
-
if (!
|
|
8102
|
-
|
|
8103
|
-
|
|
9014
|
+
const skillDir = path21.join(home2, ".askill");
|
|
9015
|
+
const skillPath = path21.join(skillDir, "skills.md");
|
|
9016
|
+
if (!fs21.existsSync(skillPath)) {
|
|
9017
|
+
fs21.mkdirSync(skillDir, { recursive: true });
|
|
9018
|
+
fs21.writeFileSync(skillPath, "# Skills\n\n_No skills installed yet. Use /skills install to add domain expertise._\n", "utf-8");
|
|
8104
9019
|
}
|
|
8105
9020
|
return true;
|
|
8106
9021
|
}
|
|
8107
9022
|
var program = new Command();
|
|
8108
|
-
program.name("aman-agent").description("Your AI companion, running locally").version("0.
|
|
9023
|
+
program.name("aman-agent").description("Your AI companion, running locally").version("0.28.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) => {
|
|
8109
9024
|
p3.intro(pc8.bold("aman agent") + pc8.dim(" \u2014 your AI companion"));
|
|
8110
9025
|
let config = loadConfig();
|
|
8111
9026
|
if (!config) {
|
|
@@ -8457,19 +9372,19 @@ program.command("init").description("Set up your AI companion with a guided wiza
|
|
|
8457
9372
|
});
|
|
8458
9373
|
if (p3.isCancel(preset)) process.exit(0);
|
|
8459
9374
|
const result = applyPreset(preset, name || "Aman");
|
|
8460
|
-
const home2 =
|
|
8461
|
-
|
|
8462
|
-
|
|
9375
|
+
const home2 = os20.homedir();
|
|
9376
|
+
fs21.mkdirSync(path21.join(home2, ".acore"), { recursive: true });
|
|
9377
|
+
fs21.writeFileSync(path21.join(home2, ".acore", "core.md"), result.coreMd, "utf-8");
|
|
8463
9378
|
p3.log.success(`Identity created \u2014 ${PRESETS[preset].identity.personality.split(".")[0].toLowerCase()}`);
|
|
8464
9379
|
if (result.rulesMd) {
|
|
8465
|
-
|
|
8466
|
-
|
|
9380
|
+
fs21.mkdirSync(path21.join(home2, ".arules"), { recursive: true });
|
|
9381
|
+
fs21.writeFileSync(path21.join(home2, ".arules", "rules.md"), result.rulesMd, "utf-8");
|
|
8467
9382
|
const ruleCount = (result.rulesMd.match(/^- /gm) || []).length;
|
|
8468
9383
|
p3.log.success(`${ruleCount} rules set`);
|
|
8469
9384
|
}
|
|
8470
9385
|
if (result.flowMd) {
|
|
8471
|
-
|
|
8472
|
-
|
|
9386
|
+
fs21.mkdirSync(path21.join(home2, ".aflow"), { recursive: true });
|
|
9387
|
+
fs21.writeFileSync(path21.join(home2, ".aflow", "flow.md"), result.flowMd, "utf-8");
|
|
8473
9388
|
const wfCount = (result.flowMd.match(/^## /gm) || []).length;
|
|
8474
9389
|
p3.log.success(`${wfCount} workflow${wfCount > 1 ? "s" : ""} added`);
|
|
8475
9390
|
}
|