@geminilight/mindos 0.6.7 → 0.6.8
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/app/app/api/ask/route.ts +35 -2
- package/app/app/api/file/route.ts +27 -0
- package/app/app/api/setup/check-port/route.ts +18 -13
- package/app/components/ImportModal.tsx +566 -61
- package/app/components/ask/AskContent.tsx +6 -1
- package/app/hooks/useAiOrganize.ts +338 -0
- package/app/hooks/useFileImport.ts +39 -2
- package/app/lib/i18n-en.ts +39 -2
- package/app/lib/i18n-zh.ts +39 -2
- package/app/next-env.d.ts +1 -1
- package/app/package.json +1 -1
- package/bin/cli.js +15 -9
- package/package.json +1 -1
- package/scripts/release.sh +1 -1
package/app/app/api/ask/route.ts
CHANGED
|
@@ -103,6 +103,34 @@ function getTurnEndData(e: AgentEvent): { toolResults: Array<{ toolName: string;
|
|
|
103
103
|
// Helpers
|
|
104
104
|
// ---------------------------------------------------------------------------
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Strip large fields (file content) from tool args before SSE serialization.
|
|
108
|
+
* The client only needs path/name for progress display, not the full content.
|
|
109
|
+
* This prevents JSON.stringify failures on oversized payloads.
|
|
110
|
+
*/
|
|
111
|
+
function sanitizeToolArgs(toolName: string, args: unknown): unknown {
|
|
112
|
+
if (!args || typeof args !== 'object') return args;
|
|
113
|
+
const a = args as Record<string, unknown>;
|
|
114
|
+
|
|
115
|
+
if (toolName === 'batch_create_files' && Array.isArray(a.files)) {
|
|
116
|
+
return {
|
|
117
|
+
...a,
|
|
118
|
+
files: (a.files as Array<Record<string, unknown>>).map(f => ({
|
|
119
|
+
path: f.path,
|
|
120
|
+
...(f.description ? { description: f.description } : {}),
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof a.content === 'string' && a.content.length > 200) {
|
|
126
|
+
return { ...a, content: `[${a.content.length} chars]` };
|
|
127
|
+
}
|
|
128
|
+
if (typeof a.text === 'string' && a.text.length > 200) {
|
|
129
|
+
return { ...a, text: `[${a.text.length} chars]` };
|
|
130
|
+
}
|
|
131
|
+
return args;
|
|
132
|
+
}
|
|
133
|
+
|
|
106
134
|
function readKnowledgeFile(filePath: string): { ok: boolean; content: string; truncated: boolean; error?: string } {
|
|
107
135
|
try {
|
|
108
136
|
const raw = getFileContent(filePath);
|
|
@@ -457,7 +485,11 @@ export async function POST(req: NextRequest) {
|
|
|
457
485
|
function send(event: MindOSSSEvent) {
|
|
458
486
|
try {
|
|
459
487
|
controller.enqueue(encoder.encode(`data:${JSON.stringify(event)}\n\n`));
|
|
460
|
-
} catch {
|
|
488
|
+
} catch (err) {
|
|
489
|
+
if (err instanceof TypeError) {
|
|
490
|
+
console.error('[ask] SSE send failed (serialization):', (err as Error).message, 'event type:', (event as { type?: string }).type);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
461
493
|
}
|
|
462
494
|
|
|
463
495
|
session.subscribe((event: AgentEvent) => {
|
|
@@ -467,11 +499,12 @@ export async function POST(req: NextRequest) {
|
|
|
467
499
|
send({ type: 'thinking_delta', delta: getThinkingDelta(event) });
|
|
468
500
|
} else if (isToolExecutionStartEvent(event)) {
|
|
469
501
|
const { toolCallId, toolName, args } = getToolExecutionStart(event);
|
|
502
|
+
const safeArgs = sanitizeToolArgs(toolName, args);
|
|
470
503
|
send({
|
|
471
504
|
type: 'tool_start',
|
|
472
505
|
toolCallId,
|
|
473
506
|
toolName,
|
|
474
|
-
args,
|
|
507
|
+
args: safeArgs,
|
|
475
508
|
});
|
|
476
509
|
} else if (isToolExecutionEndEvent(event)) {
|
|
477
510
|
const { toolCallId, output, isError } = getToolExecutionEnd(event);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export const dynamic = 'force-dynamic';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
3
5
|
import { revalidatePath } from 'next/cache';
|
|
6
|
+
import { resolveSafe } from '@/lib/core/security';
|
|
7
|
+
import { sanitizeFileName, convertToMarkdown } from '@/lib/core/file-convert';
|
|
8
|
+
import { effectiveSopRoot } from '@/lib/settings';
|
|
4
9
|
import {
|
|
5
10
|
getFileContent,
|
|
6
11
|
saveFileContent,
|
|
@@ -57,6 +62,28 @@ export async function GET(req: NextRequest) {
|
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
if (op === 'check_conflicts') {
|
|
66
|
+
const names = req.nextUrl.searchParams.get('names');
|
|
67
|
+
const space = req.nextUrl.searchParams.get('space') ?? '';
|
|
68
|
+
if (!names) return err('missing names');
|
|
69
|
+
try {
|
|
70
|
+
const mindRoot = effectiveSopRoot().trim();
|
|
71
|
+
if (!mindRoot) return err('MIND_ROOT not configured');
|
|
72
|
+
const fileNames = names.split(',').map(n => n.trim()).filter(Boolean);
|
|
73
|
+
const conflicts: string[] = [];
|
|
74
|
+
for (const name of fileNames) {
|
|
75
|
+
const sanitized = sanitizeFileName(name);
|
|
76
|
+
const { targetName } = convertToMarkdown(sanitized, '');
|
|
77
|
+
const rel = space ? path.posix.join(space, targetName) : targetName;
|
|
78
|
+
const resolved = resolveSafe(mindRoot, rel);
|
|
79
|
+
if (fs.existsSync(resolved)) conflicts.push(name);
|
|
80
|
+
}
|
|
81
|
+
return NextResponse.json({ conflicts });
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return err((e as Error).message, 500);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
if (!filePath) return err('missing path');
|
|
61
88
|
|
|
62
89
|
try {
|
|
@@ -37,16 +37,20 @@ async function findFreePort(start: number, selfPorts: Set<number>): Promise<numb
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* Derived from the incoming request URL — always reliable, no network round-trip.
|
|
40
|
+
* Ports this MindOS instance is known to be using.
|
|
42
41
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* myWebPort: derived from the incoming request URL — always reliable.
|
|
43
|
+
* myMcpPort: from MINDOS_MCP_PORT env var set by CLI / Desktop ProcessManager.
|
|
44
|
+
*
|
|
45
|
+
* We do NOT read ~/.mindos/config.json here. Config contains *configured* ports
|
|
46
|
+
* which may not actually be listening yet (e.g. first onboard before MCP starts).
|
|
47
|
+
* Env vars are only set when a process IS running, so they're safe to trust.
|
|
47
48
|
*/
|
|
48
|
-
function
|
|
49
|
-
return
|
|
49
|
+
function getKnownPorts(req: NextRequest): { myWebPort: number; myMcpPort: number } {
|
|
50
|
+
return {
|
|
51
|
+
myWebPort: parseInt(req.nextUrl.port || '0', 10),
|
|
52
|
+
myMcpPort: Number(process.env.MINDOS_MCP_PORT) || 0,
|
|
53
|
+
};
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export async function POST(req: NextRequest) {
|
|
@@ -56,10 +60,10 @@ export async function POST(req: NextRequest) {
|
|
|
56
60
|
return NextResponse.json({ error: 'Invalid port' }, { status: 400 });
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
const
|
|
63
|
+
const { myWebPort, myMcpPort } = getKnownPorts(req);
|
|
60
64
|
|
|
61
|
-
// Fast path:
|
|
62
|
-
if (
|
|
65
|
+
// Fast path: port belongs to this MindOS instance (deterministic, no network)
|
|
66
|
+
if ((myWebPort > 0 && port === myWebPort) || (myMcpPort > 0 && port === myMcpPort)) {
|
|
63
67
|
return NextResponse.json({ available: true, isSelf: true });
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -67,13 +71,14 @@ export async function POST(req: NextRequest) {
|
|
|
67
71
|
if (!inUse) {
|
|
68
72
|
return NextResponse.json({ available: true, isSelf: false });
|
|
69
73
|
}
|
|
70
|
-
// Port is occupied — check if it's another MindOS instance
|
|
74
|
+
// Port is occupied by something else — check if it's another MindOS instance
|
|
71
75
|
const self = await isSelfPort(port);
|
|
72
76
|
if (self) {
|
|
73
77
|
return NextResponse.json({ available: true, isSelf: true });
|
|
74
78
|
}
|
|
75
79
|
const skipPorts = new Set<number>();
|
|
76
|
-
if (
|
|
80
|
+
if (myWebPort > 0) skipPorts.add(myWebPort);
|
|
81
|
+
if (myMcpPort > 0) skipPorts.add(myMcpPort);
|
|
77
82
|
const suggestion = await findFreePort(port + 1, skipPorts);
|
|
78
83
|
return NextResponse.json({ available: false, isSelf: false, suggestion });
|
|
79
84
|
} catch (err) {
|