@geminilight/mindos 0.6.25 → 0.6.28

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.
Files changed (45) hide show
  1. package/README.md +19 -3
  2. package/README_zh.md +19 -3
  3. package/app/app/api/a2a/discover/route.ts +23 -0
  4. package/app/components/CreateSpaceModal.tsx +1 -0
  5. package/app/components/ImportModal.tsx +3 -0
  6. package/app/components/OnboardingView.tsx +1 -0
  7. package/app/components/RightAskPanel.tsx +4 -2
  8. package/app/components/SidebarLayout.tsx +11 -2
  9. package/app/components/agents/AgentDetailContent.tsx +48 -1
  10. package/app/components/agents/AgentsContentPage.tsx +3 -0
  11. package/app/components/agents/AgentsOverviewSection.tsx +11 -0
  12. package/app/components/agents/DiscoverAgentModal.tsx +149 -0
  13. package/app/components/ask/AskContent.tsx +29 -9
  14. package/app/components/ask/SessionTabBar.tsx +70 -0
  15. package/app/components/echo/EchoInsightCollapsible.tsx +4 -0
  16. package/app/components/help/HelpContent.tsx +65 -9
  17. package/app/components/panels/AgentsPanel.tsx +25 -2
  18. package/app/components/panels/AgentsPanelAgentListRow.tsx +10 -1
  19. package/app/components/renderers/workflow/WorkflowRenderer.tsx +5 -0
  20. package/app/components/settings/AiTab.tsx +1 -0
  21. package/app/components/settings/KnowledgeTab.tsx +2 -0
  22. package/app/components/settings/SyncTab.tsx +2 -0
  23. package/app/components/setup/StepDots.tsx +5 -1
  24. package/app/data/skills/mindos/SKILL.md +186 -0
  25. package/app/data/skills/mindos-zh/SKILL.md +185 -0
  26. package/app/hooks/useA2aRegistry.ts +53 -0
  27. package/app/hooks/useAskSession.ts +44 -25
  28. package/app/lib/a2a/a2a-tools.ts +212 -0
  29. package/app/lib/a2a/client.ts +207 -0
  30. package/app/lib/a2a/index.ts +8 -0
  31. package/app/lib/a2a/orchestrator.ts +255 -0
  32. package/app/lib/a2a/types.ts +54 -0
  33. package/app/lib/agent/tools.ts +6 -4
  34. package/app/lib/i18n-en.ts +52 -0
  35. package/app/lib/i18n-zh.ts +52 -0
  36. package/app/next-env.d.ts +1 -1
  37. package/bin/cli.js +180 -171
  38. package/bin/commands/agent.js +110 -18
  39. package/bin/commands/api.js +5 -3
  40. package/bin/commands/ask.js +3 -3
  41. package/bin/commands/file.js +13 -13
  42. package/bin/commands/search.js +2 -2
  43. package/bin/commands/space.js +64 -10
  44. package/bin/lib/command.js +10 -0
  45. package/package.json +1 -1
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import { Plus, X } from 'lucide-react';
4
+ import type { ChatSession } from '@/lib/types';
5
+ import { sessionTitle } from '@/hooks/useAskSession';
6
+ import { useLocale } from '@/lib/LocaleContext';
7
+
8
+ interface SessionTabBarProps {
9
+ sessions: ChatSession[];
10
+ activeSessionId: string | null;
11
+ onLoad: (id: string) => void;
12
+ onDelete: (id: string) => void;
13
+ onNew: () => void;
14
+ maxTabs?: number;
15
+ }
16
+
17
+ export default function SessionTabBar({
18
+ sessions, activeSessionId, onLoad, onDelete, onNew, maxTabs = 3,
19
+ }: SessionTabBarProps) {
20
+ const { t } = useLocale();
21
+ const visibleSessions = sessions.slice(0, maxTabs);
22
+
23
+ if (visibleSessions.length === 0) return null;
24
+
25
+ return (
26
+ <div className="flex items-center border-b border-border shrink-0 bg-background/50">
27
+ <div className="flex flex-1 min-w-0">
28
+ {visibleSessions.map((s) => {
29
+ const isActive = s.id === activeSessionId;
30
+ const title = sessionTitle(s);
31
+ return (
32
+ <button
33
+ key={s.id}
34
+ type="button"
35
+ onClick={() => onLoad(s.id)}
36
+ className={`group relative flex items-center gap-1 min-w-0 max-w-[160px] px-3 py-2 text-xs transition-colors
37
+ ${isActive
38
+ ? 'text-foreground border-b-2 border-[var(--amber)] bg-card'
39
+ : 'text-muted-foreground hover:text-foreground hover:bg-muted/50 border-b-2 border-transparent'
40
+ }`}
41
+ title={title}
42
+ >
43
+ <span className="truncate">{title === '(empty session)' ? t.hints.newChat : title}</span>
44
+ {visibleSessions.length > 1 && (
45
+ <span
46
+ role="button"
47
+ tabIndex={0}
48
+ onClick={(e) => { e.stopPropagation(); onDelete(s.id); }}
49
+ onKeyDown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); onDelete(s.id); } }}
50
+ className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-muted hover:text-error transition-opacity"
51
+ title={t.hints.closeSession}
52
+ >
53
+ <X size={10} />
54
+ </span>
55
+ )}
56
+ </button>
57
+ );
58
+ })}
59
+ </div>
60
+ <button
61
+ type="button"
62
+ onClick={onNew}
63
+ className="shrink-0 p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
64
+ title={t.hints.newChat}
65
+ >
66
+ <Plus size={13} />
67
+ </button>
68
+ </div>
69
+ );
70
+ }
@@ -7,6 +7,7 @@ import remarkGfm from 'remark-gfm';
7
7
  import { cn } from '@/lib/utils';
