@geminilight/mindos 0.6.28 → 0.6.29
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/a2a/agents/route.ts +9 -0
- package/app/app/api/a2a/delegations/route.ts +9 -0
- package/app/app/api/a2a/discover/route.ts +2 -0
- package/app/app/api/a2a/route.ts +6 -6
- package/app/app/api/acp/detect/route.ts +91 -0
- package/app/app/api/acp/registry/route.ts +31 -0
- package/app/app/api/acp/session/route.ts +55 -0
- package/app/app/layout.tsx +2 -0
- package/app/components/DirView.tsx +64 -2
- package/app/components/FileTree.tsx +19 -0
- package/app/components/GuideCard.tsx +7 -17
- package/app/components/MarkdownView.tsx +2 -0
- package/app/components/SearchModal.tsx +234 -80
- package/app/components/agents/AgentDetailContent.tsx +3 -5
- package/app/components/agents/AgentsContentPage.tsx +21 -6
- package/app/components/agents/AgentsPanelA2aTab.tsx +445 -0
- package/app/components/agents/SkillDetailPopover.tsx +4 -9
- package/app/components/agents/agents-content-model.ts +2 -2
- package/app/components/help/HelpContent.tsx +9 -9
- package/app/components/panels/AgentsPanel.tsx +1 -0
- package/app/components/panels/AgentsPanelAgentDetail.tsx +5 -8
- package/app/components/panels/AgentsPanelHubNav.tsx +8 -1
- package/app/components/panels/EchoPanel.tsx +5 -1
- package/app/components/panels/EchoSidebarStats.tsx +136 -0
- package/app/components/settings/KnowledgeTab.tsx +3 -6
- package/app/components/settings/McpSkillsSection.tsx +4 -5
- package/app/components/settings/McpTab.tsx +6 -8
- package/app/components/setup/StepSecurity.tsx +4 -5
- package/app/components/setup/index.tsx +5 -11
- package/app/components/ui/Toaster.tsx +39 -0
- package/app/hooks/useA2aRegistry.ts +6 -1
- package/app/hooks/useAcpDetection.ts +65 -0
- package/app/hooks/useAcpRegistry.ts +51 -0
- package/app/hooks/useDelegationHistory.ts +49 -0
- package/app/lib/a2a/client.ts +49 -5
- package/app/lib/a2a/orchestrator.ts +0 -1
- package/app/lib/a2a/task-handler.ts +4 -4
- package/app/lib/a2a/types.ts +15 -0
- package/app/lib/acp/acp-tools.ts +93 -0
- package/app/lib/acp/bridge.ts +138 -0
- package/app/lib/acp/index.ts +24 -0
- package/app/lib/acp/registry.ts +135 -0
- package/app/lib/acp/session.ts +264 -0
- package/app/lib/acp/subprocess.ts +209 -0
- package/app/lib/acp/types.ts +136 -0
- package/app/lib/agent/tools.ts +2 -1
- package/app/lib/i18n/_core.ts +22 -0
- package/app/lib/i18n/index.ts +35 -0
- package/app/lib/i18n/modules/ai-chat.ts +215 -0
- package/app/lib/i18n/modules/common.ts +71 -0
- package/app/lib/i18n/modules/features.ts +153 -0
- package/app/lib/i18n/modules/knowledge.ts +425 -0
- package/app/lib/i18n/modules/navigation.ts +151 -0
- package/app/lib/i18n/modules/onboarding.ts +523 -0
- package/app/lib/i18n/modules/panels.ts +1052 -0
- package/app/lib/i18n/modules/settings.ts +585 -0
- package/app/lib/i18n-en.ts +2 -1518
- package/app/lib/i18n-zh.ts +2 -1542
- package/app/lib/i18n.ts +3 -6
- package/app/lib/toast.ts +79 -0
- package/bin/cli.js +25 -25
- package/bin/commands/file.js +29 -2
- package/bin/commands/space.js +249 -91
- package/package.json +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP Subprocess Manager — Spawn and communicate with ACP agent processes.
|
|
3
|
+
* ACP agents communicate via JSON-RPC 2.0 over stdio (stdin/stdout).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, type ChildProcess } from 'child_process';
|
|
7
|
+
import type {
|
|
8
|
+
AcpJsonRpcRequest,
|
|
9
|
+
AcpJsonRpcResponse,
|
|
10
|
+
AcpRegistryEntry,
|
|
11
|
+
AcpTransportType,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
/* ── Types ─────────────────────────────────────────────────────────────── */
|
|
15
|
+
|
|
16
|
+
export interface AcpProcess {
|
|
17
|
+
id: string;
|
|
18
|
+
agentId: string;
|
|
19
|
+
proc: ChildProcess;
|
|
20
|
+
alive: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type MessageCallback = (msg: AcpJsonRpcResponse) => void;
|
|
24
|
+
|
|
25
|
+
/* ── State ─────────────────────────────────────────────────────────────── */
|
|
26
|
+
|
|
27
|
+
const processes = new Map<string, AcpProcess>();
|
|
28
|
+
const messageListeners = new Map<string, Set<MessageCallback>>();
|
|
29
|
+
let rpcIdCounter = 1;
|
|
30
|
+
|
|
31
|
+
/* ── Public API ────────────────────────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Spawn an ACP agent subprocess.
|
|
35
|
+
*/
|
|
36
|
+
export function spawnAcpAgent(
|
|
37
|
+
entry: AcpRegistryEntry,
|
|
38
|
+
options?: { env?: Record<string, string> },
|
|
39
|
+
): AcpProcess {
|
|
40
|
+
const { cmd, args } = buildCommand(entry);
|
|
41
|
+
|
|
42
|
+
const mergedEnv = {
|
|
43
|
+
...process.env,
|
|
44
|
+
...(entry.env ?? {}),
|
|
45
|
+
...(options?.env ?? {}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const proc = spawn(cmd, args, {
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
50
|
+
env: mergedEnv,
|
|
51
|
+
shell: false,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const id = `acp-${entry.id}-${Date.now()}`;
|
|
55
|
+
const acpProc: AcpProcess = { id, agentId: entry.id, proc, alive: true };
|
|
56
|
+
|
|
57
|
+
processes.set(id, acpProc);
|
|
58
|
+
messageListeners.set(id, new Set());
|
|
59
|
+
|
|
60
|
+
// Parse newline-delimited JSON from stdout
|
|
61
|
+
let buffer = '';
|
|
62
|
+
proc.stdout?.on('data', (chunk: Buffer) => {
|
|
63
|
+
buffer += chunk.toString();
|
|
64
|
+
const lines = buffer.split('\n');
|
|
65
|
+
buffer = lines.pop() ?? '';
|
|
66
|
+
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed) continue;
|
|
70
|
+
try {
|
|
71
|
+
const msg = JSON.parse(trimmed) as AcpJsonRpcResponse;
|
|
72
|
+
const listeners = messageListeners.get(id);
|
|
73
|
+
if (listeners) {
|
|
74
|
+
for (const cb of listeners) cb(msg);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Not valid JSON — skip (could be agent debug output)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
proc.on('close', () => {
|
|
83
|
+
acpProc.alive = false;
|
|
84
|
+
messageListeners.delete(id);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
proc.on('error', () => {
|
|
88
|
+
acpProc.alive = false;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return acpProc;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Send a JSON-RPC message to an ACP agent's stdin.
|
|
96
|
+
*/
|
|
97
|
+
export function sendMessage(acpProc: AcpProcess, method: string, params?: Record<string, unknown>): string {
|
|
98
|
+
if (!acpProc.alive || !acpProc.proc.stdin?.writable) {
|
|
99
|
+
throw new Error(`ACP process ${acpProc.id} is not alive`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const id = `rpc-${rpcIdCounter++}`;
|
|
103
|
+
const request: AcpJsonRpcRequest = {
|
|
104
|
+
jsonrpc: '2.0',
|
|
105
|
+
id,
|
|
106
|
+
method,
|
|
107
|
+
...(params ? { params } : {}),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
acpProc.proc.stdin.write(JSON.stringify(request) + '\n');
|
|
111
|
+
return id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Register a callback for messages from an ACP agent.
|
|
116
|
+
* Returns an unsubscribe function.
|
|
117
|
+
*/
|
|
118
|
+
export function onMessage(acpProc: AcpProcess, callback: MessageCallback): () => void {
|
|
119
|
+
const listeners = messageListeners.get(acpProc.id);
|
|
120
|
+
if (!listeners) throw new Error(`ACP process ${acpProc.id} not found`);
|
|
121
|
+
|
|
122
|
+
listeners.add(callback);
|
|
123
|
+
return () => { listeners.delete(callback); };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Send a JSON-RPC request and wait for a response with the matching ID.
|
|
128
|
+
*/
|
|
129
|
+
export function sendAndWait(
|
|
130
|
+
acpProc: AcpProcess,
|
|
131
|
+
method: string,
|
|
132
|
+
params?: Record<string, unknown>,
|
|
133
|
+
timeoutMs = 30_000,
|
|
134
|
+
): Promise<AcpJsonRpcResponse> {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const rpcId = sendMessage(acpProc, method, params);
|
|
137
|
+
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
unsub();
|
|
140
|
+
reject(new Error(`ACP RPC timeout after ${timeoutMs}ms for method: ${method}`));
|
|
141
|
+
}, timeoutMs);
|
|
142
|
+
|
|
143
|
+
const unsub = onMessage(acpProc, (msg) => {
|
|
144
|
+
if (String(msg.id) === rpcId) {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
unsub();
|
|
147
|
+
resolve(msg);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Kill an ACP agent process.
|
|
155
|
+
*/
|
|
156
|
+
export function killAgent(acpProc: AcpProcess): void {
|
|
157
|
+
if (acpProc.alive && acpProc.proc.pid) {
|
|
158
|
+
acpProc.proc.kill('SIGTERM');
|
|
159
|
+
// Force kill after 5s if still alive
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
if (acpProc.alive) {
|
|
162
|
+
acpProc.proc.kill('SIGKILL');
|
|
163
|
+
}
|
|
164
|
+
}, 5000);
|
|
165
|
+
}
|
|
166
|
+
acpProc.alive = false;
|
|
167
|
+
processes.delete(acpProc.id);
|
|
168
|
+
messageListeners.delete(acpProc.id);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get a process by its ID.
|
|
173
|
+
*/
|
|
174
|
+
export function getProcess(id: string): AcpProcess | undefined {
|
|
175
|
+
return processes.get(id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get all active processes.
|
|
180
|
+
*/
|
|
181
|
+
export function getActiveProcesses(): AcpProcess[] {
|
|
182
|
+
return [...processes.values()].filter(p => p.alive);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Kill all active ACP processes. Used for cleanup.
|
|
187
|
+
*/
|
|
188
|
+
export function killAllAgents(): void {
|
|
189
|
+
for (const proc of processes.values()) {
|
|
190
|
+
killAgent(proc);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ── Internal ──────────────────────────────────────────────────────────── */
|
|
195
|
+
|
|
196
|
+
function buildCommand(entry: AcpRegistryEntry): { cmd: string; args: string[] } {
|
|
197
|
+
const transport: AcpTransportType = entry.transport;
|
|
198
|
+
|
|
199
|
+
switch (transport) {
|
|
200
|
+
case 'npx':
|
|
201
|
+
return { cmd: 'npx', args: [entry.command, ...(entry.args ?? [])] };
|
|
202
|
+
case 'uvx':
|
|
203
|
+
return { cmd: 'uvx', args: [entry.command, ...(entry.args ?? [])] };
|
|
204
|
+
case 'binary':
|
|
205
|
+
case 'stdio':
|
|
206
|
+
default:
|
|
207
|
+
return { cmd: entry.command, args: entry.args ?? [] };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP (Agent Client Protocol) — Core Types for MindOS ACP integration.
|
|
3
|
+
* ACP uses JSON-RPC 2.0 over stdio (subprocess model), not HTTP.
|
|
4
|
+
* Sessions are stateful with prompt turns, tool calls, and streaming updates.
|
|
5
|
+
* Reference: https://agentclientprotocol.com
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ── Transport ────────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
/** How an ACP agent is spawned */
|
|
11
|
+
export type AcpTransportType = 'stdio' | 'npx' | 'uvx' | 'binary';
|
|
12
|
+
|
|
13
|
+
/* ── Capabilities ─────────────────────────────────────────────────────── */
|
|
14
|
+
|
|
15
|
+
/** What MindOS exposes as an ACP agent */
|
|
16
|
+
export interface AcpCapabilities {
|
|
17
|
+
streaming: boolean;
|
|
18
|
+
toolCalls: boolean;
|
|
19
|
+
multiTurn: boolean;
|
|
20
|
+
cancellation: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ── Session ──────────────────────────────────────────────────────────── */
|
|
24
|
+
|
|
25
|
+
export type AcpSessionState = 'idle' | 'active' | 'error';
|
|
26
|
+
|
|
27
|
+
export interface AcpSession {
|
|
28
|
+
id: string;
|
|
29
|
+
agentId: string;
|
|
30
|
+
state: AcpSessionState;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
lastActivityAt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ── JSON-RPC (ACP uses JSON-RPC 2.0 over stdio) ─────────────────────── */
|
|
36
|
+
|
|
37
|
+
export interface AcpJsonRpcRequest {
|
|
38
|
+
jsonrpc: '2.0';
|
|
39
|
+
id: string | number;
|
|
40
|
+
method: string;
|
|
41
|
+
params?: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface AcpJsonRpcResponse {
|
|
45
|
+
jsonrpc: '2.0';
|
|
46
|
+
id: string | number | null;
|
|
47
|
+
result?: unknown;
|
|
48
|
+
error?: AcpJsonRpcError;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface AcpJsonRpcError {
|
|
52
|
+
code: number;
|
|
53
|
+
message: string;
|
|
54
|
+
data?: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* ── Prompt ────────────────────────────────────────────────────────────── */
|
|
58
|
+
|
|
59
|
+
export interface AcpPromptRequest {
|
|
60
|
+
sessionId: string;
|
|
61
|
+
text: string;
|
|
62
|
+
metadata?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface AcpPromptResponse {
|
|
66
|
+
sessionId: string;
|
|
67
|
+
text: string;
|
|
68
|
+
done: boolean;
|
|
69
|
+
toolCalls?: AcpToolCall[];
|
|
70
|
+
metadata?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── Session Updates (streaming) ──────────────────────────────────────── */
|
|
74
|
+
|
|
75
|
+
export type AcpUpdateType = 'text' | 'tool_call' | 'tool_result' | 'done' | 'error';
|
|
76
|
+
|
|
77
|
+
export interface AcpSessionUpdate {
|
|
78
|
+
sessionId: string;
|
|
79
|
+
type: AcpUpdateType;
|
|
80
|
+
text?: string;
|
|
81
|
+
toolCall?: AcpToolCall;
|
|
82
|
+
toolResult?: AcpToolResult;
|
|
83
|
+
error?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ── Tool Calls ───────────────────────────────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
export interface AcpToolCall {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string;
|
|
91
|
+
arguments: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface AcpToolResult {
|
|
95
|
+
callId: string;
|
|
96
|
+
result: string;
|
|
97
|
+
isError?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── Registry ─────────────────────────────────────────────────────────── */
|
|
101
|
+
|
|
102
|
+
/** An entry from the ACP registry (registry.json) */
|
|
103
|
+
export interface AcpRegistryEntry {
|
|
104
|
+
id: string;
|
|
105
|
+
name: string;
|
|
106
|
+
description: string;
|
|
107
|
+
version?: string;
|
|
108
|
+
transport: AcpTransportType;
|
|
109
|
+
command: string;
|
|
110
|
+
args?: string[];
|
|
111
|
+
env?: Record<string, string>;
|
|
112
|
+
tags?: string[];
|
|
113
|
+
homepage?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Parsed registry response */
|
|
117
|
+
export interface AcpRegistry {
|
|
118
|
+
version: string;
|
|
119
|
+
agents: AcpRegistryEntry[];
|
|
120
|
+
fetchedAt: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ── Error Codes ──────────────────────────────────────────────────────── */
|
|
124
|
+
|
|
125
|
+
export const ACP_ERRORS = {
|
|
126
|
+
SESSION_NOT_FOUND: { code: -32001, message: 'Session not found' },
|
|
127
|
+
SESSION_BUSY: { code: -32002, message: 'Session is busy' },
|
|
128
|
+
AGENT_NOT_FOUND: { code: -32003, message: 'Agent not found in registry' },
|
|
129
|
+
SPAWN_FAILED: { code: -32004, message: 'Failed to spawn agent process' },
|
|
130
|
+
TRANSPORT_ERROR: { code: -32005, message: 'Transport error' },
|
|
131
|
+
PARSE_ERROR: { code: -32700, message: 'Parse error' },
|
|
132
|
+
INVALID_REQUEST: { code: -32600, message: 'Invalid request' },
|
|
133
|
+
METHOD_NOT_FOUND: { code: -32601, message: 'Method not found' },
|
|
134
|
+
INVALID_PARAMS: { code: -32602, message: 'Invalid params' },
|
|
135
|
+
INTERNAL_ERROR: { code: -32603, message: 'Internal error' },
|
|
136
|
+
} as const;
|
package/app/lib/agent/tools.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { readSkillContentByName, scanSkillDirs } from '@/lib/pi-integration/skills';
|
|
11
11
|
import { callMcporterTool, createMcporterAgentTools, listMcporterServers, listMcporterTools } from '@/lib/pi-integration/mcporter';
|
|
12
12
|
import { a2aTools } from '@/lib/a2a/a2a-tools';
|
|
13
|
+
import { acpTools } from '@/lib/acp/acp-tools';
|
|
13
14
|
|
|
14
15
|
// Max chars per file to avoid token overflow (~100k chars ≈ ~25k tokens)
|
|
15
16
|
const MAX_FILE_CHARS = 20_000;
|
|
@@ -187,7 +188,7 @@ export function getOrganizeTools(): AgentTool<any>[] {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
export async function getRequestScopedTools(): Promise<AgentTool<any>[]> {
|
|
190
|
-
const baseTools = [...knowledgeBaseTools, ...a2aTools];
|
|
191
|
+
const baseTools = [...knowledgeBaseTools, ...a2aTools, ...acpTools];
|
|
191
192
|
try {
|
|
192
193
|
const result = await listMcporterServers();
|
|
193
194
|
const okServers = (result.servers ?? []).filter((server) => server.status === 'ok');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively widen literal string types to `string` so zh values can differ from en.
|
|
3
|
+
*
|
|
4
|
+
* Without Widen<T>, TypeScript would require zh strings to be the *exact* literal values from en.
|
|
5
|
+
* E.g., if en has `common: { hello: "hello" as const }`, zh would be required to also have "hello".
|
|
6
|
+
*
|
|
7
|
+
* This type converts all string literals to generic `string`, allowing Chinese translations to use
|
|
8
|
+
* completely different values while maintaining structure compatibility.
|
|
9
|
+
*
|
|
10
|
+
* Example:
|
|
11
|
+
* ```
|
|
12
|
+
* en = { msg: "Hello" as const } // string literal "Hello"
|
|
13
|
+
* zh_without = { msg: "你好" } satisfies typeof en // ❌ ERROR: "你好" not assignable to "Hello"
|
|
14
|
+
* zh_with = { msg: "你好" } satisfies Widen<typeof en> // ✅ OK: string satisfies string
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export type Widen<T> =
|
|
18
|
+
T extends string ? string :
|
|
19
|
+
T extends readonly (infer U)[] ? readonly Widen<U>[] :
|
|
20
|
+
T extends (...args: infer A) => infer R ? (...args: A) => Widen<R> :
|
|
21
|
+
T extends object ? { [K in keyof T]: Widen<T[K]> } :
|
|
22
|
+
T;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Widen } from './_core';
|
|
2
|
+
import { commonEn, commonZh } from './modules/common';
|
|
3
|
+
import { navigationEn, navigationZh } from './modules/navigation';
|
|
4
|
+
import { aiChatEn, aiChatZh } from './modules/ai-chat';
|
|
5
|
+
import { knowledgeEn, knowledgeZh } from './modules/knowledge';
|
|
6
|
+
import { panelsEn, panelsZh } from './modules/panels';
|
|
7
|
+
import { settingsEn, settingsZh } from './modules/settings';
|
|
8
|
+
import { onboardingEn, onboardingZh } from './modules/onboarding';
|
|
9
|
+
import { featuresEn, featuresZh } from './modules/features';
|
|
10
|
+
|
|
11
|
+
export const en = {
|
|
12
|
+
...commonEn,
|
|
13
|
+
...navigationEn,
|
|
14
|
+
...aiChatEn,
|
|
15
|
+
...knowledgeEn,
|
|
16
|
+
...panelsEn,
|
|
17
|
+
...settingsEn,
|
|
18
|
+
...onboardingEn,
|
|
19
|
+
...featuresEn,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export const zh: Widen<typeof en> = {
|
|
23
|
+
...commonZh,
|
|
24
|
+
...navigationZh,
|
|
25
|
+
...aiChatZh,
|
|
26
|
+
...knowledgeZh,
|
|
27
|
+
...panelsZh,
|
|
28
|
+
...settingsZh,
|
|
29
|
+
...onboardingZh,
|
|
30
|
+
...featuresZh,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Locale = 'en' | 'zh';
|
|
34
|
+
export const messages = { en, zh } as const;
|
|
35
|
+
export type Messages = typeof en;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// ask + changes + hints
|
|
2
|
+
|
|
3
|
+
export const aiChatEn = {
|
|
4
|
+
ask: {
|
|
5
|
+
title: 'MindOS Agent',
|
|
6
|
+
fabLabel: 'Ask AI',
|
|
7
|
+
placeholder: 'Ask a question... @ files, / skills',
|
|
8
|
+
emptyPrompt: 'Ask anything about your knowledge base',
|
|
9
|
+
send: 'send',
|
|
10
|
+
newlineHint: 'new line',
|
|
11
|
+
panelComposerResize: 'Drag up to enlarge the input area',
|
|
12
|
+
panelComposerFooter: 'Resize height',
|
|
13
|
+
panelComposerResetHint: 'Double-click to reset height',
|
|
14
|
+
panelComposerKeyboard: 'Arrow keys adjust height; Home/End min/max',
|
|
15
|
+
attachFile: 'Context',
|
|
16
|
+
uploadedFiles: 'Uploaded',
|
|
17
|
+
skillsHint: 'skills',
|
|
18
|
+
attachCurrent: 'attach current file',
|
|
19
|
+
stopTitle: 'Stop',
|
|
20
|
+
cancelReconnect: 'Cancel reconnect',
|
|
21
|
+
connecting: 'Thinking with you...',
|
|
22
|
+
thinking: 'Thinking...',
|
|
23
|
+
thinkingLabel: 'Thinking',
|
|
24
|
+
searching: 'Searching knowledge base...',
|
|
25
|
+
generating: 'Generating response...',
|
|
26
|
+
stopped: 'Generation stopped.',
|
|
27
|
+
errorNoResponse: 'AI returned no content. Possible causes: model does not support streaming, proxy compatibility issue, or request exceeds context limit.',
|
|
28
|
+
reconnecting: (attempt: number, max: number) => `Connection lost. Reconnecting (${attempt}/${max})...`,
|
|
29
|
+
reconnectFailed: 'Connection failed after multiple attempts.',
|
|
30
|
+
retry: 'Retry',
|
|
31
|
+
suggestions: [
|
|
32
|
+
'Summarize this document',
|
|
33
|
+
'List all action items and TODOs',
|
|
34
|
+
'What are the key points?',
|
|
35
|
+
'Find related notes on this topic',
|
|
36
|
+
],
|
|
37
|
+
sessionHistory: 'Session History',
|
|
38
|
+
clearAll: 'Clear all',
|
|
39
|
+
confirmClear: 'Confirm clear?',
|
|
40
|
+
noSessions: 'No saved sessions.',
|
|
41
|
+
draftingHint: 'AI is still running — you can draft the next step now.',
|
|
42
|
+
},
|
|
43
|
+
changes: {
|
|
44
|
+
unreadBanner: (n: number) => `${n} content change${n === 1 ? '' : 's'} unread`,
|
|
45
|
+
reviewNow: 'Review now',
|
|
46
|
+
dismiss: 'Dismiss notification',
|
|
47
|
+
title: 'Content changes',
|
|
48
|
+
subtitle: 'Review recent edits across user and agent operations.',
|
|
49
|
+
eventsCount: (n: number) => `${n} event${n === 1 ? '' : 's'}`,
|
|
50
|
+
unreadCount: (n: number) => `${n} unread`,
|
|
51
|
+
refresh: 'Refresh',
|
|
52
|
+
markSeen: 'Mark seen',
|
|
53
|
+
markAllRead: 'Mark all read',
|
|
54
|
+
filters: {
|
|
55
|
+
filePath: 'File path',
|
|
56
|
+
filePathPlaceholder: 'e.g. Projects/plan.md',
|
|
57
|
+
source: 'Agents (source)',
|
|
58
|
+
operation: 'Tools (operation)',
|
|
59
|
+
operationAll: 'All operations',
|
|
60
|
+
keyword: 'Keyword',
|
|
61
|
+
keywordPlaceholder: 'summary / op / path',
|
|
62
|
+
all: 'All',
|
|
63
|
+
agent: 'Agent',
|
|
64
|
+
user: 'User',
|
|
65
|
+
system: 'System',
|
|
66
|
+
},
|
|
67
|
+
loading: 'Loading changes...',
|
|
68
|
+
empty: 'No content changes yet.',
|
|
69
|
+
open: 'Open',
|
|
70
|
+
unchangedLines: (n: number) => `... ${n} unchanged lines ...`,
|
|
71
|
+
relativeTime: {
|
|
72
|
+
justNow: 'just now',
|
|
73
|
+
minutesAgo: (n: number) => `${n}m ago`,
|
|
74
|
+
hoursAgo: (n: number) => `${n}h ago`,
|
|
75
|
+
daysAgo: (n: number) => `${n}d ago`,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
/** Disabled-state and contextual tooltip hints */
|
|
79
|
+
hints: {
|
|
80
|
+
noValidFiles: 'No valid files selected',
|
|
81
|
+
aiOrganizing: 'AI is organizing',
|
|
82
|
+
importInProgress: 'Import in progress',
|
|
83
|
+
templateInitializing: 'Another template is being initialized',
|
|
84
|
+
configureAiKey: 'Configure API key in Settings → AI',
|
|
85
|
+
syncInProgress: 'Sync already in progress',
|
|
86
|
+
toggleInProgress: 'Toggle operation in progress',
|
|
87
|
+
typeMessage: 'Type a message',
|
|
88
|
+
mentionInProgress: 'Mention or command in progress',
|
|
89
|
+
cleanupInProgress: 'Cleanup already in progress',
|
|
90
|
+
tokenResetInProgress: 'Token reset in progress',
|
|
91
|
+
aiNotConfigured: 'AI not configured or generation in progress',
|
|
92
|
+
generationInProgress: 'Generation in progress or AI not configured',
|
|
93
|
+
cannotJumpForward: 'Cannot jump forward in setup',
|
|
94
|
+
testInProgressOrNoKey: 'Test in progress or no API key',
|
|
95
|
+
workflowStepRunning: 'Workflow step already running',
|
|
96
|
+
workflowRunning: 'Workflow step is running',
|
|
97
|
+
sessionHistory: 'Session history',
|
|
98
|
+
newSession: 'New session',
|
|
99
|
+
attachFile: 'Attach local file',
|
|
100
|
+
maximizePanel: 'Maximize panel',
|
|
101
|
+
restorePanel: 'Restore panel',
|
|
102
|
+
dockToSide: 'Dock to side panel',
|
|
103
|
+
openAsPopup: 'Open as popup',
|
|
104
|
+
closePanel: 'Close',
|
|
105
|
+
newChat: 'New chat',
|
|
106
|
+
closeSession: 'Close session',
|
|
107
|
+
},
|
|
108
|
+
} as const;
|
|
109
|
+
|
|
110
|
+
export const aiChatZh = {
|
|
111
|
+
ask: {
|
|
112
|
+
title: 'MindOS Agent',
|
|
113
|
+
fabLabel: 'AI 助手',
|
|
114
|
+
placeholder: '输入问题… @ 附加文件,/ 技能',
|
|
115
|
+
emptyPrompt: '可以问任何关于知识库的问题',
|
|
116
|
+
send: '发送',
|
|
117
|
+
newlineHint: '换行',
|
|
118
|
+
panelComposerResize: '向上拖拽以拉高输入区',
|
|
119
|
+
panelComposerFooter: '拉高输入区',
|
|
120
|
+
panelComposerResetHint: '双击恢复默认高度',
|
|
121
|
+
panelComposerKeyboard: '方向键调节高度;Home/End 最小或最大',
|
|
122
|
+
attachFile: '上下文',
|
|
123
|
+
uploadedFiles: '已上传',
|
|
124
|
+
skillsHint: '技能',
|
|
125
|
+
attachCurrent: '附加当前文件',
|
|
126
|
+
stopTitle: '停止',
|
|
127
|
+
cancelReconnect: '取消重连',
|
|
128
|
+
connecting: '正在和你一起思考...',
|
|
129
|
+
thinking: '思考中...',
|
|
130
|
+
thinkingLabel: '思考中',
|
|
131
|
+
searching: '正在搜索知识库...',
|
|
132
|
+
generating: '正在生成回复...',
|
|
133
|
+
stopped: '已停止生成。',
|
|
134
|
+
errorNoResponse: 'AI 未返回有效内容。可能原因:模型不支持流式输出、中转站兼容性问题、或请求超出上下文限制。',
|
|
135
|
+
reconnecting: (attempt: number, max: number) => `连接中断,正在重连 (${attempt}/${max})...`,
|
|
136
|
+
reconnectFailed: '多次重连失败,请检查网络后重试。',
|
|
137
|
+
retry: '重试',
|
|
138
|
+
suggestions: [
|
|
139
|
+
'总结这篇文档',
|
|
140
|
+
'列出所有待办事项',
|
|
141
|
+
'这篇文档的核心要点是什么?',
|
|
142
|
+
'查找与这个主题相关的笔记',
|
|
143
|
+
],
|
|
144
|
+
sessionHistory: '对话历史',
|
|
145
|
+
clearAll: '清除全部',
|
|
146
|
+
confirmClear: '确认清除?',
|
|
147
|
+
noSessions: '暂无历史对话。',
|
|
148
|
+
draftingHint: 'AI 仍在执行,你可以先输入下一步。',
|
|
149
|
+
},
|
|
150
|
+
changes: {
|
|
151
|
+
unreadBanner: (n: number) => `${n} 条内容变更未读`,
|
|
152
|
+
reviewNow: '立即查看',
|
|
153
|
+
dismiss: '关闭提醒',
|
|
154
|
+
title: '内容变更',
|
|
155
|
+
subtitle: '集中查看用户与 Agent 的最近编辑记录。',
|
|
156
|
+
eventsCount: (n: number) => `${n} 条事件`,
|
|
157
|
+
unreadCount: (n: number) => `${n} 条未读`,
|
|
158
|
+
refresh: '刷新',
|
|
159
|
+
markSeen: '标记已读',
|
|
160
|
+
markAllRead: '全部已读',
|
|
161
|
+
filters: {
|
|
162
|
+
filePath: '文件路径',
|
|
163
|
+
filePathPlaceholder: '例如:Projects/plan.md',
|
|
164
|
+
source: 'Agents(来源)',
|
|
165
|
+
operation: 'Tools(操作)',
|
|
166
|
+
operationAll: '全部操作',
|
|
167
|
+
keyword: '关键词',
|
|
168
|
+
keywordPlaceholder: '摘要 / 操作 / 路径',
|
|
169
|
+
all: '全部',
|
|
170
|
+
agent: 'Agent',
|
|
171
|
+
user: '用户',
|
|
172
|
+
system: '系统',
|
|
173
|
+
},
|
|
174
|
+
loading: '正在加载变更...',
|
|
175
|
+
empty: '暂无内容变更。',
|
|
176
|
+
open: '打开',
|
|
177
|
+
unchangedLines: (n: number) => `... ${n} 行未变更 ...`,
|
|
178
|
+
relativeTime: {
|
|
179
|
+
justNow: '刚刚',
|
|
180
|
+
minutesAgo: (n: number) => `${n} 分钟前`,
|
|
181
|
+
hoursAgo: (n: number) => `${n} 小时前`,
|
|
182
|
+
daysAgo: (n: number) => `${n} 天前`,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
/** 禁用态和上下文提示文案 */
|
|
186
|
+
hints: {
|
|
187
|
+
noValidFiles: '未选择有效文件',
|
|
188
|
+
aiOrganizing: 'AI 正在整理中',
|
|
189
|
+
importInProgress: '正在导入',
|
|
190
|
+
templateInitializing: '正在初始化另一个模板',
|
|
191
|
+
configureAiKey: '请在 设置 → AI 中配置 API 密钥',
|
|
192
|
+
syncInProgress: '正在同步中',
|
|
193
|
+
toggleInProgress: '正在切换中',
|
|
194
|
+
typeMessage: '请输入消息',
|
|
195
|
+
mentionInProgress: '正在输入提及或命令',
|
|
196
|
+
cleanupInProgress: '正在清理中',
|
|
197
|
+
tokenResetInProgress: '正在重置令牌',
|
|
198
|
+
aiNotConfigured: 'AI 未配置或正在生成',
|
|
199
|
+
generationInProgress: '正在生成或 AI 未配置',
|
|
200
|
+
cannotJumpForward: '无法跳过前序步骤',
|
|
201
|
+
testInProgressOrNoKey: '正在测试或未配置 API 密钥',
|
|
202
|
+
workflowStepRunning: '工作流步骤正在运行',
|
|
203
|
+
workflowRunning: '工作流步骤正在运行',
|
|
204
|
+
sessionHistory: '会话历史',
|
|
205
|
+
newSession: '新会话',
|
|
206
|
+
attachFile: '附加本地文件',
|
|
207
|
+
maximizePanel: '最大化面板',
|
|
208
|
+
restorePanel: '还原面板',
|
|
209
|
+
dockToSide: '停靠到侧边栏',
|
|
210
|
+
openAsPopup: '弹窗模式',
|
|
211
|
+
closePanel: '关闭',
|
|
212
|
+
newChat: '新对话',
|
|
213
|
+
closeSession: '关闭会话',
|
|
214
|
+
},
|
|
215
|
+
};
|