tuna-agent 0.1.77 → 0.1.78

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.
@@ -63,6 +63,7 @@ export declare function handleCreateCharacter(ws: AgentWebSocketClient, code: st
63
63
  export declare function handleSaveCharacterSelection(ws: AgentWebSocketClient, code: string, taskId: string, characterId: string, selectedImages: string[]): void;
64
64
  /**
65
65
  * Sync apps from the agent's knowledge base.
66
- * Reads knowledge items, identifies app-related documents, extracts app info.
66
+ * Searches all folders (2 levels deep) for the "App Info" document,
67
+ * then parses the multi-app markdown format.
67
68
  */
68
69
  export declare function handleSyncApps(ws: AgentWebSocketClient, code: string, taskId: string, apiUrl: string, agentToken: string, agentId: string): Promise<void>;
@@ -896,7 +896,8 @@ const APP_COLORS = [
896
896
  ];
897
897
  /**
898
898
  * Sync apps from the agent's knowledge base.
899
- * Reads knowledge items, identifies app-related documents, extracts app info.
899
+ * Searches all folders (2 levels deep) for the "App Info" document,
900
+ * then parses the multi-app markdown format.
900
901
  */
901
902
  export async function handleSyncApps(ws, code, taskId, apiUrl, agentToken, agentId) {
902
903
  try {
@@ -916,55 +917,33 @@ export async function handleSyncApps(ws, code, taskId, apiUrl, agentToken, agent
916
917
  }
917
918
  return json.data;
918
919
  };
919
- // 1. List all root-level knowledge items
920
+ // Collect all documents (2 levels deep)
921
+ const allDocs = [];
920
922
  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;
923
+ const rootItems = rootData.items || [];
924
+ for (const item of rootItems) {
935
925
  if (item.kind === 'folder') {
936
926
  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
- }
927
+ for (const child of (folderData.items || [])) {
928
+ if (child.kind !== 'folder')
929
+ allDocs.push(child);
947
930
  }
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
931
  }
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
- });
932
+ else {
933
+ allDocs.push(item);
965
934
  }
966
935
  }
967
- console.log(`[sync_apps] Found ${apps.length} app(s)`);
936
+ // Find "App Info" document (or any doc with "app info" in name)
937
+ const appInfoDoc = allDocs.find(d => d.name.toLowerCase().includes('app info'));
938
+ let apps = [];
939
+ if (appInfoDoc) {
940
+ const doc = await knowledgeApi('GET', `/agent-knowledge/${appInfoDoc._id}`);
941
+ apps = _parseAppInfoDocument(doc.content || '');
942
+ console.log(`[sync_apps] Parsed ${apps.length} app(s) from "${appInfoDoc.name}"`);
943
+ }
944
+ else {
945
+ console.log('[sync_apps] No "App Info" document found in knowledge');
946
+ }
968
947
  ws.sendExtensionEvent(code, {
969
948
  type: 'sync_apps_result',
970
949
  apps,
@@ -982,27 +961,54 @@ export async function handleSyncApps(ws, code, taskId, apiUrl, agentToken, agent
982
961
  ws.sendExtensionDone(code, taskId, { error });
983
962
  }
984
963
  }
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);
964
+ /**
965
+ * Parse the multi-app markdown document format.
966
+ * Each app starts with `# App Name - Tagline` and is separated by `---`.
967
+ * Features are numbered lists under `## Features`.
968
+ */
969
+ function _parseAppInfoDocument(content) {
970
+ // Split by horizontal rules (---) into app sections
971
+ const sections = content.split(/\n---\n/).filter(s => s.trim());
972
+ const apps = [];
973
+ for (let i = 0; i < sections.length; i++) {
974
+ const section = sections[i];
975
+ // Extract app name from first H1: # App Name - Tagline
976
+ const h1Match = section.match(/^#\s+(.+?)(?:\s*\n|$)/m);
977
+ if (!h1Match)
978
+ continue;
979
+ const fullTitle = h1Match[1].trim();
980
+ // Split "Name - Tagline" at first " - "
981
+ const dashIdx = fullTitle.indexOf(' - ');
982
+ const name = dashIdx > 0 ? fullTitle.substring(0, dashIdx).trim() : fullTitle;
983
+ const tagline = dashIdx > 0 ? fullTitle.substring(dashIdx + 3).trim() : '';
984
+ // Extract ID from `- **ID:** \`xxx\``
985
+ const idMatch = section.match(/\*\*ID:\*\*\s*`([^`]+)`/);
986
+ const appId = idMatch ? idMatch[1] : name.toLowerCase().replace(/\s+/g, '-');
987
+ // Extract description from ## Promotional Text > blockquote
988
+ const promoMatch = section.match(/## Promotional Text\s*\n>\s*(.+)/);
989
+ const description = promoMatch ? promoMatch[1].trim() : tagline;
990
+ // Extract features from numbered list under ## Features
991
+ const features = [];
992
+ const featSection = section.match(/## Features\n([\s\S]*?)(?=\n##|\n---|\n_Last|$)/);
993
+ if (featSection) {
994
+ const featRegex = /^\d+\.\s+\*\*([^*]+)\*\*/gm;
995
+ let m;
996
+ while ((m = featRegex.exec(featSection[1])) !== null) {
997
+ features.push(m[1].trim());
1002
998
  }
1003
999
  }
1000
+ // Skip metadata-only sections (like _Last synced_)
1001
+ if (!name || name.startsWith('_'))
1002
+ continue;
1003
+ apps.push({
1004
+ id: appId,
1005
+ name,
1006
+ description,
1007
+ features,
1008
+ color: APP_COLORS[i % APP_COLORS.length],
1009
+ });
1004
1010
  }
1005
- return features;
1011
+ return apps;
1006
1012
  }
1007
1013
  function _sleep(ms) {
1008
1014
  return new Promise(resolve => setTimeout(resolve, ms));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.77",
3
+ "version": "0.1.78",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"