8
8
  import { consumeUIMessageStream } from '@/lib/agent/stream-consumer';
9
9
  import { useSettingsAiAvailable } from '@/hooks/useSettingsAiAvailable';
10
+ import { useLocale } from '@/lib/LocaleContext';
10
11
 
11
12
  const proseInsight =
12
13
  'prose prose-sm prose-panel dark:prose-invert max-w-none text-foreground ' +
@@ -50,6 +51,7 @@ export function EchoInsightCollapsible({
50
51
  const btnId = `${panelId}-btn`;
51
52
  const abortRef = useRef<AbortController | null>(null);
52
53
  const { ready: aiReady, loading: aiLoading } = useSettingsAiAvailable();
54
+ const { t } = useLocale();
53
55
 
54
56
  useEffect(() => () => abortRef.current?.abort(), []);
55
57
 
@@ -145,6 +147,7 @@ export function EchoInsightCollapsible({
145
147
  <button
146
148
  type="button"
147
149
  disabled={generateDisabled}
150
+ title={generateDisabled ? t.hints.aiNotConfigured : undefined}
148
151
  onClick={runGenerate}
149
152
  className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-[var(--amber-foreground)] transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
150
153
  >
@@ -160,6 +163,7 @@ export function EchoInsightCollapsible({
160
163
  type="button"
161
164
  onClick={runGenerate}
162
165
  disabled={streaming || !aiReady}
166
+ title={streaming || !aiReady ? t.hints.generationInProgress : undefined}
163
167
  className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
164
168
  >
165
169
  {retryLabel}
@@ -5,7 +5,8 @@ import { BookOpen, Rocket, Brain, Keyboard, HelpCircle, Bot, ChevronDown, Copy,
5
5
  import { useLocale } from '@/lib/LocaleContext';
6
6
 
7
7
  /* ── Collapsible Section ── */
8
- function Section({ icon, title, defaultOpen = false, children }: {
8
+ function Section({ id, icon, title, defaultOpen = false, children }: {
9
+ id?: string;
9
10
  icon: React.ReactNode;
10
11
  title: string;
11
12
  defaultOpen?: boolean;
@@ -14,7 +15,7 @@ function Section({ icon, title, defaultOpen = false, children }: {
14
15
  const [open, setOpen] = useState(defaultOpen);
15
16
 
16
17
  return (
17
- <div className="bg-card border border-border rounded-lg overflow-hidden">
18
+ <div id={id} className="bg-card border border-border rounded-lg overflow-hidden scroll-mt-4">
18
19
  <button
19
20
  onClick={() => setOpen(v => !v)}
20
21
  className="w-full flex items-center gap-3 px-5 py-4 text-left hover:bg-muted/50 transition-colors focus-visible:ring-2 focus-visible:ring-ring"
@@ -144,8 +145,63 @@ export default function HelpContent() {
144
145
  { keys: '@', label: h.shortcuts.attachFile },
145
146
  ], [mod, h.shortcuts]);
146
147
 
148
+ const tocItems = useMemo(() => [
149
+ { id: 'what-is', label: h.whatIs.title },
150
+ { id: 'concepts', label: h.concepts.title },
151
+ { id: 'quick-start', label: h.quickStart.title },
152
+ { id: 'agent-usage', label: h.agentUsage.title },
153
+ { id: 'shortcuts', label: h.shortcutsTitle },
154
+ { id: 'faq', label: h.faq.title },
155
+ ], [h]);
156
+
157
+ const [activeSection, setActiveSection] = useState('');
158
+ useEffect(() => {
159
+ const ids = tocItems.map(i => i.id);
160
+ const observer = new IntersectionObserver(
161
+ (entries) => {
162
+ for (const entry of entries) {
163
+ if (entry.isIntersecting) {
164
+ setActiveSection(entry.target.id);
165
+ break;
166
+ }
167
+ }
168
+ },
169
+ { rootMargin: '-10% 0px -80% 0px', threshold: 0 },
170
+ );
171
+ const timer = setTimeout(() => {
172
+ for (const id of ids) {
173
+ const el = document.getElementById(id);
174
+ if (el) observer.observe(el);
175
+ }
176
+ }, 100);
177
+ return () => { clearTimeout(timer); observer.disconnect(); };
178
+ }, [tocItems]);
179
+
147
180
  return (
148
- <div className="content-width px-4 md:px-6 py-8 md:py-12">
181
+ <div className="content-width px-4 md:px-6 py-8 md:py-12 relative">
182
+ {/* ── Floating TOC (wide screens only) ── */}
183
+ <nav className="hidden xl:block fixed top-24 w-44" style={{ left: 'calc(50% + 340px)' }} aria-label="Table of contents">
184
+ <ul className="space-y-1 border-l border-border pl-3">
185
+ {tocItems.map(item => (
186
+ <li key={item.id}>
187
+ <a
188
+ href={`#${item.id}`}
189
+ onClick={(e) => {
190
+ e.preventDefault();
191
+ document.getElementById(item.id)?.scrollIntoView({ behavior: 'smooth' });
192
+ }}
193
+ className={`block text-xs py-1 transition-colors ${
194
+ activeSection === item.id
195
+ ? 'text-[var(--amber)] font-medium border-l-2 border-[var(--amber)] -ml-[13px] pl-[11px]'
196
+ : 'text-muted-foreground hover:text-foreground'
197
+ }`}
198
+ >
199
+ {item.label}
200
+ </a>
201
+ </li>
202
+ ))}
203
+ </ul>
204
+ </nav>
149
205
  {/* ── Header ── */}
150
206
  <div className="mb-8">
151
207
  <div className="flex items-center gap-2 mb-1">
@@ -158,12 +214,12 @@ export default function HelpContent() {
158
214
  {/* ── Sections ── */}
159
215
  <div className="space-y-3">
160
216
  {/* 1. What is MindOS */}
161
- <Section icon={<BookOpen size={18} />} title={h.whatIs.title} defaultOpen>
217
+ <Section id="what-is" icon={<BookOpen size={18} />} title={h.whatIs.title} defaultOpen>
162
218
  <p className="text-sm text-muted-foreground leading-relaxed">{h.whatIs.body}</p>
163
219
  </Section>
164
220
 
165
221
  {/* 2. Core Concepts */}
166
- <Section icon={<Brain size={18} />} title={h.concepts.title} defaultOpen>
222
+ <Section id="concepts" icon={<Brain size={18} />} title={h.concepts.title} defaultOpen>
167
223
  <div className="space-y-4">
168
224
  <div>
169
225
  <p className="text-sm font-medium text-foreground">{h.concepts.spaceTitle}</p>
@@ -181,7 +237,7 @@ export default function HelpContent() {
181
237
  </Section>
182
238
 
183
239
  {/* 3. Quick Start */}
184
- <Section icon={<Rocket size={18} />} title={h.quickStart.title} defaultOpen>
240
+ <Section id="quick-start" icon={<Rocket size={18} />} title={h.quickStart.title} defaultOpen>
185
241
  <div className="space-y-4">
186
242
  <StepCard step={1} title={h.quickStart.step1Title} desc={h.quickStart.step1Desc} />
187
243
  <StepCard step={2} title={h.quickStart.step2Title} desc={h.quickStart.step2Desc} />
@@ -190,7 +246,7 @@ export default function HelpContent() {
190
246
  </Section>
191
247
 
192
248
  {/* 4. Using MindOS with AI Agents */}
193
- <Section icon={<Bot size={18} />} title={h.agentUsage.title} defaultOpen>
249
+ <Section id="agent-usage" icon={<Bot size={18} />} title={h.agentUsage.title} defaultOpen>
194
250
  <p className="text-sm text-muted-foreground leading-relaxed mb-4">{h.agentUsage.intro}</p>
195
251
 
196
252
  <div className="space-y-3">
@@ -215,7 +271,7 @@ export default function HelpContent() {
215
271
  </Section>
216
272
 
217
273
  {/* 5. Keyboard Shortcuts */}
218
- <Section icon={<Keyboard size={18} />} title={h.shortcutsTitle}>
274
+ <Section id="shortcuts" icon={<Keyboard size={18} />} title={h.shortcutsTitle}>
219
275
  <div className="space-y-0">
220
276
  {shortcuts.map((s) => (
221
277
  <ShortcutRow key={s.keys} keys={s.keys} label={s.label} />
@@ -224,7 +280,7 @@ export default function HelpContent() {
224
280
  </Section>
225
281
 
226
282
  {/* 6. FAQ */}
227
- <Section icon={<HelpCircle size={18} />} title={h.faq.title}>
283
+ <Section id="faq" icon={<HelpCircle size={18} />} title={h.faq.title}>
228
284
  <div>
229
285
  {h.faq.items.map((item, i) => (
230
286
  <FaqItem key={i} q={item.q} a={item.a} />
@@ -2,12 +2,14 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import { usePathname } from 'next/navigation';
5
- import { Loader2, RefreshCw, Settings } from 'lucide-react';
5
+ import { Globe, Loader2, RefreshCw, Settings } from 'lucide-react';
6
6
  import { useMcpData } from '@/hooks/useMcpData';
7
+ import { useA2aRegistry } from '@/hooks/useA2aRegistry';
7
8
  import { useLocale } from '@/lib/LocaleContext';
8
9
  import PanelHeader from './PanelHeader';
9
10
  import { AgentsPanelHubNav } from './AgentsPanelHubNav';
10
11
  import { AgentsPanelAgentGroups } from './AgentsPanelAgentGroups';
12
+ import DiscoverAgentModal from '../agents/DiscoverAgentModal';
11
13
 
12
14
  interface AgentsPanelProps {
13
15
  active: boolean;
@@ -28,6 +30,8 @@ export default function AgentsPanel({
28
30
  const pathname = usePathname();
29
31
  const [refreshing, setRefreshing] = useState(false);
30
32
  const [showNotDetected, setShowNotDetected] = useState(false);
33
+ const [showDiscoverModal, setShowDiscoverModal] = useState(false);
34
+ const a2a = useA2aRegistry();
31
35
 
32
36
  const handleRefresh = async () => {
33
37
  setRefreshing(true);
@@ -82,6 +86,9 @@ export default function AgentsPanel({
82
86
  {!mcp.loading && (
83
87
  <span className="text-2xs text-muted-foreground">
84
88
  {connected.length} {p.connected}
89
+ {a2a.agents.length > 0 && (
90
+ <> &middot; {p.a2aLabel} {a2a.agents.length}</>
91
+ )}
85
92
  </span>
86
93
  )}
87
94
  <button
@@ -146,7 +153,15 @@ export default function AgentsPanel({
146
153
  )}
147
154
  </div>
148
155
 
149
- <div className="px-3 py-2 border-t border-border shrink-0">
156
+ <div className="px-3 py-2 border-t border-border shrink-0 space-y-1">
157
+ <button
158
+ type="button"
159
+ onClick={() => setShowDiscoverModal(true)}
160
+ className="flex items-center gap-1.5 text-2xs text-muted-foreground hover:text-foreground transition-colors w-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
161
+ >
162
+ <Globe size={11} />
163
+ {p.a2aDiscover}
164
+ </button>
150
165
  <button
151
166
  type="button"
152
167
  onClick={openAdvancedConfig}
@@ -156,6 +171,14 @@ export default function AgentsPanel({
156
171
  {p.advancedConfig}
157
172
  </button>
158
173
  </div>
174
+
175
+ <DiscoverAgentModal
176
+ open={showDiscoverModal}
177
+ onClose={() => setShowDiscoverModal(false)}
178
+ onDiscover={a2a.discover}
179
+ discovering={a2a.discovering}
180
+ error={a2a.error}
181
+ />
159
182
  </div>
160
183
  );
161
184
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import Link from 'next/link';
5
- import { Check, ChevronRight, Loader2, RotateCw } from 'lucide-react';
5
+ import { Check, ChevronRight, Globe, Loader2, RotateCw } from 'lucide-react';
6
6
  import type { AgentInfo } from '../settings/types';
7
7
  import { AgentAvatar } from '../agents/AgentsPrimitives';
8
8
 
@@ -23,6 +23,8 @@ export default function AgentsPanelAgentListRow({
23
23
  detailHref,
24
24
  onInstallAgent,
25
25
  copy,
26
+ isA2aReady = false,
27
+ a2aTooltip,
26
28
  }: {
27
29
  agent: AgentInfo;
28
30
  agentStatus: AgentsPanelAgentListStatus;
@@ -30,6 +32,8 @@ export default function AgentsPanelAgentListRow({
30
32
  detailHref: string;
31
33
  onInstallAgent: (key: string) => Promise<boolean>;
32
34
  copy: AgentsPanelAgentListRowCopy;
35
+ isA2aReady?: boolean;
36
+ a2aTooltip?: string;
33
37
  }) {
34
38
  return (
35
39
  <div
@@ -51,6 +55,11 @@ export default function AgentsPanelAgentListRow({
51
55
  {agent.transport}
52
56
  </span>
53
57
  )}
58
+ {isA2aReady && (
59
+ <span title={a2aTooltip} className="shrink-0 text-muted-foreground/70">
60
+ <Globe size={12} />
61
+ </span>
62
+ )}
54
63
  <span className="flex-1 min-w-[4px]" />
55
64
  <ChevronRight
56
65
  size={15}
@@ -3,6 +3,7 @@
3
3
  import { useMemo, useState, useRef, useCallback } from 'react';
4
4
  import { Play, SkipForward, RotateCcw, CheckCircle2, Circle, Loader2, AlertCircle, ChevronDown, Sparkles } from 'lucide-react';
5
5
  import type { RendererContext } from '@/lib/renderers/registry';
6
+ import { useLocale } from '@/lib/LocaleContext';
6
7
 
7
8
  // ─── Types ────────────────────────────────────────────────────────────────────
8
9
 
@@ -171,6 +172,7 @@ function StepCard({
171
172
  onSkip: () => void;
172
173
  canRun: boolean;
173
174
  }) {
175
+ const { t } = useLocale();
174
176
  const [expanded, setExpanded] = useState(false);
175
177
  const hasBody = step.body.trim().length > 0;
176
178
  const hasOutput = step.output.length > 0;
@@ -201,6 +203,7 @@ function StepCard({
201
203
  <button
202
204
  onClick={onRun}
203
205
  disabled={!canRun}
206
+ title={!canRun ? t.hints.workflowStepRunning : undefined}
204
207
  style={{
205
208
  display: 'flex', alignItems: 'center', gap: 4,
206
209
  padding: '3px 10px', borderRadius: 6, fontSize: '0.72rem',
@@ -268,6 +271,7 @@ function StepCard({
268
271
  // ─── Main renderer ────────────────────────────────────────────────────────────
269
272
 
270
273
  export function WorkflowRenderer({ filePath, content }: RendererContext) {
274
+ const { t } = useLocale();
271
275
  const parsed = useMemo(() => parseWorkflow(content), [content]);
272
276
  const [steps, setSteps] = useState<WorkflowStep[]>(() => parsed.steps);
273
277
  const [running, setRunning] = useState(false);
@@ -357,6 +361,7 @@ export function WorkflowRenderer({ filePath, content }: RendererContext) {
357
361
  <button
358
362
  onClick={() => runStep(nextPendingIdx)}
359
363
  disabled={running}
364
+ title={running ? t.hints.workflowRunning : undefined}
360
365
  style={{
361
366
  display: 'flex', alignItems: 'center', gap: 5,
362
367
  padding: '4px 12px', borderRadius: 7, fontSize: '0.75rem',
@@ -125,6 +125,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
125
125
  <button
126
126
  type="button"
127
127
  disabled={disabled}
128
+ title={disabled ? t.hints.testInProgressOrNoKey : undefined}
128
129
  onClick={() => handleTestKey(providerName)}
129
130
  className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
130
131
  >
@@ -173,6 +173,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
173
173
  <button
174
174
  onClick={() => setShowCleanupConfirm(true)}
175
175
  disabled={cleaningUp}
176
+ title={cleaningUp ? t.hints.cleanupInProgress : undefined}
176
177
  className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-50"
177
178
  >
178
179
  {cleaningUp ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
@@ -249,6 +250,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
249
250
  type="button"
250
251
  onClick={handleResetToken}
251
252
  disabled={resetting}
253
+ title={resetting ? t.hints.tokenResetInProgress : undefined}
252
254
  className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
253
255
  >
254
256
  <RefreshCw size={12} className={resetting ? 'animate-spin' : ''} />
@@ -302,6 +302,7 @@ export function SyncTab({ t }: SyncTabProps) {
302
302
  type="button"
303
303
  onClick={handleSyncNow}
304
304
  disabled={syncing}
305
+ title={syncing ? t.hints.syncInProgress : undefined}
305
306
  className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
306
307
  >
307
308
  <RefreshCw size={12} className={syncing ? 'animate-spin' : ''} />
@@ -311,6 +312,7 @@ export function SyncTab({ t }: SyncTabProps) {
311
312
  type="button"
312
313
  onClick={handleToggle}
313
314
  disabled={toggling}
315
+ title={toggling ? t.hints.toggleInProgress : undefined}
314
316
  className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${
315
317
  status.enabled
316
318
  ? 'border-border text-muted-foreground hover:text-destructive hover:border-destructive/50'
@@ -1,5 +1,7 @@
1
1
  'use client';
2
2
 
3
+ import { useLocale } from '@/lib/LocaleContext';
4
+
3
5
  export interface StepDotsProps {
4
6
  step: number;
5
7
  setStep: (s: number) => void;
@@ -8,6 +10,7 @@ export interface StepDotsProps {
8
10
  }
9
11
 
10
12
  export default function StepDots({ step, setStep, stepTitles, disabled }: StepDotsProps) {
13
+ const { t } = useLocale();
11
14
  return (
12
15
  <div className="flex items-center gap-2 mb-8" role="navigation" aria-label="Setup steps">
13
16
  {stepTitles.map((title: string, i: number) => (
@@ -17,7 +20,8 @@ export default function StepDots({ step, setStep, stepTitles, disabled }: StepDo
17
20
  aria-current={i === step ? 'step' : undefined}
18
21
  aria-label={title}
19
22
  className="flex flex-col items-center gap-1 p-1 -m-1 disabled:cursor-not-allowed disabled:opacity-60"
20
- disabled={disabled || i >= step}>
23
+ disabled={disabled || i >= step}
24
+ title={(disabled || i >= step) ? t.hints.cannotJumpForward : undefined}>
21
25
  <div
22
26
  className="w-6 h-6 rounded-full text-xs font-medium flex items-center justify-center transition-colors"
23
27
  style={{
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: mindos
3
+ description: >
4
+ Operate a MindOS knowledge base: update notes, search, organize files, execute SOPs/workflows,
5
+ retrospective, append CSV, cross-agent handoff, route unstructured input to the right files,
6
+ distill experience, sync related docs. 更新笔记, 搜索知识库, 整理, 执行SOP, 复盘, 追加CSV, 交接, 路由到文件, 提炼经验.
7
+ Use when the task targets files inside the user's MindOS KB (mindRoot).
8
+ NOT for editing app source, project docs, or paths outside the KB.
9
+ Core concepts: Space, Instruction (INSTRUCTION.md), Skill (SKILL.md); notes can embody both.
10
+ ---
11
+
12
+ # MindOS Skill
13
+
14
+ <!-- version: 1.3.0 -->
15
+
16
+ **Before every task, internalize these 5 rules:**
17
+
18
+ 1. The **bootstrap directory tree is the primary index** — reason from names and hierarchy before calling search. Most questions can be answered by reading what's already in context.
19
+ 2. **Default to read-only.** Only invoke write tools when the user explicitly asks to save, record, organize, or edit. Lookup / summarize / quote = no writes.
20
+ 3. **Rule precedence** (highest wins): user's current-turn instruction → `user-skill-rules.md` → nearest directory `INSTRUCTION.md` → root `INSTRUCTION.md` → this SKILL's defaults.
21
+ 4. **Multi-file edits require a plan first.** Present the full change list; execute only after approval.
22
+ 5. After create/delete/move/rename → **sync affected READMEs** automatically.
23
+
24
+ ---
25
+
26
+ ## NEVER do (hard-won pitfalls)
27
+
28
+ - **NEVER write to the KB root** unless the user explicitly says so. Root is for governance files only (`README.md`, `INSTRUCTION.md`, `CONFIG`). New content goes under the most semantically fitting subdirectory.
29
+ - **NEVER assume directory names.** Don't hardcode `Workflows/`, `Projects/`, `Contacts/` — always infer from the bootstrap tree you actually received. The user's KB may use Chinese names, flat layout, or unconventional hierarchy.
30
+ - **NEVER use `mindos_write_file` for a small edit.** Use `update_section`, `update_lines`, or `insert_after_heading` — full-file rewrites destroy git diffs and make changes unauditable.
31
+ - **NEVER search with a single keyword.** Always fire 2-4 parallel searches (synonyms, abbreviations, Chinese/English variants). One keyword misses too much.
32
+ - **NEVER modify `INSTRUCTION.md` or `README.md` without confirmation.** These are governance docs — treat as high-sensitivity even for trivial-looking typo fixes.
33
+ - **NEVER create a file without checking siblings first.** Read at least 1-2 files in the target directory to learn local naming, heading style, and CSV schema. Inventing a new convention is a common cause of inconsistency.
34
+ - **NEVER leave orphan references.** After any rename/move, run `get_backlinks` and update every referring file. Missing this is the #1 source of broken links in a KB.
35
+ - **NEVER skip the routing confirmation for multi-file writes.** Even when the destinations seem obvious — the user's mental model may differ from yours.
36
+
37
+ ---
38
+
39
+ ## MindOS concepts
40
+
41
+ - **Space** — Knowledge partitions organized the way you think. Agents follow the same structure.
42
+ - **Instruction** — A rules file all connected agents obey. Written once, enforced everywhere.
43
+ - **Skill** — Teaches agents how to read, write, and organize the KB. Agents execute installed Skills, not guesses.
44
+
45
+ **Notes as Instruction and Skill** — `INSTRUCTION.md` and `SKILL.md` are just Markdown files in the tree. A note can be free-form text, governance rules agents must follow, or a procedure package agents execute.
46
+
47
+ ---
48
+
49
+ ## Thinking framework
50
+
51
+ Before acting, ask yourself:
52
+
53
+ 1. **What is the user's intent category?** → read-only lookup | single-file edit | multi-file routing | structural change | SOP execution. This determines which path to take below.
54
+ 2. **Where does this content belong?** → Scan the directory tree. If you can't place it in <5 seconds of looking at names, the user probably needs to confirm.
55
+ 3. **What already exists nearby?** → Read 1-2 sibling files before writing. Match their style.
56
+ 4. **What will break if I change this?** → For renames/moves: `get_backlinks`. For content edits: think about who else cites this fact.
57
+ 5. **Am I being asked, or am I volunteering?** → If the user didn't ask you to write, don't write.
58
+
59
+ ---
60
+
61
+ ## Task routing decision tree
62
+
63
+ ```
64
+ User request
65
+
66
+ ├─ Only asks to look up / summarize / quote?
67
+ │ └─ YES → [Read-only path]: search + read + cite sources. No writes. Skip hooks.
68
+
69
+ ├─ Asks to save / record / update / organize specific content?
70
+ │ ├─ Single file target? → [Single-file edit]: startup → read → minimal edit → verify
71
+ │ └─ Multiple files or unclear target? → [Multi-file routing]: parse → plan → confirm → edit
72
+
73
+ ├─ Structural change (rename / move / delete / reorganize)?
74
+ │ └─ [Structural path]: get_backlinks → impact report → confirm → execute → update refs → sync READMEs
75
+
76
+ ├─ Procedural / repeatable task?
77
+ │ └─ [SOP path]: search for matching SOP (keywords + tree scan) → read → execute stepwise → propose SOP update if diverged
78
+
79
+ ├─ Retrospective / distill / handoff?
80
+ │ └─ [Retrospective path]: confirm scope → extract artifacts → route → summarize changes
81
+
82
+ └─ Ambiguous or too broad?
83
+ └─ ASK for clarification. Propose 2-3 specific options based on KB state. Do NOT start editing.
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Startup protocol (write tasks only)
89
+
90
+ Skip for [read-only path](#task-routing-decision-tree). For all writes:
91
+
92
+ 1. **Bootstrap** — `mindos_bootstrap` (preferred) or manually read root `INSTRUCTION.md` + `README.md`.
93
+ 2. **Discover structure** — `mindos_list_spaces` (top-level zones + README blurbs) and/or `mindos_list_files` + targeted `mindos_search_notes`. Never assume top-level names.
94
+ 3. **Load local governance** — Read `README.md` / `INSTRUCTION.md` near the target path. Local conventions override global assumptions.
95
+ 4. **Match existing SOP** — If the task is procedural: scan tree for a procedure-holding directory (names like `Workflows/`, `SOPs/`, `流程/` are hints — don't assume). Search by keywords + `<!-- keywords: -->` metadata. If found, read and follow. If execution diverges, propose updating the SOP after.
96
+ 5. **Pre-write checks** — Confirm: target path exists or should be created; location is under a subdirectory (not root); current content is read; edit scope is minimal; backlink impact assessed for path changes.
97
+ 6. **Execute edits.**
98
+
99
+ If context is missing, continue with best effort and state assumptions.
100
+
101
+ ---
102
+
103
+ ## Tool selection
104
+
105
+ | Intent | Best tool | Avoid |
106
+ |--------|-----------|-------|
107
+ | Load context at start | `mindos_bootstrap` | Reading random files without bootstrap |
108
+ | List top-level Mind Spaces | `mindos_list_spaces` | Full `mindos_list_files` when you only need zone names and README blurbs |
109
+ | Find files | `mindos_search_notes` (2-4 parallel keyword variants) | Single-keyword search |
110
+ | Read content | `mindos_read_file` or `mindos_read_lines` (for large files) | Reading entire large file when you need 10 lines |
111
+ | Small text edit | `mindos_update_section` / `mindos_update_lines` / `mindos_insert_after_heading` | `mindos_write_file` for small changes |
112
+ | Append to end | `mindos_append_to_file` | Rewriting entire file to add a line |
113
+ | Full file replacement | `mindos_write_file` | Using this when a section edit suffices |
114
+ | New file | `mindos_create_file` | Creates parent dirs but does NOT scaffold Space files |
115
+ | New Mind Space (zone + README + INSTRUCTION) | `mindos_create_space` | The only way to create a Space. `create_file` creates plain folders |
116
+ | Rename a Space directory | `mindos_rename_space` | `rename_file` (files only; does not rename folders) |
117
+ | Add CSV row | `mindos_append_csv` (validates header) | Manual string append without header check |
118
+ | Check impact before rename | `mindos_get_backlinks` | Renaming without checking references |
119
+ | Inspect recent changes | `mindos_get_recent` | Guessing what changed recently |
120
+ | Recover old version | `mindos_get_file_at_version` | Asking user to recall what was there |
121
+
122
+ ### Fallbacks
123
+
124
+ - `mindos_bootstrap` unavailable → manual reads of root `INSTRUCTION.md` + `README.md`.
125
+ - Line/section tools unavailable → read + constrained `mindos_write_file` (simulate minimal edit).
126
+ - Search returns empty → don't give up: (1) scan tree in context, (2) read candidate files directly, (3) `mindos_list_files` on specific subdirectories, (4) try synonym/alternate-language keywords.
127
+
128
+ ---
129
+
130
+ ## Execution patterns
131
+
132
+ ### Single-file edit
133
+ Search → read target + local conventions → minimal edit → verify style match → summarize.
134
+
135
+ ### Context-aware Q&A (read-only)
136
+ Tree reasoning → search → read → answer with file citations → state gaps explicitly.
137
+
138
+ ### Multi-file routing
139
+ Parse unstructured input into semantic units → search candidates per unit → **present routing table** (what → file → position) → confirm → edit → summarize all changes.
140
+
141
+ ### Conversation retrospective
142
+ Confirm scope → extract decisions, rationale, pitfalls, next actions → route to best existing files → trace note of what changed.
143
+
144
+ ### SOP execution
145
+ Read SOP fully → execute step by step → update only stale sections → if diverged, propose SOP update.
146
+ **When creating a new SOP** → read [references/sop-template.md](./references/sop-template.md) first.
147
+
148
+ ### Structural changes
149
+ `get_backlinks` → report impact → confirm → execute → update all refs → sync READMEs.
150
+
151
+ ### Quick reference patterns
152
+
153
+ | Pattern | Key steps |
154
+ |---------|-----------|
155
+ | CSV append | Read header → validate fields → `mindos_append_csv` |
156
+ | TODO management | Find list → read format → minimal edit preserving conventions |
157
+ | Cross-agent handoff | Read task state + decisions → continue without re-discovery → write back progress |
158
+ | Knowledge conflict | Multi-keyword search → list affected files → present change plan → confirm → update |
159
+ | Periodic review | `get_recent` / `get_history` → read changed files → structured summary |
160
+ | Handoff doc | Read sources → synthesize (background, decisions, status, open items) → place in project dir |
161
+
162
+ ---
163
+
164
+ ## Interaction rules
165
+
166
+ - **Ambiguous request?** Ask first. Propose 2-3 options based on KB state (recent changes, directory structure). Never start reorganizing without understanding scope.
167
+ - **Cite sources.** Every fact from the KB gets a file path so the user can verify.
168
+ - **Brevity first.** Show the most likely match, not an exhaustive list.
169
+
170
+ ---
171
+
172
+ ## Post-task hooks
173
+
174
+ After **write tasks**, read [references/post-task-hooks.md](./references/post-task-hooks.md) for one-line follow-up proposals (experience capture, consistency sync, SOP drift, etc.).
175
+ **Read-only tasks: skip hooks.** If the user asked for quiet mode, skip all hooks for the session.
176
+ **Do NOT read** post-task-hooks for simple single-file edits or read-only lookups.
177
+
178
+ ## Preference capture
179
+
180
+ When the user expresses a standing preference ("don't do X", "always put Y in Z"), read [references/preference-capture.md](./references/preference-capture.md) and follow the confirm-then-write flow to `user-skill-rules.md`.
181
+ **Do NOT read** preference-capture unless the user actually expressed a preference to persist.
182
+
183
+ ## SOP authoring
184
+
185
+ When creating or rewriting a workflow SOP, **MANDATORY — read [references/sop-template.md](./references/sop-template.md)** for required structure (prerequisites, steps with branches, exit conditions, pitfall log).
186
+ **Do NOT read** sop-template for SOP execution (only for SOP creation/editing).