tuna-agent 0.1.75 → 0.1.77
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.
|
@@ -930,8 +930,45 @@ export class ClaudeCodeAdapter {
|
|
|
930
930
|
}
|
|
931
931
|
}
|
|
932
932
|
}
|
|
933
|
-
// Filter:
|
|
934
|
-
const
|
|
933
|
+
// Filter 1: Quality gate — reject garbage rules
|
|
934
|
+
const MAX_LEARNED_RULES = 20;
|
|
935
|
+
const MIN_CONFIDENCE = 3;
|
|
936
|
+
const qualityPatterns = patterns.filter(p => {
|
|
937
|
+
const r = p.rule.trim();
|
|
938
|
+
// Confidence gate — only persist rules seen 3+ times
|
|
939
|
+
if (p.confidence < MIN_CONFIDENCE) {
|
|
940
|
+
console.log(`[Self-Improve] Skipped (low confidence ${p.confidence}): "${r.substring(0, 80)}"`);
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
// Reject generic platitudes
|
|
944
|
+
if (/^always (prioritize|verify|ensure|keep an eye|strive|validate|check|monitor)/i.test(r)) {
|
|
945
|
+
console.log(`[Self-Improve] Skipped (generic): "${r.substring(0, 80)}"`);
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
if (/^(prior to|when making decisions|before engaging|before using)/i.test(r)) {
|
|
949
|
+
console.log(`[Self-Improve] Skipped (generic): "${r.substring(0, 80)}"`);
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
// Reject rules that look like app descriptions (hallucination from research)
|
|
953
|
+
if (/\b(block all apps|learned to read|language required|learnlock|user education)\b/i.test(r)) {
|
|
954
|
+
console.log(`[Self-Improve] Skipped (hallucinated from app idea): "${r.substring(0, 80)}"`);
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
// Reject too short or too vague rules
|
|
958
|
+
if (r.length < 30) {
|
|
959
|
+
console.log(`[Self-Improve] Skipped (too short): "${r}"`);
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
return true;
|
|
963
|
+
});
|
|
964
|
+
// Cap total rules — if already at max, skip adding more
|
|
965
|
+
if (existingRules.length >= MAX_LEARNED_RULES) {
|
|
966
|
+
console.log(`[Self-Improve] Already at max ${MAX_LEARNED_RULES} rules — skipping`);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const slotsAvailable = MAX_LEARNED_RULES - existingRules.length;
|
|
970
|
+
// Filter 2: skip patterns similar to ANY existing rule
|
|
971
|
+
const newPatterns = qualityPatterns.filter(p => {
|
|
935
972
|
return !existingRules.some(existing => ClaudeCodeAdapter.isSimilarRule(p.rule, existing));
|
|
936
973
|
});
|
|
937
974
|
// Also dedup among new patterns themselves
|
|
@@ -945,8 +982,8 @@ export class ClaudeCodeAdapter {
|
|
|
945
982
|
console.log(`[Self-Improve] ${patterns.length} patterns found but all similar to existing rules`);
|
|
946
983
|
return;
|
|
947
984
|
}
|
|
948
|
-
// Cap at 3 new rules per run
|
|
949
|
-
const toAdd = dedupedPatterns.slice(0, 3);
|
|
985
|
+
// Cap at 3 new rules per run, and respect total max
|
|
986
|
+
const toAdd = dedupedPatterns.slice(0, Math.min(3, slotsAvailable));
|
|
950
987
|
// Append new rules at the END of the Learned Rules section
|
|
951
988
|
const rulesBlock = toAdd.map(p => `- ${p.rule} (confidence: ${p.confidence})`).join('\n');
|
|
952
989
|
if (existingContent.includes(SECTION_HEADER)) {
|
|
@@ -61,3 +61,8 @@ export interface CharProfile {
|
|
|
61
61
|
export declare function handleListCharacters(ws: AgentWebSocketClient, code: string, taskId: string): void;
|
|
62
62
|
export declare function handleCreateCharacter(ws: AgentWebSocketClient, code: string, taskId: string, name: string, displayName: string, prompt: string, outputCount?: number, orientation?: string): Promise<void>;
|
|
63
63
|
export declare function handleSaveCharacterSelection(ws: AgentWebSocketClient, code: string, taskId: string, characterId: string, selectedImages: string[]): void;
|
|
64
|
+
/**
|
|
65
|
+
* Sync apps from the agent's knowledge base.
|
|
66
|
+
* Reads knowledge items, identifies app-related documents, extracts app info.
|
|
67
|
+
*/
|
|
68
|
+
export declare function handleSyncApps(ws: AgentWebSocketClient, code: string, taskId: string, apiUrl: string, agentToken: string, agentId: string): Promise<void>;
|
|
@@ -890,6 +890,120 @@ export function handleSaveCharacterSelection(ws, code, taskId, characterId, sele
|
|
|
890
890
|
ws.sendExtensionDone(code, taskId, { error });
|
|
891
891
|
}
|
|
892
892
|
}
|
|
893
|
+
const APP_COLORS = [
|
|
894
|
+
'#22D3EE', '#A78BFA', '#F472B6', '#34D399', '#FBBF24',
|
|
895
|
+
'#FB923C', '#60A5FA', '#E879F9', '#2DD4BF', '#F87171',
|
|
896
|
+
];
|
|
897
|
+
/**
|
|
898
|
+
* Sync apps from the agent's knowledge base.
|
|
899
|
+
* Reads knowledge items, identifies app-related documents, extracts app info.
|
|
900
|
+
*/
|
|
901
|
+
export async function handleSyncApps(ws, code, taskId, apiUrl, agentToken, agentId) {
|
|
902
|
+
try {
|
|
903
|
+
console.log('[sync_apps] Starting knowledge sync...');
|
|
904
|
+
const knowledgeApi = async (method, apiPath) => {
|
|
905
|
+
const url = `${apiUrl}${apiPath}`;
|
|
906
|
+
const res = await fetch(url, {
|
|
907
|
+
method,
|
|
908
|
+
headers: {
|
|
909
|
+
'Authorization': `Bearer ${agentToken}`,
|
|
910
|
+
'Content-Type': 'application/json',
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
const json = await res.json();
|
|
914
|
+
if (!res.ok || (json.code && json.code >= 400)) {
|
|
915
|
+
throw new Error(json.message || `API error: ${res.status}`);
|
|
916
|
+
}
|
|
917
|
+
return json.data;
|
|
918
|
+
};
|
|
919
|
+
// 1. List all root-level knowledge items
|
|
920
|
+
const rootData = await knowledgeApi('GET', `/agent-knowledge?agent_id=${agentId}`);
|
|
921
|
+
const items = rootData.items || [];
|
|
922
|
+
// 2. Find app-related documents
|
|
923
|
+
const apps = [];
|
|
924
|
+
let colorIdx = 0;
|
|
925
|
+
for (const item of items) {
|
|
926
|
+
const nameLower = item.name.toLowerCase();
|
|
927
|
+
const descLower = (item.description || '').toLowerCase();
|
|
928
|
+
const isApp = nameLower.includes('app') ||
|
|
929
|
+
descLower.includes('app') ||
|
|
930
|
+
descLower.includes('application') ||
|
|
931
|
+
descLower.includes('mobile app') ||
|
|
932
|
+
descLower.includes('product');
|
|
933
|
+
if (!isApp)
|
|
934
|
+
continue;
|
|
935
|
+
if (item.kind === 'folder') {
|
|
936
|
+
const folderData = await knowledgeApi('GET', `/agent-knowledge?agent_id=${agentId}&parent_id=${item._id}`);
|
|
937
|
+
const children = folderData.items || [];
|
|
938
|
+
const features = [];
|
|
939
|
+
let appDesc = item.description || '';
|
|
940
|
+
for (const child of children) {
|
|
941
|
+
if (child.kind === 'document') {
|
|
942
|
+
const doc = await knowledgeApi('GET', `/agent-knowledge/${child._id}`);
|
|
943
|
+
features.push(..._extractFeatures(doc.content || ''));
|
|
944
|
+
if (!appDesc && doc.description)
|
|
945
|
+
appDesc = doc.description;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
apps.push({
|
|
949
|
+
id: item._id,
|
|
950
|
+
name: item.name,
|
|
951
|
+
description: appDesc,
|
|
952
|
+
features: [...new Set(features)].slice(0, 20),
|
|
953
|
+
color: APP_COLORS[colorIdx++ % APP_COLORS.length],
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
else if (item.kind === 'document') {
|
|
957
|
+
const doc = await knowledgeApi('GET', `/agent-knowledge/${item._id}`);
|
|
958
|
+
apps.push({
|
|
959
|
+
id: item._id,
|
|
960
|
+
name: item.name,
|
|
961
|
+
description: item.description || doc.description || '',
|
|
962
|
+
features: [...new Set(_extractFeatures(doc.content || ''))].slice(0, 20),
|
|
963
|
+
color: APP_COLORS[colorIdx++ % APP_COLORS.length],
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
console.log(`[sync_apps] Found ${apps.length} app(s)`);
|
|
968
|
+
ws.sendExtensionEvent(code, {
|
|
969
|
+
type: 'sync_apps_result',
|
|
970
|
+
apps,
|
|
971
|
+
});
|
|
972
|
+
ws.sendExtensionDone(code, taskId, { ok: true, count: apps.length });
|
|
973
|
+
}
|
|
974
|
+
catch (err) {
|
|
975
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
976
|
+
console.error('[sync_apps] Error:', error);
|
|
977
|
+
ws.sendExtensionEvent(code, {
|
|
978
|
+
type: 'sync_apps_result',
|
|
979
|
+
apps: null,
|
|
980
|
+
error,
|
|
981
|
+
});
|
|
982
|
+
ws.sendExtensionDone(code, taskId, { error });
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
/** Extract feature names from markdown bullet lists or headings. */
|
|
986
|
+
function _extractFeatures(content) {
|
|
987
|
+
const features = [];
|
|
988
|
+
const bulletRegex = /^[\s]*[-*]\s+\*{0,2}([^*\n]+)\*{0,2}/gm;
|
|
989
|
+
let match;
|
|
990
|
+
while ((match = bulletRegex.exec(content)) !== null) {
|
|
991
|
+
const feat = match[1].trim().replace(/[:–—].+$/, '').trim();
|
|
992
|
+
if (feat.length > 2 && feat.length < 60) {
|
|
993
|
+
features.push(feat);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (features.length === 0) {
|
|
997
|
+
const headingRegex = /^#{2,4}\s+(.+)$/gm;
|
|
998
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
999
|
+
const feat = match[1].trim();
|
|
1000
|
+
if (feat.length > 2 && feat.length < 60) {
|
|
1001
|
+
features.push(feat);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return features;
|
|
1006
|
+
}
|
|
893
1007
|
function _sleep(ms) {
|
|
894
1008
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
895
1009
|
}
|
package/dist/daemon/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { loadPMState, savePMState, clearPMState } from './pm-state.js';
|
|
|
9
9
|
import { chatWithPM } from '../pm/planner.js';
|
|
10
10
|
import { executePlanAndReport, simplifyMarkdown, waitForInput } from '../utils/execution-helpers.js';
|
|
11
11
|
import { runClaude } from '../utils/claude-cli.js';
|
|
12
|
-
import { handleGetHistory, handleRetryVideo, handleGenerateIdeas, handleGenerateScript, handleGenerateScene, handleGenerateScenes, handleRenderVideo, handleListCharacters, handleCreateCharacter, handleSaveCharacterSelection, } from './extension-handlers.js';
|
|
12
|
+
import { handleGetHistory, handleRetryVideo, handleGenerateIdeas, handleGenerateScript, handleGenerateScene, handleGenerateScenes, handleRenderVideo, handleListCharacters, handleCreateCharacter, handleSaveCharacterSelection, handleSyncApps, } from './extension-handlers.js';
|
|
13
13
|
import { downloadAttachments, cleanupAttachments } from '../utils/image-download.js';
|
|
14
14
|
import { scanSkills } from '../utils/skill-scanner.js';
|
|
15
15
|
import { setupMcpConfig } from '../mcp/setup.js';
|
|
@@ -565,6 +565,12 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
565
565
|
handleSaveCharacterSelection(ws, extCode, extTaskId, msg.characterId, msg.selectedImages || []);
|
|
566
566
|
break;
|
|
567
567
|
}
|
|
568
|
+
if (extTask === 'sync_apps') {
|
|
569
|
+
(async () => {
|
|
570
|
+
await handleSyncApps(ws, extCode, extTaskId, config.apiUrl, config.agentToken, config.agentId);
|
|
571
|
+
})();
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
568
574
|
// ── Claude-powered tasks (ideas, script, etc.) ────────────────────────
|
|
569
575
|
(async () => {
|
|
570
576
|
try {
|
package/dist/mcp/setup.d.ts
CHANGED
|
@@ -5,11 +5,6 @@ import type { AgentConfig } from '../types/index.js';
|
|
|
5
5
|
* Returns 0 if MEM0_HTTP_BASE is not set or request fails.
|
|
6
6
|
*/
|
|
7
7
|
export declare function fetchMem0Count(agentName: string): Promise<number>;
|
|
8
|
-
/**
|
|
9
|
-
* Add memory via OpenMemory HTTP API (POST /api/v1/memories/).
|
|
10
|
-
* Uses MEM0_HTTP_BASE if available, falls back to SSH+mem0-add.
|
|
11
|
-
* OpenMemory API stores in both SQLite (metadata) + Qdrant (vectors) — correct path.
|
|
12
|
-
*/
|
|
13
8
|
export declare function callMem0AddMemory(text: string, agentName: string): Promise<void>;
|
|
14
9
|
/**
|
|
15
10
|
* Search Mem0 for relevant memories before task execution.
|
package/dist/mcp/setup.js
CHANGED
|
@@ -65,9 +65,46 @@ export async function fetchMem0Count(agentName) {
|
|
|
65
65
|
* Uses MEM0_HTTP_BASE if available, falls back to SSH+mem0-add.
|
|
66
66
|
* OpenMemory API stores in both SQLite (metadata) + Qdrant (vectors) — correct path.
|
|
67
67
|
*/
|
|
68
|
+
/**
|
|
69
|
+
* Validate memory text before storing. Returns null if valid, or rejection reason.
|
|
70
|
+
*/
|
|
71
|
+
function validateMemory(text) {
|
|
72
|
+
const t = text.trim();
|
|
73
|
+
// Too short — likely a fragment like "Upvoted 10 posts"
|
|
74
|
+
if (t.length < 60)
|
|
75
|
+
return `too short (${t.length} chars, min 60)`;
|
|
76
|
+
// Generic platitudes that waste memory
|
|
77
|
+
const genericPatterns = [
|
|
78
|
+
/^always (prioritize|verify|ensure|keep an eye|strive|validate|check)/i,
|
|
79
|
+
/^prior to (any|engaging|making|using)/i,
|
|
80
|
+
/^when making decisions/i,
|
|
81
|
+
/^(believe|believes) reddit is/i,
|
|
82
|
+
/^follow (accounts|verified)/i,
|
|
83
|
+
/^before (engaging|using|running)/i,
|
|
84
|
+
];
|
|
85
|
+
if (genericPatterns.some(p => p.test(t)))
|
|
86
|
+
return `generic platitude`;
|
|
87
|
+
// Individual action fragments
|
|
88
|
+
const fragmentPatterns = [
|
|
89
|
+
/^(followed|upvoted|liked|joined|saved|has \d+|observed \d+)\b/i,
|
|
90
|
+
/^top product hunt product:/i,
|
|
91
|
+
/^plan(s)? to /i,
|
|
92
|
+
/^\w+ promoted to watching/i,
|
|
93
|
+
/^\w+ is top watching/i,
|
|
94
|
+
];
|
|
95
|
+
if (fragmentPatterns.some(p => p.test(t)))
|
|
96
|
+
return `action fragment`;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
68
99
|
export async function callMem0AddMemory(text, agentName) {
|
|
69
100
|
if (!MEM0_SSH_HOST && !MEM0_HTTP_BASE)
|
|
70
101
|
throw new Error('Mem0 not configured');
|
|
102
|
+
// Validate memory quality before storing
|
|
103
|
+
const rejection = validateMemory(text);
|
|
104
|
+
if (rejection) {
|
|
105
|
+
console.log(`[Mem0 Filter] Rejected memory (${rejection}): "${text.substring(0, 80)}..."`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
71
108
|
const safeAgentName = agentName.toLowerCase().replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'agent';
|
|
72
109
|
// Try HTTP API first (OpenMemory) — stores in SQLite+Qdrant, shows up in counts
|
|
73
110
|
// Falls through to SSH+curl if HTTP is not reachable (e.g. port not exposed externally)
|