tuna-agent 0.1.76 → 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.
@@ -61,3 +61,9 @@ 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
+ * Searches all folders (2 levels deep) for the "App Info" document,
67
+ * then parses the multi-app markdown format.
68
+ */
69
+ export declare function handleSyncApps(ws: AgentWebSocketClient, code: string, taskId: string, apiUrl: string, agentToken: string, agentId: string): Promise<void>;
@@ -890,6 +890,126 @@ 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
+ * Searches all folders (2 levels deep) for the "App Info" document,
900
+ * then parses the multi-app markdown format.
901
+ */
902
+ export async function handleSyncApps(ws, code, taskId, apiUrl, agentToken, agentId) {
903
+ try {
904
+ console.log('[sync_apps] Starting knowledge sync...');
905
+ const knowledgeApi = async (method, apiPath) => {
906
+ const url = `${apiUrl}${apiPath}`;
907
+ const res = await fetch(url, {
908
+ method,
909
+ headers: {
910
+ 'Authorization': `Bearer ${agentToken}`,
911
+ 'Content-Type': 'application/json',
912
+ },
913
+ });
914
+ const json = await res.json();
915
+ if (!res.ok || (json.code && json.code >= 400)) {
916
+ throw new Error(json.message || `API error: ${res.status}`);
917
+ }
918
+ return json.data;
919
+ };
920
+ // Collect all documents (2 levels deep)
921
+ const allDocs = [];
922
+ const rootData = await knowledgeApi('GET', `/agent-knowledge?agent_id=${agentId}`);
923
+ const rootItems = rootData.items || [];
924
+ for (const item of rootItems) {
925
+ if (item.kind === 'folder') {
926
+ const folderData = await knowledgeApi('GET', `/agent-knowledge?agent_id=${agentId}&parent_id=${item._id}`);
927
+ for (const child of (folderData.items || [])) {
928
+ if (child.kind !== 'folder')
929
+ allDocs.push(child);
930
+ }
931
+ }
932
+ else {
933
+ allDocs.push(item);
934
+ }
935
+ }
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
+ }
947
+ ws.sendExtensionEvent(code, {
948
+ type: 'sync_apps_result',
949
+ apps,
950
+ });
951
+ ws.sendExtensionDone(code, taskId, { ok: true, count: apps.length });
952
+ }
953
+ catch (err) {
954
+ const error = err instanceof Error ? err.message : String(err);
955
+ console.error('[sync_apps] Error:', error);
956
+ ws.sendExtensionEvent(code, {
957
+ type: 'sync_apps_result',
958
+ apps: null,
959
+ error,
960
+ });
961
+ ws.sendExtensionDone(code, taskId, { error });
962
+ }
963
+ }
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());
998
+ }
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
+ });
1010
+ }
1011
+ return apps;
1012
+ }
893
1013
  function _sleep(ms) {
894
1014
  return new Promise(resolve => setTimeout(resolve, ms));
895
1015
  }
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.76",
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"