@geminilight/mindos 0.6.22 → 0.6.25
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/README.md +58 -46
- package/README_zh.md +58 -46
- package/app/app/.well-known/agent-card.json/route.ts +34 -0
- package/app/app/api/a2a/route.ts +100 -0
- package/app/app/api/file/import/route.ts +0 -2
- package/app/app/api/setup/route.ts +2 -0
- package/app/components/Backlinks.tsx +2 -2
- package/app/components/Breadcrumb.tsx +1 -1
- package/app/components/CsvView.tsx +41 -19
- package/app/components/DirView.tsx +2 -2
- package/app/components/FileTree.tsx +14 -1
- package/app/components/GuideCard.tsx +6 -2
- package/app/components/HomeContent.tsx +2 -2
- package/app/components/RightAskPanel.tsx +17 -10
- package/app/components/SearchModal.tsx +3 -3
- package/app/components/SidebarLayout.tsx +4 -2
- package/app/components/SyncStatusBar.tsx +2 -2
- package/app/components/ask/AskContent.tsx +6 -6
- package/app/components/ask/FileChip.tsx +1 -1
- package/app/components/ask/MentionPopover.tsx +2 -2
- package/app/components/ask/MessageList.tsx +1 -1
- package/app/components/ask/SlashCommandPopover.tsx +1 -1
- package/app/components/explore/UseCaseCard.tsx +2 -2
- package/app/components/help/HelpContent.tsx +6 -1
- package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
- package/app/components/panels/DiscoverPanel.tsx +3 -3
- package/app/components/panels/PanelNavRow.tsx +2 -2
- package/app/components/panels/PluginsPanel.tsx +1 -1
- package/app/components/panels/SearchPanel.tsx +3 -3
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/app/components/settings/AiTab.tsx +4 -4
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpTab.tsx +22 -4
- package/app/components/settings/UpdateTab.tsx +1 -1
- package/app/components/setup/index.tsx +9 -3
- package/app/components/walkthrough/WalkthroughProvider.tsx +2 -2
- package/app/hooks/useAskPanel.ts +7 -3
- package/app/hooks/useFileImport.ts +1 -1
- package/app/lib/a2a/agent-card.ts +107 -0
- package/app/lib/a2a/index.ts +23 -0
- package/app/lib/a2a/task-handler.ts +228 -0
- package/app/lib/a2a/types.ts +158 -0
- package/app/lib/agent/tools.ts +1 -1
- package/app/lib/core/fs-ops.ts +3 -2
- package/app/lib/fs.ts +28 -11
- package/app/lib/i18n-en.ts +2 -0
- package/app/lib/i18n-zh.ts +2 -0
- package/app/lib/settings.ts +1 -1
- package/bin/cli.js +48 -20
- package/bin/commands/agent.js +18 -0
- package/bin/commands/api.js +58 -0
- package/bin/commands/ask.js +101 -0
- package/bin/commands/file.js +286 -0
- package/bin/commands/search.js +51 -0
- package/bin/commands/space.js +167 -0
- package/bin/commands/status.js +69 -0
- package/bin/lib/command.js +156 -0
- package/mcp/dist/index.cjs +1 -1
- package/mcp/src/index.ts +1 -1
- package/package.json +1 -1
- package/skills/mindos/SKILL.md +2 -2
- package/skills/mindos-zh/SKILL.md +2 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Agent Card generator for MindOS.
|
|
3
|
+
* Builds the card dynamically from current settings and MCP tools.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AgentCard, AgentSkill } from './types';
|
|
7
|
+
|
|
8
|
+
/** MindOS knowledge-base skills exposed via A2A */
|
|
9
|
+
const KB_SKILLS: AgentSkill[] = [
|
|
10
|
+
{
|
|
11
|
+
id: 'kb-search',
|
|
12
|
+
name: 'Search Knowledge Base',
|
|
13
|
+
description: 'Full-text search across all notes, files, and spaces in the knowledge base.',
|
|
14
|
+
tags: ['search', 'knowledge', 'notes'],
|
|
15
|
+
examples: ['Search for notes about machine learning', 'Find files mentioning project deadlines'],
|
|
16
|
+
inputModes: ['text/plain'],
|
|
17
|
+
outputModes: ['text/plain', 'application/json'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'kb-read',
|
|
21
|
+
name: 'Read Note',
|
|
22
|
+
description: 'Read the full content of a specific file in the knowledge base.',
|
|
23
|
+
tags: ['read', 'file', 'content'],
|
|
24
|
+
examples: ['Read the file at Projects/roadmap.md'],
|
|
25
|
+
inputModes: ['text/plain'],
|
|
26
|
+
outputModes: ['text/plain'],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'kb-write',
|
|
30
|
+
name: 'Write Note',
|
|
31
|
+
description: 'Create or update a note in the knowledge base. Supports .md and .csv files.',
|
|
32
|
+
tags: ['write', 'create', 'update'],
|
|
33
|
+
examples: ['Create a new meeting note at Work/meetings/2026-03-30.md'],
|
|
34
|
+
inputModes: ['text/plain'],
|
|
35
|
+
outputModes: ['text/plain'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'kb-list',
|
|
39
|
+
name: 'List Files',
|
|
40
|
+
description: 'List files and directory structure of the knowledge base.',
|
|
41
|
+
tags: ['list', 'tree', 'structure'],
|
|
42
|
+
examples: ['Show the file tree', 'List all spaces'],
|
|
43
|
+
inputModes: ['text/plain'],
|
|
44
|
+
outputModes: ['application/json'],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'kb-organize',
|
|
48
|
+
name: 'Organize Files',
|
|
49
|
+
description: 'AI-powered file organization into appropriate spaces and directories.',
|
|
50
|
+
tags: ['organize', 'ai', 'structure'],
|
|
51
|
+
examples: ['Organize imported files into relevant spaces'],
|
|
52
|
+
inputModes: ['text/plain'],
|
|
53
|
+
outputModes: ['text/plain'],
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build the A2A Agent Card for this MindOS instance.
|
|
59
|
+
* @param baseUrl The publicly reachable base URL (e.g. http://localhost:3456)
|
|
60
|
+
*/
|
|
61
|
+
export function buildAgentCard(baseUrl: string): AgentCard {
|
|
62
|
+
let version = process.env.npm_package_version || '0.0.0';
|
|
63
|
+
|
|
64
|
+
// Try reading version from package.json as fallback
|
|
65
|
+
if (version === '0.0.0') {
|
|
66
|
+
try {
|
|
67
|
+
const fs = require('fs');
|
|
68
|
+
const path = require('path');
|
|
69
|
+
const projRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
70
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projRoot, 'package.json'), 'utf-8'));
|
|
71
|
+
if (pkg.version) version = pkg.version;
|
|
72
|
+
} catch { /* use default */ }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name: 'MindOS',
|
|
77
|
+
description: 'Personal knowledge management system with AI-powered Spaces, Instructions, and Skills. Store, organize, and retrieve knowledge through natural language.',
|
|
78
|
+
version,
|
|
79
|
+
provider: {
|
|
80
|
+
organization: 'MindOS',
|
|
81
|
+
url: baseUrl,
|
|
82
|
+
},
|
|
83
|
+
supportedInterfaces: [
|
|
84
|
+
{
|
|
85
|
+
url: `${baseUrl}/api/a2a`,
|
|
86
|
+
protocolBinding: 'JSONRPC',
|
|
87
|
+
protocolVersion: '1.0',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
capabilities: {
|
|
91
|
+
streaming: false, // Phase 1: no streaming
|
|
92
|
+
pushNotifications: false,
|
|
93
|
+
stateTransitionHistory: false,
|
|
94
|
+
},
|
|
95
|
+
defaultInputModes: ['text/plain'],
|
|
96
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
97
|
+
skills: KB_SKILLS,
|
|
98
|
+
securitySchemes: {
|
|
99
|
+
bearer: {
|
|
100
|
+
httpAuthSecurityScheme: {
|
|
101
|
+
scheme: 'Bearer',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
securityRequirements: [{ bearer: [] }],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { buildAgentCard } from './agent-card';
|
|
2
|
+
export { handleSendMessage, handleGetTask, handleCancelTask } from './task-handler';
|
|
3
|
+
export { A2A_ERRORS } from './types';
|
|
4
|
+
export type {
|
|
5
|
+
AgentCard,
|
|
6
|
+
AgentInterface,
|
|
7
|
+
AgentCapabilities,
|
|
8
|
+
AgentSkill,
|
|
9
|
+
SecurityScheme,
|
|
10
|
+
JsonRpcRequest,
|
|
11
|
+
JsonRpcResponse,
|
|
12
|
+
JsonRpcError,
|
|
13
|
+
TaskState,
|
|
14
|
+
MessageRole,
|
|
15
|
+
MessagePart,
|
|
16
|
+
A2AMessage,
|
|
17
|
+
TaskStatus,
|
|
18
|
+
TaskArtifact,
|
|
19
|
+
A2ATask,
|
|
20
|
+
SendMessageParams,
|
|
21
|
+
GetTaskParams,
|
|
22
|
+
CancelTaskParams,
|
|
23
|
+
} from './types';
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Task handler for MindOS.
|
|
3
|
+
* Routes A2A SendMessage requests to internal MCP tools.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
import type {
|
|
8
|
+
A2ATask,
|
|
9
|
+
A2AMessage,
|
|
10
|
+
SendMessageParams,
|
|
11
|
+
GetTaskParams,
|
|
12
|
+
CancelTaskParams,
|
|
13
|
+
TaskState,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
/* ── In-memory Task Store (Phase 1) ───────────────────────────────────── */
|
|
17
|
+
// NOTE: In-memory Map is lost on serverless cold starts / process restarts.
|
|
18
|
+
// Acceptable for Phase 1. Phase 2 should use persistent storage if needed.
|
|
19
|
+
|
|
20
|
+
const tasks = new Map<string, A2ATask>();
|
|
21
|
+
const MAX_TASKS = 1000;
|
|
22
|
+
|
|
23
|
+
function pruneOldTasks() {
|
|
24
|
+
if (tasks.size <= MAX_TASKS) return;
|
|
25
|
+
// Remove oldest completed tasks first
|
|
26
|
+
const entries = [...tasks.entries()].sort((a, b) =>
|
|
27
|
+
new Date(a[1].status.timestamp).getTime() - new Date(b[1].status.timestamp).getTime()
|
|
28
|
+
);
|
|
29
|
+
const toRemove = entries.slice(0, tasks.size - MAX_TASKS);
|
|
30
|
+
for (const [id] of toRemove) tasks.delete(id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ── Skill Router ─────────────────────────────────────────────────────── */
|
|
34
|
+
|
|
35
|
+
interface SkillRoute {
|
|
36
|
+
pattern: RegExp;
|
|
37
|
+
tool: string;
|
|
38
|
+
extractParams: (text: string) => Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const SKILL_ROUTES: SkillRoute[] = [
|
|
42
|
+
{
|
|
43
|
+
pattern: /^(?:search|find|look\s*up|query)\b/i,
|
|
44
|
+
tool: 'search_notes',
|
|
45
|
+
extractParams: (text) => ({ q: text.replace(/^(?:search|find|look\s*up|query)\s+(?:for\s+)?/i, '').trim() }),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /^(?:read|get|show|open|view)\s+(?:the\s+)?(?:file\s+)?(?:at\s+)?(.+\.(?:md|csv))/i,
|
|
49
|
+
tool: 'read_file',
|
|
50
|
+
extractParams: (text) => {
|
|
51
|
+
const match = text.match(/(?:at\s+)?([^\s]+\.(?:md|csv))/i);
|
|
52
|
+
return { path: match?.[1] ?? '' };
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /^(?:list|show|tree)\s+(?:files|spaces|structure)/i,
|
|
57
|
+
tool: 'list_files',
|
|
58
|
+
extractParams: () => ({}),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: /^(?:list|show)\s+spaces/i,
|
|
62
|
+
tool: 'list_spaces',
|
|
63
|
+
extractParams: () => ({}),
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
function routeToTool(text: string): { tool: string; params: Record<string, string> } | null {
|
|
68
|
+
for (const route of SKILL_ROUTES) {
|
|
69
|
+
if (route.pattern.test(text)) {
|
|
70
|
+
return { tool: route.tool, params: route.extractParams(text) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* ── Execute via internal API ─────────────────────────────────────────── */
|
|
77
|
+
|
|
78
|
+
const TOOL_TIMEOUT_MS = 10_000;
|
|
79
|
+
|
|
80
|
+
async function fetchWithTimeout(url: string): Promise<Response> {
|
|
81
|
+
const controller = new AbortController();
|
|
82
|
+
const timeout = setTimeout(() => controller.abort(), TOOL_TIMEOUT_MS);
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
85
|
+
return res;
|
|
86
|
+
} finally {
|
|
87
|
+
clearTimeout(timeout);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Sanitize file path: reject traversal attempts */
|
|
92
|
+
function sanitizePath(p: string): string {
|
|
93
|
+
if (!p || p.includes('..') || p.includes('\0')) throw new Error('Invalid path');
|
|
94
|
+
// Normalize double slashes and strip leading slashes
|
|
95
|
+
return p.replace(/\/\//g, '/').replace(/^\/+/, '');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function executeTool(tool: string, params: Record<string, string>): Promise<string> {
|
|
99
|
+
const baseUrl = `http://localhost:${process.env.PORT || 3456}`;
|
|
100
|
+
|
|
101
|
+
switch (tool) {
|
|
102
|
+
case 'search_notes': {
|
|
103
|
+
const q = (params.q || '').slice(0, 500); // limit query length
|
|
104
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/search?q=${encodeURIComponent(q)}`);
|
|
105
|
+
if (!res.ok) throw new Error(`Search failed: ${res.status}`);
|
|
106
|
+
const data = await res.json();
|
|
107
|
+
return JSON.stringify(data, null, 2);
|
|
108
|
+
}
|
|
109
|
+
case 'read_file': {
|
|
110
|
+
const safePath = sanitizePath(params.path || '');
|
|
111
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/file?path=${encodeURIComponent(safePath)}`);
|
|
112
|
+
if (!res.ok) throw new Error(`Read failed: ${res.status}`);
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
return typeof data.content === 'string' ? data.content : JSON.stringify(data);
|
|
115
|
+
}
|
|
116
|
+
case 'list_files': {
|
|
117
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/files`);
|
|
118
|
+
if (!res.ok) throw new Error(`List failed: ${res.status}`);
|
|
119
|
+
const data = await res.json();
|
|
120
|
+
return JSON.stringify(data, null, 2);
|
|
121
|
+
}
|
|
122
|
+
case 'list_spaces': {
|
|
123
|
+
const res = await fetchWithTimeout(`${baseUrl}/api/files`);
|
|
124
|
+
if (!res.ok) throw new Error(`List failed: ${res.status}`);
|
|
125
|
+
const data = await res.json();
|
|
126
|
+
const spaces = (data.tree ?? data.files ?? []).filter((n: { isSpace?: boolean }) => n.isSpace);
|
|
127
|
+
return JSON.stringify(spaces, null, 2);
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unknown tool: ${tool}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── Public API ────────────────────────────────────────────────────────── */
|
|
135
|
+
|
|
136
|
+
export async function handleSendMessage(params: SendMessageParams): Promise<A2ATask> {
|
|
137
|
+
const taskId = randomUUID();
|
|
138
|
+
const now = new Date().toISOString();
|
|
139
|
+
|
|
140
|
+
// Extract text from message parts
|
|
141
|
+
const text = params.message.parts
|
|
142
|
+
.map(p => p.text ?? (p.data ? JSON.stringify(p.data) : ''))
|
|
143
|
+
.join(' ')
|
|
144
|
+
.trim();
|
|
145
|
+
|
|
146
|
+
if (!text) {
|
|
147
|
+
const failedTask = createTask(taskId, 'TASK_STATE_FAILED', 'Empty message — no text content found.', now);
|
|
148
|
+
tasks.set(taskId, failedTask);
|
|
149
|
+
return failedTask;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create task in WORKING state
|
|
153
|
+
const task = createTask(taskId, 'TASK_STATE_WORKING', undefined, now);
|
|
154
|
+
task.history = [params.message];
|
|
155
|
+
tasks.set(taskId, task);
|
|
156
|
+
pruneOldTasks();
|
|
157
|
+
|
|
158
|
+
// Route to tool
|
|
159
|
+
const route = routeToTool(text);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
let result: string;
|
|
163
|
+
if (route) {
|
|
164
|
+
result = await executeTool(route.tool, route.params);
|
|
165
|
+
} else {
|
|
166
|
+
// Fallback: treat as search query
|
|
167
|
+
result = await executeTool('search_notes', { q: text });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Update task to completed
|
|
171
|
+
task.status = {
|
|
172
|
+
state: 'TASK_STATE_COMPLETED',
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
task.artifacts = [{
|
|
176
|
+
artifactId: randomUUID(),
|
|
177
|
+
name: 'result',
|
|
178
|
+
parts: [{ text: result, mediaType: 'text/plain' }],
|
|
179
|
+
}];
|
|
180
|
+
task.history.push({
|
|
181
|
+
role: 'ROLE_AGENT',
|
|
182
|
+
parts: [{ text: result }],
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return task;
|
|
186
|
+
} catch (err) {
|
|
187
|
+
task.status = {
|
|
188
|
+
state: 'TASK_STATE_FAILED',
|
|
189
|
+
message: {
|
|
190
|
+
role: 'ROLE_AGENT',
|
|
191
|
+
parts: [{ text: `Error: ${(err as Error).message}` }],
|
|
192
|
+
},
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
};
|
|
195
|
+
return task;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function handleGetTask(params: GetTaskParams): A2ATask | null {
|
|
200
|
+
return tasks.get(params.id) ?? null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function handleCancelTask(params: CancelTaskParams): A2ATask | null {
|
|
204
|
+
const task = tasks.get(params.id);
|
|
205
|
+
if (!task) return null;
|
|
206
|
+
|
|
207
|
+
const terminalStates: TaskState[] = ['TASK_STATE_COMPLETED', 'TASK_STATE_FAILED', 'TASK_STATE_CANCELED', 'TASK_STATE_REJECTED'];
|
|
208
|
+
if (terminalStates.includes(task.status.state)) return null; // not cancelable
|
|
209
|
+
|
|
210
|
+
task.status = {
|
|
211
|
+
state: 'TASK_STATE_CANCELED',
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
};
|
|
214
|
+
return task;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ── Helpers ───────────────────────────────────────────────────────────── */
|
|
218
|
+
|
|
219
|
+
function createTask(id: string, state: TaskState, errorMessage: string | undefined, timestamp: string): A2ATask {
|
|
220
|
+
return {
|
|
221
|
+
id,
|
|
222
|
+
status: {
|
|
223
|
+
state,
|
|
224
|
+
timestamp,
|
|
225
|
+
...(errorMessage ? { message: { role: 'ROLE_AGENT', parts: [{ text: errorMessage }] } } : {}),
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Protocol v1.0 — Core Types for MindOS Agent Card & Task handling.
|
|
3
|
+
* Subset of the full spec: only what's needed for Phase 1 (Server mode).
|
|
4
|
+
* Reference: https://a2a-protocol.org/latest/specification/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* ── Agent Card ────────────────────────────────────────────────────────── */
|
|
8
|
+
|
|
9
|
+
export interface AgentCard {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
version: string;
|
|
13
|
+
provider: {
|
|
14
|
+
organization: string;
|
|
15
|
+
url: string;
|
|
16
|
+
};
|
|
17
|
+
supportedInterfaces: AgentInterface[];
|
|
18
|
+
capabilities: AgentCapabilities;
|
|
19
|
+
defaultInputModes: string[];
|
|
20
|
+
defaultOutputModes: string[];
|
|
21
|
+
skills: AgentSkill[];
|
|
22
|
+
securitySchemes?: Record<string, SecurityScheme>;
|
|
23
|
+
securityRequirements?: Record<string, string[]>[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AgentInterface {
|
|
27
|
+
url: string;
|
|
28
|
+
protocolBinding: 'JSONRPC' | 'GRPC' | 'HTTP_JSON';
|
|
29
|
+
protocolVersion: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AgentCapabilities {
|
|
33
|
+
streaming: boolean;
|
|
34
|
+
pushNotifications: boolean;
|
|
35
|
+
stateTransitionHistory: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AgentSkill {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
tags?: string[];
|
|
43
|
+
examples?: string[];
|
|
44
|
+
inputModes?: string[];
|
|
45
|
+
outputModes?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SecurityScheme {
|
|
49
|
+
httpAuthSecurityScheme?: {
|
|
50
|
+
scheme: string;
|
|
51
|
+
bearerFormat?: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── JSON-RPC ──────────────────────────────────────────────────────────── */
|
|
56
|
+
|
|
57
|
+
export interface JsonRpcRequest {
|
|
58
|
+
jsonrpc: '2.0';
|
|
59
|
+
id: string | number;
|
|
60
|
+
method: string;
|
|
61
|
+
params?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface JsonRpcResponse {
|
|
65
|
+
jsonrpc: '2.0';
|
|
66
|
+
id: string | number | null;
|
|
67
|
+
result?: unknown;
|
|
68
|
+
error?: JsonRpcError;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface JsonRpcError {
|
|
72
|
+
code: number;
|
|
73
|
+
message: string;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ── A2A Messages & Tasks ──────────────────────────────────────────────── */
|
|
78
|
+
|
|
79
|
+
export type TaskState =
|
|
80
|
+
| 'TASK_STATE_SUBMITTED'
|
|
81
|
+
| 'TASK_STATE_WORKING'
|
|
82
|
+
| 'TASK_STATE_INPUT_REQUIRED'
|
|
83
|
+
| 'TASK_STATE_COMPLETED'
|
|
84
|
+
| 'TASK_STATE_FAILED'
|
|
85
|
+
| 'TASK_STATE_CANCELED'
|
|
86
|
+
| 'TASK_STATE_REJECTED';
|
|
87
|
+
|
|
88
|
+
export type MessageRole = 'ROLE_USER' | 'ROLE_AGENT';
|
|
89
|
+
|
|
90
|
+
export interface MessagePart {
|
|
91
|
+
text?: string;
|
|
92
|
+
data?: unknown;
|
|
93
|
+
mediaType?: string;
|
|
94
|
+
metadata?: Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface A2AMessage {
|
|
98
|
+
role: MessageRole;
|
|
99
|
+
parts: MessagePart[];
|
|
100
|
+
metadata?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface TaskStatus {
|
|
104
|
+
state: TaskState;
|
|
105
|
+
message?: A2AMessage;
|
|
106
|
+
timestamp: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface TaskArtifact {
|
|
110
|
+
artifactId: string;
|
|
111
|
+
name?: string;
|
|
112
|
+
description?: string;
|
|
113
|
+
parts: MessagePart[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface A2ATask {
|
|
117
|
+
id: string;
|
|
118
|
+
contextId?: string;
|
|
119
|
+
status: TaskStatus;
|
|
120
|
+
artifacts?: TaskArtifact[];
|
|
121
|
+
history?: A2AMessage[];
|
|
122
|
+
metadata?: Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ── A2A Method Params ─────────────────────────────────────────────────── */
|
|
126
|
+
|
|
127
|
+
export interface SendMessageParams {
|
|
128
|
+
message: A2AMessage;
|
|
129
|
+
configuration?: {
|
|
130
|
+
acceptedOutputModes?: string[];
|
|
131
|
+
blocking?: boolean;
|
|
132
|
+
historyLength?: number;
|
|
133
|
+
};
|
|
134
|
+
metadata?: Record<string, unknown>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface GetTaskParams {
|
|
138
|
+
id: string;
|
|
139
|
+
historyLength?: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface CancelTaskParams {
|
|
143
|
+
id: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ── Error Codes ───────────────────────────────────────────────────────── */
|
|
147
|
+
|
|
148
|
+
export const A2A_ERRORS = {
|
|
149
|
+
TASK_NOT_FOUND: { code: -32001, message: 'Task not found' },
|
|
150
|
+
TASK_NOT_CANCELABLE: { code: -32002, message: 'Task not cancelable' },
|
|
151
|
+
UNSUPPORTED_OPERATION: { code: -32004, message: 'Unsupported operation' },
|
|
152
|
+
CONTENT_TYPE_NOT_SUPPORTED: { code: -32005, message: 'Content type not supported' },
|
|
153
|
+
PARSE_ERROR: { code: -32700, message: 'Parse error' },
|
|
154
|
+
INVALID_REQUEST: { code: -32600, message: 'Invalid request' },
|
|
155
|
+
METHOD_NOT_FOUND: { code: -32601, message: 'Method not found' },
|
|
156
|
+
INVALID_PARAMS: { code: -32602, message: 'Invalid params' },
|
|
157
|
+
INTERNAL_ERROR: { code: -32603, message: 'Internal error' },
|
|
158
|
+
} as const;
|
package/app/lib/agent/tools.ts
CHANGED
|
@@ -532,7 +532,7 @@ export const knowledgeBaseTools: AgentTool<any>[] = [
|
|
|
532
532
|
{
|
|
533
533
|
name: 'create_file',
|
|
534
534
|
label: 'Create File',
|
|
535
|
-
description: 'Create a new file. Only .md and .csv files are allowed. Parent directories are created automatically.',
|
|
535
|
+
description: 'Create a new file. Only .md and .csv files are allowed. Parent directories are created automatically. Does NOT create Space scaffolding (INSTRUCTION.md/README.md). Use create_space to create a Space.',
|
|
536
536
|
parameters: CreateFileParams,
|
|
537
537
|
execute: safeExecute(async (_id, params: Static<typeof CreateFileParams>) => {
|
|
538
538
|
createFile(params.path, params.content ?? '');
|
package/app/lib/core/fs-ops.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolveSafe, assertWithinRoot } from './security';
|
|
4
4
|
import { MindOSError, ErrorCodes } from '@/lib/errors';
|
|
5
|
-
import {
|
|
5
|
+
import { cleanDirName, INSTRUCTION_TEMPLATE, README_TEMPLATE } from './space-scaffold';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Reads the content of a file given a relative path from mindRoot.
|
|
@@ -33,6 +33,8 @@ export function writeFile(mindRoot: string, filePath: string, content: string):
|
|
|
33
33
|
/**
|
|
34
34
|
* Creates a new file. Throws if the file already exists.
|
|
35
35
|
* Creates parent directories as needed.
|
|
36
|
+
* NOTE: Does NOT auto-scaffold Space files (INSTRUCTION.md/README.md).
|
|
37
|
+
* Use createSpaceFilesystem() or convertToSpace() for explicit Space creation.
|
|
36
38
|
*/
|
|
37
39
|
export function createFile(mindRoot: string, filePath: string, initialContent = ''): void {
|
|
38
40
|
const resolved = resolveSafe(mindRoot, filePath);
|
|
@@ -41,7 +43,6 @@ export function createFile(mindRoot: string, filePath: string, initialContent =
|
|
|
41
43
|
}
|
|
42
44
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
43
45
|
fs.writeFileSync(resolved, initialContent, 'utf-8');
|
|
44
|
-
scaffoldIfNewSpace(mindRoot, filePath);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/**
|
package/app/lib/fs.ts
CHANGED
|
@@ -59,9 +59,36 @@ const CACHE_TTL_MS = 5_000; // 5 seconds
|
|
|
59
59
|
|
|
60
60
|
let _treeVersion = 0;
|
|
61
61
|
|
|
62
|
+
function buildCache(root: string): FileTreeCache {
|
|
63
|
+
const tree = buildFileTree(root);
|
|
64
|
+
const allFiles: string[] = [];
|
|
65
|
+
function collect(nodes: FileNode[]) {
|
|
66
|
+
for (const n of nodes) {
|
|
67
|
+
if (n.type === 'file') allFiles.push(n.path);
|
|
68
|
+
else if (n.children) collect(n.children);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
collect(tree);
|
|
72
|
+
return { tree, allFiles, timestamp: Date.now() };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sameFileList(a: string[], b: string[]): boolean {
|
|
76
|
+
if (a.length !== b.length) return false;
|
|
77
|
+
const sa = [...a].sort();
|
|
78
|
+
const sb = [...b].sort();
|
|
79
|
+
return sa.every((p, i) => p === sb[i]);
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
/** Monotonically increasing counter — bumped on every file mutation so the
|
|
63
83
|
* client can cheaply detect changes without rebuilding the full tree. */
|
|
64
84
|
export function getTreeVersion(): number {
|
|
85
|
+
if (_cache && !isCacheValid()) {
|
|
86
|
+
const next = buildCache(getMindRoot());
|
|
87
|
+
const changed = !sameFileList(_cache.allFiles, next.allFiles);
|
|
88
|
+
_cache = next;
|
|
89
|
+
_searchIndex = null;
|
|
90
|
+
if (changed) _treeVersion++;
|
|
91
|
+
}
|
|
65
92
|
return _treeVersion;
|
|
66
93
|
}
|
|
67
94
|
|
|
@@ -80,17 +107,7 @@ export function invalidateCache(): void {
|
|
|
80
107
|
function ensureCache(): FileTreeCache {
|
|
81
108
|
if (isCacheValid()) return _cache!;
|
|
82
109
|
const root = getMindRoot();
|
|
83
|
-
|
|
84
|
-
// Extract all file paths from the tree to avoid a second full traversal.
|
|
85
|
-
const allFiles: string[] = [];
|
|
86
|
-
function collect(nodes: FileNode[]) {
|
|
87
|
-
for (const n of nodes) {
|
|
88
|
-
if (n.type === 'file') allFiles.push(n.path);
|
|
89
|
-
else if (n.children) collect(n.children);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
collect(tree);
|
|
93
|
-
_cache = { tree, allFiles, timestamp: Date.now() };
|
|
110
|
+
_cache = buildCache(root);
|
|
94
111
|
return _cache;
|
|
95
112
|
}
|
|
96
113
|
|
package/app/lib/i18n-en.ts
CHANGED
package/app/lib/i18n-zh.ts
CHANGED
package/app/lib/settings.ts
CHANGED
|
@@ -148,7 +148,7 @@ function parseGuideState(raw: unknown): GuideState | undefined {
|
|
|
148
148
|
askedAI: obj.askedAI === true,
|
|
149
149
|
nextStepIndex: typeof obj.nextStepIndex === 'number' ? obj.nextStepIndex : 0,
|
|
150
150
|
walkthroughStep: typeof obj.walkthroughStep === 'number' ? obj.walkthroughStep : undefined,
|
|
151
|
-
walkthroughDismissed: obj.walkthroughDismissed ===
|
|
151
|
+
walkthroughDismissed: typeof obj.walkthroughDismissed === 'boolean' ? obj.walkthroughDismissed : undefined,
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|