@miphamai/cli 0.33.1 → 0.34.0
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/package.json +1 -1
- package/src/agent/cross-session/file-inbox.ts +30 -12
- package/src/core/compaction-progress.ts +205 -0
- package/src/core/context-compact.ts +70 -6
- package/src/core/context-drain.ts +65 -6
- package/src/core/context.ts +11 -4
- package/src/core/dream-engine.ts +552 -0
- package/src/core/emotion-detector.ts +257 -0
- package/src/core/engine.ts +50 -0
- package/src/core/hooks.ts +114 -9
- package/src/ui/app.tsx +164 -130
- package/src/ui/commands.ts +409 -35
- package/src/ui/error-boundary.tsx +85 -0
package/src/ui/app.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react'
|
|
2
2
|
import { Box, Text, useInput } from 'ink'
|
|
3
3
|
import TextInput from 'ink-text-input'
|
|
4
|
+
import { ErrorBoundary } from './error-boundary'
|
|
4
5
|
import type { QueryEngine } from '../core/engine'
|
|
5
6
|
import type { RemoteEngine } from '../daemon/remote-engine'
|
|
6
7
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
@@ -410,7 +411,17 @@ export function App({
|
|
|
410
411
|
role: msg.role,
|
|
411
412
|
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
412
413
|
}))
|
|
413
|
-
|
|
414
|
+
if (result.resumeWarning) {
|
|
415
|
+
// Fix 7: Session isolation — add a visual separator between current session
|
|
416
|
+
// and restored history to prevent accidental leakage/confusion
|
|
417
|
+
const separator: ChatMessage = {
|
|
418
|
+
role: 'assistant' as const,
|
|
419
|
+
content: '── Restored session history below ──',
|
|
420
|
+
}
|
|
421
|
+
setMessages((prev) => [...prev, separator, ...restored])
|
|
422
|
+
} else {
|
|
423
|
+
setMessages((prev) => [...prev, ...restored])
|
|
424
|
+
}
|
|
414
425
|
}
|
|
415
426
|
if (result.copyContent) {
|
|
416
427
|
// Copy to clipboard via pbcopy (macOS) or clip (Windows)
|
|
@@ -438,6 +449,24 @@ export function App({
|
|
|
438
449
|
}
|
|
439
450
|
}
|
|
440
451
|
|
|
452
|
+
// ── Emotion detection: adjust behavior based on user's emotional state ──
|
|
453
|
+
// Uses regex heuristics (zero-latency) to detect frustration/impatience/confusion.
|
|
454
|
+
// When frustrated, prepends a terseness instruction to the user input so the
|
|
455
|
+
// AI model skips explanations and gets straight to the fix.
|
|
456
|
+
let emotionPrefix = ''
|
|
457
|
+
try {
|
|
458
|
+
const { EmotionDetector } = await import('../core/emotion-detector.js')
|
|
459
|
+
const detector = new EmotionDetector()
|
|
460
|
+
const result = detector.detect(input)
|
|
461
|
+
if (result.emotion === 'frustrated' || result.emotion === 'impatient') {
|
|
462
|
+
emotionPrefix = `[SYSTEM NOTE: The user is ${result.emotion}. Be extremely concise. Skip all explanations, preambles, and summaries. Output only the fix/result. No "here's what I did" or "let me explain". One sentence maximum before code.]\n\n`
|
|
463
|
+
} else if (result.emotion === 'confused') {
|
|
464
|
+
emotionPrefix = `[SYSTEM NOTE: The user seems confused. Explain more thoroughly, break down complex steps, and offer clarifying questions rather than assuming understanding.]\n\n`
|
|
465
|
+
}
|
|
466
|
+
} catch {
|
|
467
|
+
// Emotion detection is non-critical — fail silently
|
|
468
|
+
}
|
|
469
|
+
|
|
441
470
|
// ── Normal message processing (AI chat) ──
|
|
442
471
|
setMessages((prev) => [...prev, { role: 'user', content: input }])
|
|
443
472
|
setIsLoading(true)
|
|
@@ -482,7 +511,10 @@ export function App({
|
|
|
482
511
|
}
|
|
483
512
|
|
|
484
513
|
try {
|
|
485
|
-
for await (const chunk of engine.process(
|
|
514
|
+
for await (const chunk of engine.process(
|
|
515
|
+
emotionPrefix ? emotionPrefix + input : input,
|
|
516
|
+
controller.signal,
|
|
517
|
+
)) {
|
|
486
518
|
// Reasoning content (DeepSeek V4 thinking mode) — silently consumed,
|
|
487
519
|
// not shown to user to avoid noise.
|
|
488
520
|
if (chunk.reasoning_content) {
|
|
@@ -743,140 +775,142 @@ export function App({
|
|
|
743
775
|
}
|
|
744
776
|
|
|
745
777
|
return (
|
|
746
|
-
<
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
<
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
778
|
+
<ErrorBoundary>
|
|
779
|
+
<Box flexDirection="column" padding={1} height="100%">
|
|
780
|
+
{/* Workflow progress — auto-detects active workflows, renders nothing when idle */}
|
|
781
|
+
<WorkflowProgress />
|
|
782
|
+
|
|
783
|
+
{/* Agent View Dashboard — Ctrl+G overlay (replaces chat + input) */}
|
|
784
|
+
{agentViewOpen && agentViewManager ? (
|
|
785
|
+
<AgentViewDashboard
|
|
786
|
+
manager={agentViewManager}
|
|
787
|
+
onAttach={() => {}}
|
|
788
|
+
onExit={() => setAgentViewOpen(false)}
|
|
789
|
+
/>
|
|
790
|
+
) : (
|
|
791
|
+
<>
|
|
792
|
+
{/* Chat panel */}
|
|
793
|
+
<ChatPanel messages={messages} focusMode={focusMode} />
|
|
794
|
+
|
|
795
|
+
{/* Input with separator lines */}
|
|
796
|
+
{pickerOpen ? (
|
|
797
|
+
<ModelPicker
|
|
798
|
+
config={config}
|
|
799
|
+
currentProvider={providerId}
|
|
800
|
+
currentModel={modelId}
|
|
801
|
+
onSelect={(newProvider, newModel) => {
|
|
802
|
+
engine.switchProvider(newProvider, newModel)
|
|
803
|
+
setProviderId(newProvider)
|
|
804
|
+
setModelId(newModel)
|
|
805
|
+
setPickerOpen(false)
|
|
806
|
+
setMessages((prev) => [
|
|
807
|
+
...prev,
|
|
808
|
+
{ role: 'system', content: `✓ Switched to ${newProvider}/${newModel}` },
|
|
809
|
+
])
|
|
810
|
+
}}
|
|
811
|
+
onNeedsApiKey={(providerId, modelId, providerName) => {
|
|
812
|
+
setApiKeyPrompt({ providerId, modelId, providerName })
|
|
813
|
+
setApiKeyInput('')
|
|
814
|
+
setPickerOpen(false)
|
|
815
|
+
setMessages((prev) => [
|
|
816
|
+
...prev,
|
|
817
|
+
{
|
|
818
|
+
role: 'system',
|
|
819
|
+
content: t('ui.picker.needs_api_key', { provider: providerName }),
|
|
820
|
+
},
|
|
821
|
+
])
|
|
822
|
+
}}
|
|
823
|
+
onClose={() => setPickerOpen(false)}
|
|
824
|
+
/>
|
|
825
|
+
) : (
|
|
826
|
+
/* Input bar (hidden when picker is open) */
|
|
827
|
+
<Box flexDirection="column">
|
|
828
|
+
<Text dimColor>──────────────────────────────</Text>
|
|
829
|
+
<InputBar
|
|
830
|
+
onSubmit={handleSubmit}
|
|
831
|
+
isLoading={isLoading}
|
|
832
|
+
onTogglePicker={() => setPickerOpen((prev) => !prev)}
|
|
833
|
+
onToggleFocus={() => setFocusMode((prev) => !prev)}
|
|
834
|
+
onToggleExpand={() => {
|
|
835
|
+
setMessages((prev) => {
|
|
836
|
+
const msgs = [...prev]
|
|
837
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
838
|
+
if (msgs[i]?.toolMeta) {
|
|
839
|
+
const meta = msgs[i]!.toolMeta!
|
|
840
|
+
if (meta.collapsed) {
|
|
841
|
+
msgs[i] = {
|
|
842
|
+
...msgs[i]!,
|
|
843
|
+
content: `🔧 ${meta.name}: ${meta.input}\n📋 Result: ${meta.output || '(pending)'}`,
|
|
844
|
+
toolMeta: { ...meta, collapsed: false },
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
const short =
|
|
848
|
+
meta.input.length > 50 ? meta.input.slice(0, 50) + '...' : meta.input
|
|
849
|
+
msgs[i] = {
|
|
850
|
+
...msgs[i]!,
|
|
851
|
+
content: `⏺ ${meta.name} · ${short} (Ctrl+O to expand)`,
|
|
852
|
+
toolMeta: { ...meta, collapsed: true },
|
|
853
|
+
}
|
|
820
854
|
}
|
|
855
|
+
break
|
|
821
856
|
}
|
|
822
|
-
break
|
|
823
857
|
}
|
|
858
|
+
return msgs
|
|
859
|
+
})
|
|
860
|
+
}}
|
|
861
|
+
onCyclePermission={() => {
|
|
862
|
+
setPermissionMode((prev) => {
|
|
863
|
+
const idx = PERMISSION_MODES.indexOf(prev)
|
|
864
|
+
const next = PERMISSION_MODES[(idx + 1) % PERMISSION_MODES.length]!
|
|
865
|
+
engine.getPermission().setMode(next)
|
|
866
|
+
return next
|
|
867
|
+
})
|
|
868
|
+
}}
|
|
869
|
+
onCancel={() => {
|
|
870
|
+
if (abortRef.current) {
|
|
871
|
+
abortRef.current.abort()
|
|
824
872
|
}
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
setPermissionMode((prev) => {
|
|
830
|
-
const idx = PERMISSION_MODES.indexOf(prev)
|
|
831
|
-
const next = PERMISSION_MODES[(idx + 1) % PERMISSION_MODES.length]!
|
|
832
|
-
engine.getPermission().setMode(next)
|
|
833
|
-
return next
|
|
834
|
-
})
|
|
835
|
-
}}
|
|
836
|
-
onCancel={() => {
|
|
837
|
-
if (abortRef.current) {
|
|
838
|
-
abortRef.current.abort()
|
|
839
|
-
}
|
|
840
|
-
}}
|
|
841
|
-
onToggleAgentView={() => setAgentViewOpen((prev) => !prev)}
|
|
842
|
-
/>
|
|
843
|
-
<Text dimColor>──────────────────────────────</Text>
|
|
844
|
-
</Box>
|
|
845
|
-
)}
|
|
846
|
-
|
|
847
|
-
{/* Agent status footer — shows active tool indicator + inline agent progress + running background agents */}
|
|
848
|
-
<AgentFooter
|
|
849
|
-
agents={Object.values(runningAgents)}
|
|
850
|
-
tick={agentTick}
|
|
851
|
-
activeTool={activeTool}
|
|
852
|
-
agentProgress={agentProgress}
|
|
853
|
-
/>
|
|
854
|
-
|
|
855
|
-
{/* Status line — Claude Code style */}
|
|
856
|
-
<Box marginTop={1} flexDirection="column">
|
|
857
|
-
{goalText && (
|
|
858
|
-
<Box>
|
|
859
|
-
<Text color="green">🎯 Goal: {goalText}</Text>
|
|
873
|
+
}}
|
|
874
|
+
onToggleAgentView={() => setAgentViewOpen((prev) => !prev)}
|
|
875
|
+
/>
|
|
876
|
+
<Text dimColor>──────────────────────────────</Text>
|
|
860
877
|
</Box>
|
|
861
878
|
)}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
879
|
+
|
|
880
|
+
{/* Agent status footer — shows active tool indicator + inline agent progress + running background agents */}
|
|
881
|
+
<AgentFooter
|
|
882
|
+
agents={Object.values(runningAgents)}
|
|
883
|
+
tick={agentTick}
|
|
884
|
+
activeTool={activeTool}
|
|
885
|
+
agentProgress={agentProgress}
|
|
886
|
+
/>
|
|
887
|
+
|
|
888
|
+
{/* Status line — Claude Code style */}
|
|
889
|
+
<Box marginTop={1} flexDirection="column">
|
|
890
|
+
{goalText && (
|
|
891
|
+
<Box>
|
|
892
|
+
<Text color="green">🎯 Goal: {goalText}</Text>
|
|
893
|
+
</Box>
|
|
894
|
+
)}
|
|
895
|
+
<Box flexDirection="row">
|
|
896
|
+
<Text color={PERMISSION_COLORS[permissionMode]}>
|
|
897
|
+
⏵⏵ {PERMISSION_LABELS[permissionMode]}
|
|
898
|
+
</Text>
|
|
899
|
+
<Text dimColor>
|
|
900
|
+
{' '}
|
|
901
|
+
({t('ui.status.shift_tab_cycle')}: {PERMISSION_LABELS.default} ·{' '}
|
|
902
|
+
{PERMISSION_LABELS.acceptEdits} · {PERMISSION_LABELS.bypassPermissions} ·{' '}
|
|
903
|
+
{PERMISSION_LABELS.plan} · {PERMISSION_LABELS.auto} · {PERMISSION_LABELS.dontAsk})
|
|
904
|
+
{' · '}
|
|
905
|
+
{t('ui.status.esc_to_interrupt')}
|
|
906
|
+
{' · '}
|
|
907
|
+
{t('ui.status.left_for_agents')}
|
|
908
|
+
</Text>
|
|
909
|
+
</Box>
|
|
876
910
|
</Box>
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
</
|
|
911
|
+
</>
|
|
912
|
+
)}
|
|
913
|
+
</Box>
|
|
914
|
+
</ErrorBoundary>
|
|
881
915
|
)
|
|
882
916
|
}
|