@evo-dev/core 0.0.1-alpha.1 → 0.0.1-alpha.10

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.
Files changed (66) hide show
  1. package/assets/skills/coding/knowledge-distillation/SKILL.md +117 -114
  2. package/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +11 -7
  3. package/assets/team/agents/code-reviewer.md +48 -0
  4. package/assets/team/agents/docs-maintainer.md +51 -0
  5. package/assets/team/agents/implementation-engineer.md +51 -0
  6. package/assets/team/agents/product-scope-analyst.md +58 -0
  7. package/assets/team/agents/release-engineer.md +55 -0
  8. package/assets/team/agents/security-boundary-reviewer.md +50 -0
  9. package/assets/team/agents/solution-architect.md +51 -0
  10. package/assets/team/agents/verification-engineer.md +51 -0
  11. package/assets/team/team.md +102 -0
  12. package/dist/config/index.js +925 -97
  13. package/dist/index.js +13107 -5618
  14. package/package.json +5 -1
  15. package/src/agents/index.ts +56 -264
  16. package/src/code-agent-traces/index.ts +520 -0
  17. package/src/config/index.ts +5 -0
  18. package/src/config/paths.ts +1 -1
  19. package/src/config/settings.ts +149 -0
  20. package/src/config/store.ts +2 -0
  21. package/src/daemon/index.ts +99 -50
  22. package/src/evolution/candidates/index.ts +564 -0
  23. package/src/evolution/control/index.ts +20 -0
  24. package/src/evolution/evidence/analysis.ts +533 -0
  25. package/src/evolution/evidence/index.ts +3 -0
  26. package/src/evolution/evidence/session-memory/analysis.ts +281 -0
  27. package/src/evolution/evidence/session-memory/constants.ts +9 -0
  28. package/src/evolution/evidence/session-memory/index.ts +7 -0
  29. package/src/evolution/evidence/session-memory/paths.ts +29 -0
  30. package/src/evolution/evidence/session-memory/policy.ts +39 -0
  31. package/src/evolution/evidence/session-memory/segment.ts +202 -0
  32. package/src/evolution/evidence/session-memory/sensitivity.ts +335 -0
  33. package/src/evolution/evidence/session-memory/state-machine.ts +249 -0
  34. package/src/evolution/evidence/session-memory/storage.ts +379 -0
  35. package/src/evolution/evidence/session-memory/types.ts +221 -0
  36. package/src/evolution/evidence/session-memory/updater.ts +191 -0
  37. package/src/evolution/formatters.ts +169 -0
  38. package/src/evolution/index.ts +16 -2356
  39. package/src/evolution/knowledge/index.ts +5427 -0
  40. package/src/evolution/paths.ts +44 -0
  41. package/src/evolution/processor/distillation.ts +518 -0
  42. package/src/evolution/processor/index.ts +3 -0
  43. package/src/evolution/processor/process.ts +528 -0
  44. package/src/{learning → evolution/review}/index.ts +10 -14
  45. package/src/evolution/schema.ts +568 -0
  46. package/src/evolution/shared.ts +758 -0
  47. package/src/evolution/triggers/classification.ts +102 -0
  48. package/src/evolution/triggers/index.ts +295 -0
  49. package/src/hooks/index.ts +438 -179
  50. package/src/index.ts +12 -3
  51. package/src/projects/index.ts +453 -0
  52. package/src/runtime-logs/index.ts +490 -24
  53. package/src/team/index.ts +1429 -185
  54. package/src/team/mcp.ts +9 -5
  55. package/src/team/prompts.ts +141 -0
  56. package/src/utils/errors.ts +13 -0
  57. package/src/utils/fs.ts +40 -0
  58. package/src/utils/hash.ts +9 -0
  59. package/src/utils/ids.ts +12 -0
  60. package/src/utils/index.ts +7 -0
  61. package/src/utils/parsing.ts +11 -0
  62. package/src/utils/text.ts +18 -0
  63. package/src/utils/time.ts +5 -0
  64. package/src/workflow/index.ts +3 -21
  65. package/src/project/index.ts +0 -507
  66. package/src/task/index.ts +0 -840
