@lobehub/lobehub 2.0.0-next.251 → 2.0.0-next.253
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/CHANGELOG.md +58 -0
- package/apps/desktop/build/entitlements.mac.plist +9 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
- package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
- package/apps/desktop/src/main/locales/default/menu.ts +7 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
- package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +0 -2
- package/packages/database/migrations/meta/_journal.json +1 -1
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +37 -8
- package/packages/database/src/models/topic.ts +71 -4
- package/packages/database/src/schemas/agentCronJob.ts +1 -2
- package/packages/electron-client-ipc/src/events/system.ts +1 -0
- package/packages/memory-user-memory/src/extractors/context.ts +1 -4
- package/packages/memory-user-memory/src/extractors/experience.ts +2 -8
- package/packages/memory-user-memory/src/extractors/preference.ts +2 -8
- package/packages/memory-user-memory/src/prompts/gatekeeper.ts +123 -123
- package/packages/memory-user-memory/src/prompts/layers/context.ts +152 -152
- package/packages/memory-user-memory/src/prompts/layers/experience.ts +159 -159
- package/packages/memory-user-memory/src/prompts/layers/identity.ts +213 -213
- package/packages/memory-user-memory/src/prompts/layers/preference.ts +160 -160
- package/packages/memory-user-memory/src/services/extractExecutor.ts +33 -30
- package/packages/memory-user-memory/src/types.ts +10 -8
- package/packages/types/src/topic/topic.ts +9 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
- package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Body.tsx +4 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicGroup.tsx +74 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/CronTopicItem.tsx +40 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/CronTopicList/index.tsx +140 -0
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/TopicListContent/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/cron/[cronId]/index.tsx +664 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobCards.tsx +160 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobForm.tsx +202 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/CronJobList.tsx +137 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/hooks/useAgentCronJobs.ts +138 -0
- package/src/app/[variants]/(main)/chat/profile/features/AgentCronJobs/index.tsx +130 -0
- package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/index.tsx +33 -3
- package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
- package/src/hooks/useFetchCronTopics.ts +29 -0
- package/src/hooks/useFetchCronTopicsWithJobInfo.ts +56 -0
- package/src/hooks/useFetchTopics.ts +4 -1
- package/src/locales/default/setting.ts +44 -1
- package/src/server/routers/lambda/agentCronJob.ts +367 -0
- package/src/server/routers/lambda/image/index.test.ts +2 -2
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/topic.ts +15 -3
- package/src/server/services/aiAgent/index.ts +18 -1
- package/src/server/services/memory/userMemory/extract.ts +14 -6
- package/src/services/agentCronJob.ts +95 -0
- package/src/services/topic/index.ts +1 -0
- package/src/store/chat/slices/topic/action.ts +53 -2
- package/src/store/chat/slices/topic/initialState.ts +1 -0
- package/src/store/chat/slices/topic/selectors.ts +14 -6
- package/src/tools/placeholders.ts +1 -4
- package/apps/desktop/src/main/controllers/scripts/full-disk-access.applescript +0 -85
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { eq, inArray } from 'drizzle-orm';
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
3
|
|
|
4
|
+
import { getTestDB } from '../../../core/getTestDB';
|
|
4
5
|
import { agents, messages, sessions, topics, users } from '../../../schemas';
|
|
5
6
|
import { LobeChatDatabase } from '../../../type';
|
|
6
7
|
import { CreateTopicParams, TopicModel } from '../../topic';
|
|
7
|
-
import { getTestDB } from '../../../core/getTestDB';
|
|
8
8
|
|
|
9
9
|
const userId = 'topic-create-user';
|
|
10
10
|
const userId2 = 'topic-create-user-2';
|
|
@@ -68,11 +68,17 @@ describe('TopicModel - Create', () => {
|
|
|
68
68
|
expect(dbTopic).toHaveLength(1);
|
|
69
69
|
expect(dbTopic[0]).toEqual(createdTopic);
|
|
70
70
|
|
|
71
|
-
const associatedMessages = await serverDB
|
|
71
|
+
const associatedMessages = await serverDB
|
|
72
|
+
.select()
|
|
73
|
+
.from(messages)
|
|
74
|
+
.where(inArray(messages.id, topicData.messages!));
|
|
72
75
|
expect(associatedMessages).toHaveLength(2);
|
|
73
76
|
expect(associatedMessages.every((msg) => msg.topicId === topicId)).toBe(true);
|
|
74
77
|
|
|
75
|
-
const unassociatedMessage = await serverDB
|
|
78
|
+
const unassociatedMessage = await serverDB
|
|
79
|
+
.select()
|
|
80
|
+
.from(messages)
|
|
81
|
+
.where(eq(messages.id, 'message3'));
|
|
76
82
|
expect(unassociatedMessage[0].topicId).toBeNull();
|
|
77
83
|
});
|
|
78
84
|
|
|
@@ -169,8 +175,18 @@ describe('TopicModel - Create', () => {
|
|
|
169
175
|
const createdTopics = await topicModel.batchCreate(topicParams);
|
|
170
176
|
|
|
171
177
|
expect(createdTopics).toHaveLength(2);
|
|
172
|
-
expect(createdTopics[0]).toMatchObject({
|
|
173
|
-
|
|
178
|
+
expect(createdTopics[0]).toMatchObject({
|
|
179
|
+
title: 'Topic 1',
|
|
180
|
+
favorite: true,
|
|
181
|
+
sessionId,
|
|
182
|
+
userId,
|
|
183
|
+
});
|
|
184
|
+
expect(createdTopics[1]).toMatchObject({
|
|
185
|
+
title: 'Topic 2',
|
|
186
|
+
favorite: false,
|
|
187
|
+
sessionId,
|
|
188
|
+
userId,
|
|
189
|
+
});
|
|
174
190
|
|
|
175
191
|
const items = await serverDB.select().from(topics);
|
|
176
192
|
expect(items).toHaveLength(2);
|
|
@@ -216,7 +232,15 @@ describe('TopicModel - Create', () => {
|
|
|
216
232
|
expect(createdTopics[1].agentId).toBe('batch-agent-2');
|
|
217
233
|
expect(createdTopics[1].sessionId).toBeNull();
|
|
218
234
|
|
|
219
|
-
const dbTopics = await serverDB
|
|
235
|
+
const dbTopics = await serverDB
|
|
236
|
+
.select()
|
|
237
|
+
.from(topics)
|
|
238
|
+
.where(
|
|
239
|
+
inArray(
|
|
240
|
+
topics.id,
|
|
241
|
+
createdTopics.map((t) => t.id),
|
|
242
|
+
),
|
|
243
|
+
);
|
|
220
244
|
expect(dbTopics).toHaveLength(2);
|
|
221
245
|
expect(dbTopics.find((t) => t.id === createdTopics[0].id)?.agentId).toBe('batch-agent-1');
|
|
222
246
|
expect(dbTopics.find((t) => t.id === createdTopics[1].id)?.agentId).toBe('batch-agent-2');
|
|
@@ -236,7 +260,10 @@ describe('TopicModel - Create', () => {
|
|
|
236
260
|
]);
|
|
237
261
|
});
|
|
238
262
|
|
|
239
|
-
const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(
|
|
263
|
+
const { topic: duplicatedTopic, messages: duplicatedMessages } = await topicModel.duplicate(
|
|
264
|
+
topicId,
|
|
265
|
+
newTitle,
|
|
266
|
+
);
|
|
240
267
|
|
|
241
268
|
expect(duplicatedTopic.id).not.toBe(topicId);
|
|
242
269
|
expect(duplicatedTopic.title).toBe(newTitle);
|
|
@@ -255,7 +282,9 @@ describe('TopicModel - Create', () => {
|
|
|
255
282
|
it('should throw an error if the topic to duplicate does not exist', async () => {
|
|
256
283
|
const topicId = 'nonexistent-topic';
|
|
257
284
|
|
|
258
|
-
await expect(topicModel.duplicate(topicId)).rejects.toThrow(
|
|
285
|
+
await expect(topicModel.duplicate(topicId)).rejects.toThrow(
|
|
286
|
+
`Topic with id ${topicId} not found`,
|
|
287
|
+
);
|
|
259
288
|
});
|
|
260
289
|
});
|
|
261
290
|
});
|
|
@@ -27,8 +27,10 @@ export interface CreateTopicParams {
|
|
|
27
27
|
favorite?: boolean;
|
|
28
28
|
groupId?: string | null;
|
|
29
29
|
messages?: string[];
|
|
30
|
+
metadata?: ChatTopicMetadata;
|
|
30
31
|
sessionId?: string | null;
|
|
31
32
|
title?: string;
|
|
33
|
+
trigger?: string | null;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface QueryTopicParams {
|
|
@@ -39,6 +41,10 @@ interface QueryTopicParams {
|
|
|
39
41
|
*/
|
|
40
42
|
containerId?: string | null;
|
|
41
43
|
current?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Exclude topics by trigger types (e.g. ['cron'])
|
|
46
|
+
*/
|
|
47
|
+
excludeTriggers?: string[];
|
|
42
48
|
/**
|
|
43
49
|
* Group ID to filter topics by
|
|
44
50
|
*/
|
|
@@ -70,15 +76,24 @@ export class TopicModel {
|
|
|
70
76
|
agentId,
|
|
71
77
|
containerId,
|
|
72
78
|
current = 0,
|
|
79
|
+
excludeTriggers,
|
|
73
80
|
pageSize = 9999,
|
|
74
81
|
groupId,
|
|
75
82
|
isInbox,
|
|
76
83
|
}: QueryTopicParams = {}) => {
|
|
77
84
|
const offset = current * pageSize;
|
|
85
|
+
const excludeTriggerCondition =
|
|
86
|
+
excludeTriggers && excludeTriggers.length > 0
|
|
87
|
+
? or(isNull(topics.trigger), not(inArray(topics.trigger, excludeTriggers)))
|
|
88
|
+
: undefined;
|
|
78
89
|
|
|
79
90
|
// If groupId is provided, query topics by groupId directly
|
|
80
91
|
if (groupId) {
|
|
81
|
-
const whereCondition = and(
|
|
92
|
+
const whereCondition = and(
|
|
93
|
+
eq(topics.userId, this.userId),
|
|
94
|
+
eq(topics.groupId, groupId),
|
|
95
|
+
excludeTriggerCondition,
|
|
96
|
+
);
|
|
82
97
|
|
|
83
98
|
const [items, totalResult] = await Promise.all([
|
|
84
99
|
this.db
|
|
@@ -155,21 +170,25 @@ export class TopicModel {
|
|
|
155
170
|
updatedAt: topics.updatedAt,
|
|
156
171
|
})
|
|
157
172
|
.from(topics)
|
|
158
|
-
.where(and(eq(topics.userId, this.userId), agentCondition))
|
|
173
|
+
.where(and(eq(topics.userId, this.userId), agentCondition, excludeTriggerCondition))
|
|
159
174
|
.orderBy(desc(topics.favorite), desc(topics.updatedAt))
|
|
160
175
|
.limit(pageSize)
|
|
161
176
|
.offset(offset),
|
|
162
177
|
this.db
|
|
163
178
|
.select({ count: count(topics.id) })
|
|
164
179
|
.from(topics)
|
|
165
|
-
.where(and(eq(topics.userId, this.userId), agentCondition)),
|
|
180
|
+
.where(and(eq(topics.userId, this.userId), agentCondition, excludeTriggerCondition)),
|
|
166
181
|
]);
|
|
167
182
|
|
|
168
183
|
return { items, total: totalResult[0].count };
|
|
169
184
|
}
|
|
170
185
|
|
|
171
186
|
// Fallback to containerId-based query (backward compatibility)
|
|
172
|
-
const whereCondition = and(
|
|
187
|
+
const whereCondition = and(
|
|
188
|
+
eq(topics.userId, this.userId),
|
|
189
|
+
this.matchContainer(containerId),
|
|
190
|
+
excludeTriggerCondition,
|
|
191
|
+
);
|
|
173
192
|
|
|
174
193
|
const [items, totalResult] = await Promise.all([
|
|
175
194
|
this.db
|
|
@@ -664,4 +683,52 @@ export class TopicModel {
|
|
|
664
683
|
),
|
|
665
684
|
});
|
|
666
685
|
};
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Get cron topics grouped by cronJob for a specific agent
|
|
689
|
+
* Returns topics where trigger='cron' and metadata contains cronJobId
|
|
690
|
+
*/
|
|
691
|
+
getCronTopicsGroupedByCronJob = async (agentId: string) => {
|
|
692
|
+
const cronTopics = await this.db
|
|
693
|
+
.select({
|
|
694
|
+
createdAt: topics.createdAt,
|
|
695
|
+
favorite: topics.favorite,
|
|
696
|
+
historySummary: topics.historySummary,
|
|
697
|
+
id: topics.id,
|
|
698
|
+
metadata: topics.metadata,
|
|
699
|
+
title: topics.title,
|
|
700
|
+
trigger: topics.trigger,
|
|
701
|
+
updatedAt: topics.updatedAt,
|
|
702
|
+
})
|
|
703
|
+
.from(topics)
|
|
704
|
+
.where(
|
|
705
|
+
and(
|
|
706
|
+
eq(topics.userId, this.userId),
|
|
707
|
+
eq(topics.agentId, agentId),
|
|
708
|
+
eq(topics.trigger, 'cron'),
|
|
709
|
+
// Check if metadata contains cronJobId
|
|
710
|
+
sql`${topics.metadata}->>'cronJobId' IS NOT NULL`,
|
|
711
|
+
),
|
|
712
|
+
)
|
|
713
|
+
.orderBy(desc(topics.updatedAt));
|
|
714
|
+
|
|
715
|
+
// Group topics by cronJobId
|
|
716
|
+
const groupedTopics = new Map<string, typeof cronTopics>();
|
|
717
|
+
|
|
718
|
+
cronTopics.forEach((topic) => {
|
|
719
|
+
const cronJobId = topic.metadata?.cronJobId;
|
|
720
|
+
if (cronJobId) {
|
|
721
|
+
if (!groupedTopics.has(cronJobId)) {
|
|
722
|
+
groupedTopics.set(cronJobId, []);
|
|
723
|
+
}
|
|
724
|
+
groupedTopics.get(cronJobId)!.push(topic);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Convert Map to array of grouped objects
|
|
729
|
+
return Array.from(groupedTopics.entries()).map(([cronJobId, topicList]) => ({
|
|
730
|
+
cronJobId,
|
|
731
|
+
topics: topicList,
|
|
732
|
+
}));
|
|
733
|
+
};
|
|
667
734
|
}
|
|
@@ -32,8 +32,7 @@ export const agentCronJobs = pgTable(
|
|
|
32
32
|
agentId: text('agent_id')
|
|
33
33
|
.references(() => agents.id, { onDelete: 'cascade' })
|
|
34
34
|
.notNull(),
|
|
35
|
-
groupId: text('group_id')
|
|
36
|
-
.references(() => chatGroups.id, { onDelete: 'cascade' }),
|
|
35
|
+
groupId: text('group_id').references(() => chatGroups.id, { onDelete: 'cascade' }),
|
|
37
36
|
userId: text('user_id')
|
|
38
37
|
.references(() => users.id, { onDelete: 'cascade' })
|
|
39
38
|
.notNull(),
|
|
@@ -16,10 +16,7 @@ export class ContextExtractor extends BaseMemoryExtractor<ContextMemory> {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
getSchema() {
|
|
19
|
-
return buildGenerateObjectSchema(
|
|
20
|
-
ContextMemorySchema,
|
|
21
|
-
{ name: 'context_extraction' },
|
|
22
|
-
);
|
|
19
|
+
return buildGenerateObjectSchema(ContextMemorySchema, { name: 'context_extraction' });
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
getResultSchema() {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
|
2
2
|
|
|
3
3
|
import { experiencePrompt } from '../prompts';
|
|
4
|
-
import {
|
|
5
|
-
ExperienceMemory,
|
|
6
|
-
ExperienceMemorySchema
|
|
7
|
-
} from '../schemas';
|
|
4
|
+
import { ExperienceMemory, ExperienceMemorySchema } from '../schemas';
|
|
8
5
|
import { ExtractorTemplateProps } from '../types';
|
|
9
6
|
import { buildGenerateObjectSchema } from '../utils/zod';
|
|
10
7
|
import { BaseMemoryExtractor } from './base';
|
|
@@ -19,10 +16,7 @@ export class ExperienceExtractor extends BaseMemoryExtractor<ExperienceMemory> {
|
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
getSchema() {
|
|
22
|
-
return buildGenerateObjectSchema(
|
|
23
|
-
ExperienceMemorySchema,
|
|
24
|
-
{ name: 'experience_extraction' },
|
|
25
|
-
);
|
|
19
|
+
return buildGenerateObjectSchema(ExperienceMemorySchema, { name: 'experience_extraction' });
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
getResultSchema() {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { renderPlaceholderTemplate } from '@lobechat/context-engine';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
PreferenceMemory,
|
|
5
|
-
PreferenceMemorySchema,
|
|
6
|
-
} from '../schemas';
|
|
7
3
|
import { preferencePrompt } from '../prompts';
|
|
4
|
+
import { PreferenceMemory, PreferenceMemorySchema } from '../schemas';
|
|
8
5
|
import { ExtractorTemplateProps } from '../types';
|
|
9
6
|
import { buildGenerateObjectSchema } from '../utils/zod';
|
|
10
7
|
import { BaseMemoryExtractor } from './base';
|
|
@@ -19,10 +16,7 @@ export class PreferenceExtractor extends BaseMemoryExtractor<PreferenceMemory> {
|
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
getSchema() {
|
|
22
|
-
return buildGenerateObjectSchema(
|
|
23
|
-
PreferenceMemorySchema,
|
|
24
|
-
{ name: 'preference_extraction' },
|
|
25
|
-
);
|
|
19
|
+
return buildGenerateObjectSchema(PreferenceMemorySchema, { name: 'preference_extraction' });
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
getResultSchema() {
|
|
@@ -1,127 +1,127 @@
|
|
|
1
1
|
export const gatekeeperPrompt = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
2
|
+
'You are a "gate keeper" that analyzes conversations between user and assistant to determine which memory layers contain extractable information worth storing.',
|
|
3
|
+
'',
|
|
4
|
+
'Your role is to efficiently filter conversations by identifying which of the five memory layers are present and valuable enough to extract.',
|
|
5
|
+
'',
|
|
6
|
+
'Bias toward sensitivity: when in doubt, prefer setting `shouldExtract: true` so downstream extractors can refine. Minor, incremental or clarifying updates should still be allowed to pass.',
|
|
7
|
+
'',
|
|
8
|
+
'## Memory Layer Definitions',
|
|
9
|
+
'',
|
|
10
|
+
'Evaluate the conversation for these memory layers:',
|
|
11
|
+
'',
|
|
12
|
+
'**Activity Layer** - Episodic events with clear timelines and participants:',
|
|
13
|
+
'',
|
|
14
|
+
'- Meetings, calls, errands, and appointments with start/end times',
|
|
15
|
+
'- Locations (physical or virtual) and timezones',
|
|
16
|
+
'- Status (planned, completed) and follow-up actions',
|
|
17
|
+
'- Narratives summarizing what happened and feedback/notes',
|
|
18
|
+
'',
|
|
19
|
+
'**Identity Layer** - Information about actors, relationships, and personal attributes:',
|
|
20
|
+
'',
|
|
21
|
+
'- Describing labels and demographics',
|
|
22
|
+
'- Current focusing and life priorities',
|
|
23
|
+
'- Relationships with other people',
|
|
24
|
+
'- Background and experience',
|
|
25
|
+
'- Roles in various contexts',
|
|
26
|
+
'- Identity should remain compact. Route lists of tools, stacks, or',
|
|
27
|
+
' implementation techniques to the preference layer unless they materially',
|
|
28
28
|
" change the user's biography.",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
29
|
+
'',
|
|
30
|
+
'**Context Layer** - Only capture brand-new situational frameworks and ongoing situations that have not been recorded before:',
|
|
31
|
+
'',
|
|
32
|
+
'- Distinct situations, topics, research threads, sessions, or rounds that are clearly first-time mentions',
|
|
33
|
+
'- Ongoing projects and their status when the project itself is new, not just progress updates',
|
|
34
|
+
'- Long-term goals and objectives that are newly introduced',
|
|
35
|
+
'- Persistent relationships and dynamics that have not been logged previously',
|
|
36
|
+
'- Environmental factors and recurring situations only when they represent a new context',
|
|
37
|
+
'- Timelines and temporal context that establish a novel situation',
|
|
38
|
+
'- Impact and urgency assessments tied to a new context',
|
|
39
|
+
'- If the content overlaps with learnings or takeaways, treat it as Experience instead of Context',
|
|
40
|
+
'',
|
|
41
|
+
'**Preference Layer** - Durable user choices and behavioral directives that apply across multiple conversations:',
|
|
42
|
+
'',
|
|
43
|
+
'- Explicit long-term preferences with clear temporal markers (e.g., "always", "never", "from now on")',
|
|
44
|
+
'- Likes and dislikes stated as persistent traits (e.g., "I prefer...", "I don\'t like...")',
|
|
45
|
+
'- Workflow and approach preferences explicitly stated as ongoing (what to do, not how to implement)',
|
|
46
|
+
'- Communication style preferences explicitly stated as ongoing',
|
|
47
|
+
'- Response formatting preferences explicitly stated as ongoing',
|
|
48
|
+
'- Priority levels for preference resolution',
|
|
49
|
+
'- Capture recurring tool/stacks/technology usage as preferences rather than',
|
|
50
|
+
' duplicating them as identity facts.',
|
|
51
|
+
'',
|
|
52
|
+
'Note: Task-specific requirements, constraints for a single object/entity, or one-time instructions do NOT belong to this layer.',
|
|
53
|
+
'',
|
|
54
|
+
'**Experience Layer** - Learned insights and practical knowledge worth reusing and',
|
|
55
|
+
'sharing publicly:',
|
|
56
|
+
'',
|
|
57
|
+
'- Lessons learned and insights gained, especially surprising aha/yurika moments',
|
|
58
|
+
'- Practical tricks and techniques that solve tough or non-obvious problems',
|
|
59
|
+
'- Transferable knowledge and wisdom that would make a strong blog/knowledge-base',
|
|
60
|
+
' snippet',
|
|
61
|
+
'- Situation → Reasoning → Action → Outcome patterns',
|
|
62
|
+
'- Key learnings from experiences with self-assessed confidence/impact',
|
|
63
|
+
'- Skip routine or repetitive steps already well covered in retrieved memories',
|
|
64
|
+
'',
|
|
65
|
+
'## Gate Keeping Guidelines',
|
|
66
|
+
'',
|
|
67
|
+
'For each layer, consider:',
|
|
68
|
+
'',
|
|
69
|
+
'- **Relevance**: Does the conversation contain information for this layer?',
|
|
70
|
+
'- **Value**: Is the information substantial enough to be worth extracting?',
|
|
71
|
+
'- **Clarity**: Is the information clear and extractable (not vague or ambiguous)?',
|
|
72
|
+
'',
|
|
73
|
+
'Additionally, review the retrieved similar memories first (top {{ topK }} items below). If the conversation content is clearly and fully covered by existing memories with no meaningful nuance or update, set `shouldExtract: false`.',
|
|
74
|
+
'For the Experience layer, prefer `shouldExtract: true` only when the conversation adds a new takeaway, aha moment, or harder challenge resolution beyond what is already recorded.',
|
|
75
|
+
'For the Context layer specifically, favor `shouldExtract: false` unless the conversation introduces a genuinely new situation/topic/research/session that is not already represented in existing context memories or overlaps with Experience items.',
|
|
76
|
+
'Otherwise, favor `shouldExtract: true` for:',
|
|
77
|
+
'',
|
|
78
|
+
'- Novel facts OR even small clarifications/precision improvements to existing details',
|
|
79
|
+
'- Incremental changes to status/progress of an ongoing context',
|
|
80
|
+
'- A new preference OR a refinement that affects future behavior',
|
|
81
|
+
'- Distinct experiences/lessons, even if closely related to prior items',
|
|
82
|
+
'',
|
|
83
|
+
'Preference-specific filters (STRICT - when in doubt, set shouldExtract: false):',
|
|
84
|
+
'',
|
|
85
|
+
'CRITICAL: Distinguish between "user\'s behavioral preferences" vs. "task requirements for a specific deliverable":',
|
|
86
|
+
'',
|
|
87
|
+
'- If the instruction describes what the OUTPUT should be like (e.g., "name should have natural vibe", "don\'t use surname"), it\'s a task requirement, NOT a user preference.',
|
|
88
|
+
'- If the instruction describes how the ASSISTANT should behave across conversations (e.g., "always explain before coding", "never use jargon"), it\'s a preference.',
|
|
89
|
+
'',
|
|
90
|
+
'Specific exclusion rules:',
|
|
91
|
+
'',
|
|
92
92
|
"- Do NOT treat the conversation's language as a preference unless the user explicitly states a persistent preference for language.",
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
'- Exclude one-off task instructions and implementation steps; these are not preferences.',
|
|
94
|
+
'- Exclude task-specific constraints, requirements, or clarifications (e.g., "don\'t use surname Wang", "make it summer-themed", "natural vibe") - these describe the current task deliverable, not user\'s persistent preferences.',
|
|
95
|
+
'- Exclude requirements or attributes for a specific object/entity being discussed (e.g., naming a cat, designing a logo) - these are task parameters, not user preferences.',
|
|
96
|
+
'- Exclude in-conversation clarifications or corrections (e.g., user first says X, then says "no, not X") - these refine the current task, not future behavior.',
|
|
97
|
+
'- Only extract when the user uses explicit preference markers like "I always...", "I prefer...", "I never...", "from now on...", "please always..." that clearly indicate cross-session intent.',
|
|
98
|
+
'- If the preference is about how to complete THIS specific task rather than how to behave in FUTURE tasks, exclude it.',
|
|
99
|
+
'- Ask yourself: "Would this apply to a completely different conversation topic?" If no, it\'s not a preference.',
|
|
100
|
+
'',
|
|
101
|
+
'Examples of what NOT to extract:',
|
|
102
|
+
'',
|
|
103
|
+
'- User asks for a cat name with "natural vibe" and "born in summer" → NOT a preference (task constraint)',
|
|
104
|
+
'- User says "don\'t use surname Wang" for naming → NOT a preference (task clarification)',
|
|
105
|
+
'- User wants a "minimalist design" for this logo → NOT a preference (task requirement)',
|
|
106
|
+
'',
|
|
107
|
+
'Examples of what TO extract:',
|
|
108
|
+
'',
|
|
109
|
+
'- "I always prefer concise responses" → IS a preference (cross-session directive)',
|
|
110
|
+
'- "Never use technical jargon when explaining to me" → IS a preference (persistent rule)',
|
|
111
|
+
'- "From now on, please format code with comments" → IS a preference (ongoing instruction)',
|
|
112
|
+
'',
|
|
113
|
+
'If uncertain about novelty, but information is relevant and clear, set `shouldExtract: true` and include a short reasoning.',
|
|
114
|
+
'',
|
|
115
|
+
'## Output Guidelines',
|
|
116
|
+
'',
|
|
117
|
+
'For each memory layer (activity, identity, context, preference, experience), provide:',
|
|
118
|
+
'',
|
|
119
|
+
'- `shouldExtract`: Boolean indicating whether extraction is recommended',
|
|
120
|
+
'- `reasoning`: Brief explanation of your decision (write in English)',
|
|
121
|
+
'',
|
|
122
|
+
'## Retrieved Memory (Top {{ topK }})',
|
|
123
|
+
'',
|
|
124
|
+
'Use the list below as context for deduplication and to determine whether extraction is necessary. Do not repeat these verbatim unless needed for comparison.',
|
|
125
|
+
'',
|
|
126
|
+
'{{ retrievedContext }}',
|
|
127
127
|
].join('\n');
|