@@ -0,0 +1,335 @@
1
+ import type {
2
+ SessionEvidenceSegmentV1,
3
+ SessionMemoryRawEventV1,
4
+ SessionMemorySensitivity,
5
+ SessionMemorySensitivityReason,
6
+ } from "./types.ts";
7
+
8
+ export interface SessionMemorySensitivityDetection {
9
+ classification: SessionMemorySensitivity;
10
+ reasons: SessionMemorySensitivityReason[];
11
+ }
12
+
13
+ export interface SessionMemoryCredentialRedaction<T> {
14
+ value: T;
15
+ redacted: boolean;
16
+ }
17
+
18
+ export const SESSION_MEMORY_CREDENTIAL_REDACTION = "[credential-redacted]";
19
+
20
+ const CREDENTIAL_FIELD_KEYS = new Set([
21
+ "accesstoken",
22
+ "apikey",
23
+ "auth",
24
+ "authtoken",
25
+ "authorization",
26
+ "clientsecret",
27
+ "cookie",
28
+ "credential",
29
+ "credentials",
30
+ "idtoken",
31
+ "password",
32
+ "passwd",
33
+ "privatekey",
34
+ "proxyauthorization",
35
+ "refreshtoken",
36
+ "secret",
37
+ "secretvalue",
38
+ "sessiontoken",
39
+ "setcookie",
40
+ "signingkey",
41
+ "token",
42
+ ]);
43
+
44
+ const PRIVATE_MARKER_PATTERN = /\b(?:internal|private)\b|(?:^|[\\/])\.env(?:\.|$|[\\/])/i;
45
+ const URL_PATTERN = /https?:\/\/[^\s"'<>\\]+/gi;
46
+ const CREDENTIAL_KEY_PATTERN = String.raw`(?:access[_-]?token|api[_-]?key|auth(?:orization)?|client[_-]?secret|cookie|credential(?:s)?|id[_-]?token|password|passwd|private[_-]?key|proxy[_-]?authorization|refresh[_-]?token|secret(?:[_-]?value)?|session[_-]?token|set[_-]?cookie|signing[_-]?key|token)`;
47
+ const CREDENTIAL_SHAPE_PATTERNS = [
48
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/i,
49
+ /\bAKIA[0-9A-Z]{16}\b/,
50
+ /\bgh[pousr]_[A-Za-z0-9]{20,}\b/,
51
+ /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/,
52
+ /\bBearer\s+[A-Za-z0-9._~+/=-]+/i,
53
+ /\bBasic\s+[A-Za-z0-9+/]+={0,2}/i,
54
+ new RegExp(
55
+ String.raw`(?:^|[^A-Za-z0-9_])["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*["']?[A-Za-z0-9][A-Za-z0-9._~+/=-]*`,
56
+ "i",
57
+ ),
58
+ new RegExp(
59
+ String.raw`(?:^|[^A-Za-z0-9_])["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*["'](?!\[credential-redacted\]["'])[^"'\r\n]+["']`,
60
+ "i",
61
+ ),
62
+ ];
63
+ const PRIVATE_KEY_BLOCK_PATTERN =
64
+ /-----BEGIN ([A-Z0-9 ]*PRIVATE KEY)-----[\s\S]*?-----END \1-----/giu;
65
+ const AUTHORIZATION_HEADER_PATTERN =
66
+ /(^|[\r\n])(\s*(?:authorization|proxy-authorization)\s*:\s*)(?:Bearer|Basic)\s+[^\r\n]+/gimu;
67
+ const COOKIE_HEADER_PATTERN = /(^|[\r\n])(\s*(?:cookie|set-cookie)\s*:\s*)[^\r\n]+/gimu;
68
+ const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu;
69
+ const BASIC_PATTERN = /\bBasic\s+[A-Za-z0-9+/]+={0,2}/giu;
70
+ const AWS_ACCESS_KEY_PATTERN = /\bAKIA[0-9A-Z]{16}\b/gu;
71
+ const GITHUB_TOKEN_PATTERN = /\bgh[pousr]_[A-Za-z0-9]{20,}\b/gu;
72
+ const SLACK_TOKEN_PATTERN = /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/gu;
73
+ const QUOTED_SERIALIZED_CREDENTIAL_PATTERN = new RegExp(
74
+ String.raw`(^|[^A-Za-z0-9_])(["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*)(["'])([^"'\r\n]+)(\3)`,
75
+ "gimu",
76
+ );
77
+ const SERIALIZED_CREDENTIAL_PATTERN = new RegExp(
78
+ String.raw`(^|[^A-Za-z0-9_])(["']?${CREDENTIAL_KEY_PATTERN}["']?\s*[:=]\s*)(["']?)([A-Za-z0-9][A-Za-z0-9._~+/=-]*)(\3)`,
79
+ "gimu",
80
+ );
81
+ const URL_USERINFO_PATTERN = /(https?:\/\/)[^@\s/"']+@/giu;
82
+ const URL_CREDENTIAL_PARAMETER_PATTERN = new RegExp(
83
+ String.raw`([?&#]["']?${CREDENTIAL_KEY_PATTERN}["']?=)[^&#\s"'<>]+`,
84
+ "giu",
85
+ );
86
+
87
+ export function detectSessionMemorySensitivity(value: unknown): SessionMemorySensitivityDetection {
88
+ const reasons = new Set<SessionMemorySensitivityReason>();
89
+ inspectValue(value, null, reasons, new WeakSet<object>());
90
+ return createDetection(reasons);
91
+ }
92
+
93
+ export function redactSessionMemoryCredentialText(
94
+ value: string,
95
+ ): SessionMemoryCredentialRedaction<string> {
96
+ let redacted = value
97
+ .replace(PRIVATE_KEY_BLOCK_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION)
98
+ .replace(AUTHORIZATION_HEADER_PATTERN, (_, boundary: string, prefix: string) => {
99
+ return `${boundary}${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`;
100
+ })
101
+ .replace(COOKIE_HEADER_PATTERN, (_, boundary: string, prefix: string) => {
102
+ return `${boundary}${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`;
103
+ })
104
+ .replace(BEARER_PATTERN, `Bearer ${SESSION_MEMORY_CREDENTIAL_REDACTION}`)
105
+ .replace(BASIC_PATTERN, `Basic ${SESSION_MEMORY_CREDENTIAL_REDACTION}`)
106
+ .replace(AWS_ACCESS_KEY_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION)
107
+ .replace(GITHUB_TOKEN_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION)
108
+ .replace(SLACK_TOKEN_PATTERN, SESSION_MEMORY_CREDENTIAL_REDACTION)
109
+ .replace(
110
+ QUOTED_SERIALIZED_CREDENTIAL_PATTERN,
111
+ (_, boundary: string, prefix: string, quote: string) =>
112
+ `${boundary}${prefix}${quote}${SESSION_MEMORY_CREDENTIAL_REDACTION}${quote}`,
113
+ )
114
+ .replace(
115
+ SERIALIZED_CREDENTIAL_PATTERN,
116
+ (_, boundary: string, prefix: string, quote: string, credentialValue: string) => {
117
+ const trailingPunctuation =
118
+ quote === "" ? (credentialValue.match(/[.,;!?]+$/u)?.[0] ?? "") : "";
119
+ return `${boundary}${prefix}${quote}${SESSION_MEMORY_CREDENTIAL_REDACTION}${quote}${trailingPunctuation}`;
120
+ },
121
+ )
122
+ .replace(
123
+ URL_CREDENTIAL_PARAMETER_PATTERN,
124
+ (_, prefix: string) => `${prefix}${SESSION_MEMORY_CREDENTIAL_REDACTION}`,
125
+ )
126
+ .replace(URL_USERINFO_PATTERN, (_, protocol: string) => protocol);
127
+ if (redacted === "") redacted = value;
128
+ return { value: redacted, redacted: redacted !== value };
129
+ }
130
+
131
+ export function redactSessionMemoryCredentials<T>(value: T): SessionMemoryCredentialRedaction<T> {
132
+ const result = redactCredentialValue(value, null, new WeakSet<object>());
133
+ return result as SessionMemoryCredentialRedaction<T>;
134
+ }
135
+
136
+ export function mergeSessionMemorySensitivity(
137
+ detections: SessionMemorySensitivityDetection[],
138
+ ): SessionMemorySensitivityDetection {
139
+ const reasons = new Set<SessionMemorySensitivityReason>();
140
+ for (const detection of detections) {
141
+ for (const reason of detection.reasons) reasons.add(reason);
142
+ }
143
+ return createDetection(reasons);
144
+ }
145
+
146
+ /**
147
+ * Resolves both newly classified segments and legacy segments whose `secretsDetected` flag used
148
+ * the older keyword-only matcher. Legacy raw events are reclassified without changing their
149
+ * stored evidence so ordinary URLs or words such as "internal" do not remain credential blocks.
150
+ */
151
+ export function classifySessionEvidenceSegment(
152
+ segment: SessionEvidenceSegmentV1,
153
+ ): SessionMemorySensitivityDetection {
154
+ if (segment.privacy.sensitivity !== undefined) {
155
+ return {
156
+ classification: segment.privacy.sensitivity,
157
+ reasons: [...(segment.privacy.sensitivityReasons ?? [])],
158
+ };
159
+ }
160
+
161
+ const detections: SessionMemorySensitivityDetection[] = [];
162
+ let reclassifiedStoredPayload = false;
163
+ for (const line of segment.rawExcerpt.content.split("\n")) {
164
+ if (line.trim() === "") continue;
165
+ try {
166
+ const event = JSON.parse(line) as Partial<SessionMemoryRawEventV1>;
167
+ if (event.sensitivity !== undefined) {
168
+ detections.push({
169
+ classification: event.sensitivity,
170
+ reasons: [...(event.sensitivityReasons ?? [])],
171
+ });
172
+ continue;
173
+ }
174
+ if (typeof event.rawPayloadJson === "string") {
175
+ detections.push(detectSerializedPayload(event.rawPayloadJson));
176
+ reclassifiedStoredPayload = true;
177
+ continue;
178
+ }
179
+ } catch {
180
+ // A bounded raw line may be truncated. The fallback still recognizes credential shapes.
181
+ }
182
+ detections.push(detectSessionMemorySensitivity(line));
183
+ }
184
+
185
+ const merged = mergeSessionMemorySensitivity(detections);
186
+ if (merged.classification !== "safe-metadata") return merged;
187
+ return segment.privacy.secretsDetected && !reclassifiedStoredPayload
188
+ ? { classification: "credential", reasons: ["legacy-sensitive-flag"] }
189
+ : merged;
190
+ }
191
+
192
+ function detectSerializedPayload(value: string): SessionMemorySensitivityDetection {
193
+ try {
194
+ return detectSessionMemorySensitivity(JSON.parse(value) as unknown);
195
+ } catch {
196
+ return detectSessionMemorySensitivity(value);
197
+ }
198
+ }
199
+
200
+ function inspectValue(
201
+ value: unknown,
202
+ key: string | null,
203
+ reasons: Set<SessionMemorySensitivityReason>,
204
+ seen: WeakSet<object>,
205
+ ): void {
206
+ if (typeof value === "string") {
207
+ inspectText(value, key, reasons);
208
+ return;
209
+ }
210
+ if (value === null || typeof value !== "object") return;
211
+ if (seen.has(value)) return;
212
+ seen.add(value);
213
+ if (Array.isArray(value)) {
214
+ for (const item of value) inspectValue(item, key, reasons, seen);
215
+ return;
216
+ }
217
+ for (const [childKey, child] of Object.entries(value as Record<string, unknown>)) {
218
+ inspectValue(child, childKey, reasons, seen);
219
+ }
220
+ }
221
+
222
+ function inspectText(
223
+ value: string,
224
+ key: string | null,
225
+ reasons: Set<SessionMemorySensitivityReason>,
226
+ ): void {
227
+ const trimmed = value.trim();
228
+ if (trimmed === "") return;
229
+ if (
230
+ key !== null &&
231
+ CREDENTIAL_FIELD_KEYS.has(normalizeKey(key)) &&
232
+ trimmed !== SESSION_MEMORY_CREDENTIAL_REDACTION
233
+ ) {
234
+ reasons.add("credential-field");
235
+ }
236
+ if (CREDENTIAL_SHAPE_PATTERNS.some((pattern) => pattern.test(value))) {
237
+ reasons.add("credential-shape");
238
+ }
239
+ if (PRIVATE_MARKER_PATTERN.test(value)) reasons.add("private-marker");
240
+
241
+ for (const match of value.matchAll(URL_PATTERN)) {
242
+ const rawUrl = match[0];
243
+ if (rawUrl === undefined) continue;
244
+ reasons.add("url");
245
+ try {
246
+ const url = new URL(rawUrl.replace(/[),.;]+$/u, ""));
247
+ if (
248
+ (url.username !== "" || url.password !== "") &&
249
+ decodeURIComponent(url.username) !== SESSION_MEMORY_CREDENTIAL_REDACTION
250
+ ) {
251
+ reasons.add("credential-url-userinfo");
252
+ }
253
+ const parameterSets = [
254
+ url.searchParams,
255
+ new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash),
256
+ ];
257
+ for (const parameters of parameterSets) {
258
+ for (const [parameter, parameterValue] of parameters) {
259
+ if (
260
+ parameterValue.trim() !== "" &&
261
+ parameterValue !== SESSION_MEMORY_CREDENTIAL_REDACTION &&
262
+ CREDENTIAL_FIELD_KEYS.has(normalizeKey(parameter))
263
+ ) {
264
+ reasons.add("credential-url-parameter");
265
+ }
266
+ }
267
+ }
268
+ } catch {
269
+ // The URL marker is still private content even when the bounded value is incomplete.
270
+ }
271
+ }
272
+ }
273
+
274
+ function createDetection(
275
+ reasons: Set<SessionMemorySensitivityReason>,
276
+ ): SessionMemorySensitivityDetection {
277
+ const values = [...reasons].sort();
278
+ if (
279
+ reasons.has("credential-field") ||
280
+ reasons.has("credential-shape") ||
281
+ reasons.has("credential-url-parameter") ||
282
+ reasons.has("credential-url-userinfo") ||
283
+ reasons.has("legacy-sensitive-flag")
284
+ ) {
285
+ return { classification: "credential", reasons: values };
286
+ }
287
+ if (reasons.has("url") || reasons.has("private-marker")) {
288
+ return { classification: "private-content", reasons: values };
289
+ }
290
+ return { classification: "safe-metadata", reasons: values };
291
+ }
292
+
293
+ function normalizeKey(value: string): string {
294
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
295
+ }
296
+
297
+ function redactCredentialValue(
298
+ value: unknown,
299
+ key: string | null,
300
+ seen: WeakSet<object>,
301
+ ): SessionMemoryCredentialRedaction<unknown> {
302
+ if (key !== null && CREDENTIAL_FIELD_KEYS.has(normalizeKey(key))) {
303
+ if (
304
+ value === null ||
305
+ value === undefined ||
306
+ (typeof value === "string" &&
307
+ (value.trim() === "" || value.trim() === SESSION_MEMORY_CREDENTIAL_REDACTION))
308
+ ) {
309
+ return { value, redacted: false };
310
+ }
311
+ return { value: SESSION_MEMORY_CREDENTIAL_REDACTION, redacted: true };
312
+ }
313
+ if (typeof value === "string") return redactSessionMemoryCredentialText(value);
314
+ if (value === null || typeof value !== "object") return { value, redacted: false };
315
+ if (seen.has(value)) {
316
+ return { value: "[circular-reference]", redacted: true };
317
+ }
318
+ seen.add(value);
319
+ if (Array.isArray(value)) {
320
+ let redacted = false;
321
+ const items = value.map((item) => {
322
+ const child = redactCredentialValue(item, key, seen);
323
+ redacted ||= child.redacted;
324
+ return child.value;
325
+ });
326
+ return { value: items, redacted };
327
+ }
328
+ let redacted = false;
329
+ const entries = Object.entries(value as Record<string, unknown>).map(([childKey, childValue]) => {
330
+ const child = redactCredentialValue(childValue, childKey, seen);
331
+ redacted ||= child.redacted;
332
+ return [childKey, child.value];
333
+ });
334
+ return { value: Object.fromEntries(entries), redacted };
335
+ }
@@ -0,0 +1,249 @@
1
+ import { createStableId, optionalString } from "../../../utils/index.ts";
2
+ import { EXPLICIT_MEMORY_PATTERN, FAILURE_PATTERN, VERIFICATION_PATTERN } from "./constants.ts";
3
+ import type {
4
+ SessionMemoryPolicySnapshot,
5
+ SessionMemoryRawEventV1,
6
+ SessionMemorySegmentReason,
7
+ SessionMemorySignal,
8
+ SessionMemorySignalStrength,
9
+ SessionMemoryStateV1,
10
+ SessionMemoryTarget,
11
+ UpdateSessionMemoryInput,
12
+ } from "./types.ts";
13
+
14
+ export function createInitialState(input: {
15
+ projectKey: string;
16
+ sessionKey: string;
17
+ target: SessionMemoryTarget;
18
+ runId: string | null;
19
+ roleId: string | null;
20
+ now: string;
21
+ policy: SessionMemoryPolicySnapshot;
22
+ eventId: string;
23
+ tokenEstimate: number;
24
+ }): SessionMemoryStateV1 {
25
+ return {
26
+ schemaVersion: 1,
27
+ kind: "session-memory-state",
28
+ projectKey: input.projectKey,
29
+ sessionKey: input.sessionKey,
30
+ target: input.target,
31
+ runId: input.runId,
32
+ roleId: input.roleId,
33
+ initialized: true,
34
+ createdAt: input.now,
35
+ updatedAt: input.now,
36
+ counters: {
37
+ messageTokenEstimate: 0,
38
+ tokensSinceLastUpdate: 0,
39
+ toolCallsSinceLastUpdate: 0,
40
+ userPromptCount: 0,
41
+ assistantTurnCount: 0,
42
+ toolCallCount: 0,
43
+ failureSignalCount: 0,
44
+ verificationSignalCount: 0,
45
+ },
46
+ activeSpan: {
47
+ spanId: createStableId("span", [input.projectKey, input.sessionKey, input.eventId]),
48
+ status: "open",
49
+ openedAt: input.now,
50
+ closedAt: null,
51
+ openedByEventId: input.eventId,
52
+ lastEventId: input.eventId,
53
+ userPromptCount: 0,
54
+ toolCallCount: 0,
55
+ workStarted: false,
56
+ tokenEstimate: input.tokenEstimate,
57
+ },
58
+ pendingSignals: [],
59
+ lastSegmentId: null,
60
+ lastMemoryUpdateAt: null,
61
+ policy: input.policy,
62
+ };
63
+ }
64
+
65
+ export function applyEventToState(
66
+ state: SessionMemoryStateV1,
67
+ input: {
68
+ input: UpdateSessionMemoryInput;
69
+ now: string;
70
+ tokenEstimate: number;
71
+ policy: SessionMemoryPolicySnapshot;
72
+ teamRunId: string | null;
73
+ teamRoleId: string | null;
74
+ },
75
+ ): void {
76
+ state.policy = input.policy;
77
+ state.runId = input.teamRunId ?? state.runId;
78
+ state.roleId = input.teamRoleId ?? state.roleId;
79
+ state.target = input.input.target;
80
+ state.counters.messageTokenEstimate += input.tokenEstimate;
81
+ state.counters.tokensSinceLastUpdate += input.tokenEstimate;
82
+ if (input.input.event.type === "UserPromptSubmit") {
83
+ state.counters.userPromptCount += 1;
84
+ }
85
+ if (isToolCallCompletion(input.input.event.type)) {
86
+ state.counters.toolCallCount += 1;
87
+ state.counters.toolCallsSinceLastUpdate += 1;
88
+ }
89
+ if (isAssistantTurn(input.input.event.type)) {
90
+ state.counters.assistantTurnCount += 1;
91
+ }
92
+ if (isFailureSignal(input.input.event.type, input.input.event.payload.summary)) {
93
+ state.counters.failureSignalCount += 1;
94
+ }
95
+ if (isVerificationSignal(input.input.event.payload.summary)) {
96
+ state.counters.verificationSignalCount += 1;
97
+ }
98
+
99
+ if (state.activeSpan !== null) {
100
+ state.activeSpan.lastEventId = input.input.event.eventId;
101
+ state.activeSpan.tokenEstimate += input.tokenEstimate;
102
+ if (input.input.event.type === "UserPromptSubmit") {
103
+ state.activeSpan.userPromptCount += 1;
104
+ }
105
+ if (isWorkStartedEvent(input.input.event.type)) {
106
+ state.activeSpan.workStarted = true;
107
+ }
108
+ if (isToolCallCompletion(input.input.event.type)) {
109
+ state.activeSpan.toolCallCount += 1;
110
+ }
111
+ if (isSpanCloseEvent(input.input.event.type)) {
112
+ state.activeSpan.status = "closed";
113
+ state.activeSpan.closedAt = input.now;
114
+ }
115
+ }
116
+ }
117
+
118
+ export function decideSegment(input: {
119
+ previous: SessionMemoryStateV1;
120
+ next: SessionMemoryStateV1;
121
+ input: UpdateSessionMemoryInput;
122
+ rawEvent: SessionMemoryRawEventV1;
123
+ currentSignal: SessionMemorySignal;
124
+ tokenEstimate: number;
125
+ }): { reason: SessionMemorySegmentReason; strength: SessionMemorySignalStrength } | null {
126
+ const eventType = input.input.event.type;
127
+ const promptText = extractPromptText(input.input.rawPayload);
128
+ if (eventType === "UserPromptSubmit") {
129
+ if (input.previous.counters.userPromptCount === 0) return null;
130
+ if (EXPLICIT_MEMORY_PATTERN.test(promptText)) {
131
+ return { reason: "explicit-memory-intent", strength: "strong" };
132
+ }
133
+ if (input.previous.activeSpan?.workStarted === true) {
134
+ reopenInterruptedSpan(
135
+ input.next,
136
+ input.input.event.eventId,
137
+ input.input.receivedAt ?? input.rawEvent.receivedAt,
138
+ );
139
+ return { reason: "user-interruption", strength: "strong" };
140
+ }
141
+ return { reason: "intent-refinement", strength: "strong" };
142
+ }
143
+
144
+ if (eventType === "PermissionDenied") return { reason: "permission-denied", strength: "strong" };
145
+ if (isFailureSignal(eventType, input.input.event.payload.summary)) {
146
+ return { reason: "failure-signal", strength: "strong" };
147
+ }
148
+ if (
149
+ input.previous.counters.failureSignalCount > 0 &&
150
+ isVerificationSignal(input.input.event.payload.summary)
151
+ ) {
152
+ return { reason: "verification-after-fix", strength: "strong" };
153
+ }
154
+
155
+ if (
156
+ input.next.lastMemoryUpdateAt === null &&
157
+ input.next.counters.messageTokenEstimate >= input.next.policy.minimumMessageTokensToInit
158
+ ) {
159
+ return { reason: "session-memory-threshold", strength: "normal" };
160
+ }
161
+ if (
162
+ input.next.lastMemoryUpdateAt !== null &&
163
+ (input.next.counters.tokensSinceLastUpdate >= input.next.policy.minimumTokensBetweenUpdate ||
164
+ input.next.counters.toolCallsSinceLastUpdate >= input.next.policy.toolCallsBetweenUpdates)
165
+ ) {
166
+ return { reason: "session-memory-threshold", strength: "normal" };
167
+ }
168
+ return null;
169
+ }
170
+
171
+ export function cloneState(state: SessionMemoryStateV1): SessionMemoryStateV1 {
172
+ return JSON.parse(JSON.stringify(state)) as SessionMemoryStateV1;
173
+ }
174
+
175
+ export function appendBoundedSignal(
176
+ signals: SessionMemorySignal[],
177
+ signal: SessionMemorySignal,
178
+ ): SessionMemorySignal[] {
179
+ return [...signals, signal].slice(-50);
180
+ }
181
+
182
+ function reopenInterruptedSpan(state: SessionMemoryStateV1, eventId: string, now: string): void {
183
+ if (state.activeSpan !== null) {
184
+ state.activeSpan.status = "interrupted";
185
+ state.activeSpan.closedAt = now;
186
+ }
187
+ state.activeSpan = {
188
+ spanId: createStableId("span", [state.projectKey, state.sessionKey, eventId, now]),
189
+ status: "open",
190
+ openedAt: now,
191
+ closedAt: null,
192
+ openedByEventId: eventId,
193
+ lastEventId: eventId,
194
+ userPromptCount: 1,
195
+ toolCallCount: 0,
196
+ workStarted: false,
197
+ tokenEstimate: 0,
198
+ };
199
+ }
200
+
201
+ function isWorkStartedEvent(eventType: string): boolean {
202
+ return [
203
+ "PreToolUse",
204
+ "PermissionRequest",
205
+ "PostToolUse",
206
+ "PostToolUseFailure",
207
+ "PostToolBatch",
208
+ "TaskCreated",
209
+ "SubagentStart",
210
+ ].includes(eventType);
211
+ }
212
+
213
+ function isToolCallCompletion(eventType: string): boolean {
214
+ return eventType === "PostToolUse" || eventType === "PostToolUseFailure";
215
+ }
216
+
217
+ function isAssistantTurn(eventType: string): boolean {
218
+ return ["PostToolUse", "PostToolUseFailure", "PostToolBatch", "Stop", "StopFailure"].includes(
219
+ eventType,
220
+ );
221
+ }
222
+
223
+ function isSpanCloseEvent(eventType: string): boolean {
224
+ return ["TaskCompleted", "Stop", "SessionEnd", "SubagentStop"].includes(eventType);
225
+ }
226
+
227
+ function isFailureSignal(eventType: string, summary: string): boolean {
228
+ return (
229
+ eventType === "PostToolUseFailure" ||
230
+ eventType === "StopFailure" ||
231
+ FAILURE_PATTERN.test(summary)
232
+ );
233
+ }
234
+
235
+ function isVerificationSignal(summary: string): boolean {
236
+ return VERIFICATION_PATTERN.test(summary);
237
+ }
238
+
239
+ function extractPromptText(payload: Record<string, unknown>): string {
240
+ return (
241
+ optionalString(payload.prompt) ??
242
+ optionalString(payload.user_prompt) ??
243
+ optionalString(payload.userPrompt) ??
244
+ optionalString(payload.query) ??
245
+ optionalString(payload.message) ??
246
+ optionalString(payload.text) ??
247
+ ""
248
+ );
249
+ }