@moxxy/cli 1.2.7 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxxy/cli",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "CLI for the Moxxy agentic framework — manage agents, skills, plugins, channels, and vaults from the terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,29 +3,59 @@ import { Box, Text } from 'ink';
3
3
  import { THEME } from '../../theme.js';
4
4
 
5
5
  export function SkillMessage({ msg, showDetails = false }) {
6
- let icon = '';
7
- let color = THEME.warning;
6
+ const isRunning = msg.status === 'running';
7
+ const isError = msg.status === 'error';
8
+ const steps = msg.steps || [];
8
9
 
10
+ // Header icon and color
11
+ let headerIcon = '⊞';
12
+ let headerColor = THEME.primary;
9
13
  if (msg.status === 'completed') {
10
- icon = '✓';
11
- color = THEME.success;
12
- } else if (msg.status === 'error') {
13
- icon = '✗';
14
- color = THEME.error;
14
+ headerIcon = '✓';
15
+ headerColor = THEME.success;
16
+ } else if (isError) {
17
+ headerIcon = '✗';
18
+ headerColor = THEME.error;
15
19
  }
16
20
 
21
+ // Header text
22
+ const headerText = isRunning
23
+ ? `Invoking ${msg.name}...`
24
+ : msg.name;
25
+
17
26
  return (
18
27
  <Box flexDirection="column">
19
28
  <Text>
20
- <Text color={color}>{icon}</Text>
21
- <Text bold color={color}> {msg.name}</Text>
22
- <Text color={THEME.dim}> skill</Text>
23
- {msg.status === 'running' && <Text color={THEME.dim}> …</Text>}
24
- {msg.error ? <Text color={THEME.error}> - {msg.error}</Text> : null}
29
+ <Text color={headerColor}>{headerIcon}</Text>
30
+ <Text bold color={headerColor}> {headerText}</Text>
31
+ {isError && msg.error ? <Text color={THEME.error}> - {msg.error}</Text> : null}
25
32
  </Text>
26
- {showDetails && msg.description ? (
27
- <Text color={THEME.dim}> {msg.description}</Text>
28
- ) : null}
33
+ {steps.map((step, i) => {
34
+ let stepIcon = '◷';
35
+ let stepColor = THEME.dim;
36
+ if (step.status === 'completed') {
37
+ stepIcon = '◷';
38
+ stepColor = THEME.dim;
39
+ } else if (step.status === 'error') {
40
+ stepIcon = '✗';
41
+ stepColor = THEME.error;
42
+ } else {
43
+ stepIcon = '⏳';
44
+ stepColor = THEME.warning;
45
+ }
46
+ const resultText = step.error
47
+ ? step.error
48
+ : step.result
49
+ ? (step.result.length > 60 ? step.result.slice(0, 60) + '…' : step.result)
50
+ : null;
51
+ return (
52
+ <Text key={i}>
53
+ <Text color={stepColor}>{stepIcon}</Text>
54
+ <Text bold> {step.name}</Text>
55
+ {resultText ? <Text color={THEME.dim}> → {resultText}</Text> : null}
56
+ </Text>
57
+ );
58
+ })}
29
59
  </Box>
30
60
  );
31
61
  }
@@ -24,13 +24,17 @@ export function ToolGroup({ messages, expanded = false }) {
24
24
 
25
25
  const icon = errors > 0 ? '✗' : '✓';
26
26
  const color = errors > 0 ? THEME.error : THEME.success;
27
- const skills = messages.filter(m => m.type === 'skill').length;
27
+ const skillMsgs = messages.filter(m => m.type === 'skill');
28
28
  const tools = messages.filter(m => m.type === 'tool').length;
29
+ // Count total steps across all skills
30
+ const skillSteps = skillMsgs.reduce((sum, m) => sum + (m.steps ? m.steps.length : 0), 0);
29
31
  const parts = [];
30
- if (tools > 0) parts.push(`${tools} tool${tools > 1 ? 's' : ''}`);
31
- if (skills > 0) parts.push(`${skills} skill${skills > 1 ? 's' : ''}`);
32
+ const totalTools = tools + skillSteps;
33
+ if (totalTools > 0) parts.push(`${totalTools} tool${totalTools > 1 ? 's' : ''}`);
34
+ if (skillMsgs.length > 0) parts.push(`${skillMsgs.length} skill${skillMsgs.length > 1 ? 's' : ''}`);
32
35
  const label = parts.join(', ');
33
- const detail = errors > 0 ? `${completed} done, ${errors} failed` : `${completed} done`;
36
+ const totalDone = completed + skillSteps;
37
+ const detail = errors > 0 ? `${totalDone} done, ${errors} failed` : `${totalDone} done`;
34
38
 
35
39
  if (!expanded) {
36
40
  return (
@@ -8,9 +8,10 @@ const ERROR_EVENTS = new Set([
8
8
  ]);
9
9
 
10
10
  // Error events that should be shown as user-friendly system messages
11
- // instead of raw event brackets (brackets only in debug mode)
11
+ // instead of raw event brackets (brackets only in debug mode).
12
+ // NOTE: primitive.failed is handled separately (with skill awareness) before this check.
12
13
  const FRIENDLY_ERROR_EVENTS = new Set([
13
- 'run.failed', 'primitive.failed',
14
+ 'run.failed',
14
15
  ]);
15
16
 
16
17
  // Tool activity events always shown in chat as compact messages
@@ -104,6 +105,7 @@ export class EventsHandler {
104
105
  this._thinkingTimer = null;
105
106
  this.pendingAsk = null; // { questionId, question } = set when agent asks user
106
107
  this._subAgents = new Map(); // agentId -> { name, task, buffer, status }
108
+ this._activeSkillIdx = null; // index into this.messages for the running skill
107
109
  this._hiveStatus = null; // aggregated hive status tracker
108
110
  this._version = 0;
109
111
  this.messageVersion = 0;
@@ -597,6 +599,14 @@ export class EventsHandler {
597
599
  clearTimeout(this._deltaTimer);
598
600
  this._deltaTimer = null;
599
601
  }
602
+ // Close active skill session on final message
603
+ if (this._activeSkillIdx != null) {
604
+ const skill = this.messages[this._activeSkillIdx];
605
+ if (skill && skill.status === 'running') {
606
+ skill.status = 'completed';
607
+ }
608
+ this._activeSkillIdx = null;
609
+ }
600
610
  const finalContent = payload.content || payload.text || this._assistantBuffer;
601
611
  this._assistantBuffer = '';
602
612
  const last = this.messages[this.messages.length - 1];
@@ -614,6 +624,14 @@ export class EventsHandler {
614
624
  // Stop thinking on run completion or errors
615
625
  if (type === 'run.completed' || type === 'run.failed') {
616
626
  if (this.thinking) this._stopThinking();
627
+ // Close active skill session on run end
628
+ if (this._activeSkillIdx != null) {
629
+ const skill = this.messages[this._activeSkillIdx];
630
+ if (skill) {
631
+ skill.status = type === 'run.failed' ? 'error' : 'completed';
632
+ }
633
+ this._activeSkillIdx = null;
634
+ }
617
635
  }
618
636
 
619
637
  // Silence errors for hidden tool prefixes (e.g. agent.*)
@@ -621,6 +639,58 @@ export class EventsHandler {
621
639
  return;
622
640
  }
623
641
 
642
+ // Handle primitive.failed with skill awareness BEFORE friendly error fallback
643
+ if (type === 'primitive.failed') {
644
+ const toolName = payload.name || 'unknown';
645
+ const errorMsg = payload.error || 'unknown error';
646
+
647
+ // skill.execute itself failed → mark the skill as error and close session
648
+ if (toolName === 'skill.execute' && this._activeSkillIdx != null) {
649
+ const skill = this.messages[this._activeSkillIdx];
650
+ if (skill) {
651
+ skill.status = 'error';
652
+ skill.error = errorMsg;
653
+ }
654
+ this._activeSkillIdx = null;
655
+ this.messageVersion++;
656
+ this._notify();
657
+ return;
658
+ }
659
+
660
+ // A tool within an active skill failed → mark the step as error
661
+ if (this._activeSkillIdx != null) {
662
+ const skill = this.messages[this._activeSkillIdx];
663
+ if (skill && skill.status === 'running') {
664
+ const step = [...skill.steps].reverse().find(s => s.name === toolName && s.status === 'running');
665
+ if (step) {
666
+ step.status = 'error';
667
+ step.error = errorMsg;
668
+ this.messageVersion++;
669
+ this._notify();
670
+ return;
671
+ }
672
+ }
673
+ }
674
+
675
+ // Update the last tool message for the same primitive if it was just invoked
676
+ const last = this.messages[this.messages.length - 1];
677
+ if (last && last.type === 'tool' && last.name === toolName && last.status === 'invoked') {
678
+ last.status = 'error';
679
+ last.error = errorMsg;
680
+ this.messageVersion++;
681
+ this._notify();
682
+ return;
683
+ }
684
+
685
+ // Fallback: show as friendly system message (non-debug mode)
686
+ if (!this.debug) {
687
+ this.messages.push({ type: 'system', content: `Tool error: ${errorMsg}`, ts: event.ts });
688
+ this.messageVersion++;
689
+ this._notify();
690
+ return;
691
+ }
692
+ }
693
+
624
694
  // For user-facing error events, show as a friendly system message
625
695
  // (raw event format only in debug mode)
626
696
  if (!this.debug && FRIENDLY_ERROR_EVENTS.has(type)) {
@@ -636,8 +706,48 @@ export class EventsHandler {
636
706
  if (TOOL_ACTIVITY_EVENTS.has(type)) {
637
707
  if (type === 'primitive.invoked') {
638
708
  if (isHiddenTool(payload.name || '')) return;
709
+ const toolName = payload.name || 'unknown';
710
+
711
+ // Detect skill.execute → start a skill session
712
+ if (toolName === 'skill.execute') {
713
+ // Parse arguments — may be an object or a JSON string
714
+ let args = payload.arguments;
715
+ if (typeof args === 'string') {
716
+ try { args = JSON.parse(args); } catch { args = {}; }
717
+ }
718
+ const skillName = (args && args.name) || 'unknown';
719
+
720
+ // Close any previous active skill
721
+ if (this._activeSkillIdx != null) {
722
+ const prev = this.messages[this._activeSkillIdx];
723
+ if (prev && prev.status === 'running') {
724
+ prev.status = 'completed';
725
+ }
726
+ }
727
+
728
+ this.messages.push({
729
+ type: 'skill', name: skillName, status: 'running',
730
+ steps: [], ts: event.ts,
731
+ });
732
+ this._activeSkillIdx = this.messages.length - 1;
733
+ this.messageVersion++;
734
+ this._notify();
735
+ return;
736
+ }
737
+
738
+ // If a skill is active, nest this tool as a step
739
+ if (this._activeSkillIdx != null) {
740
+ const skill = this.messages[this._activeSkillIdx];
741
+ if (skill && skill.status === 'running') {
742
+ skill.steps.push({ name: toolName, status: 'running', result: null });
743
+ this.messageVersion++;
744
+ this._notify();
745
+ return;
746
+ }
747
+ }
748
+
639
749
  this.messages.push({
640
- type: 'tool', name: payload.name || 'unknown', status: 'invoked',
750
+ type: 'tool', name: toolName, status: 'invoked',
641
751
  arguments: formatParams(payload.arguments),
642
752
  rawArguments: payload.arguments,
643
753
  ts: event.ts,
@@ -648,9 +758,41 @@ export class EventsHandler {
648
758
  }
649
759
  if (type === 'primitive.completed') {
650
760
  if (isHiddenTool(payload.name || '')) return;
761
+ const toolName = payload.name || 'unknown';
762
+
763
+ // skill.execute completed → just absorb (skill stays running for subsequent tools)
764
+ if (toolName === 'skill.execute') {
765
+ // Update skill name from result if available
766
+ if (this._activeSkillIdx != null) {
767
+ const skill = this.messages[this._activeSkillIdx];
768
+ const resultName = payload.result && typeof payload.result === 'object' && payload.result.name;
769
+ if (skill && resultName) {
770
+ skill.name = resultName;
771
+ }
772
+ }
773
+ this.messageVersion++;
774
+ this._notify();
775
+ return;
776
+ }
777
+
778
+ // If a skill is active, update the step
779
+ if (this._activeSkillIdx != null) {
780
+ const skill = this.messages[this._activeSkillIdx];
781
+ if (skill && skill.status === 'running') {
782
+ const step = [...skill.steps].reverse().find(s => s.name === toolName && s.status === 'running');
783
+ if (step) {
784
+ step.status = 'completed';
785
+ step.result = formatParams(payload.result);
786
+ }
787
+ this.messageVersion++;
788
+ this._notify();
789
+ return;
790
+ }
791
+ }
792
+
651
793
  // Update the last tool message for the same primitive if it was just invoked
652
794
  const last = this.messages[this.messages.length - 1];
653
- if (last && last.type === 'tool' && last.name === (payload.name || 'unknown') && last.status === 'invoked') {
795
+ if (last && last.type === 'tool' && last.name === toolName && last.status === 'invoked') {
654
796
  last.status = 'completed';
655
797
  last.result = formatParams(payload.result);
656
798
  last.rawResult = payload.result;
@@ -663,10 +805,18 @@ export class EventsHandler {
663
805
 
664
806
  // Show skill activity events as compact bordered messages
665
807
  if (type === 'skill.invoked') {
808
+ // Close any previous active skill
809
+ if (this._activeSkillIdx != null) {
810
+ const prev = this.messages[this._activeSkillIdx];
811
+ if (prev && prev.status === 'running') {
812
+ prev.status = 'completed';
813
+ }
814
+ }
666
815
  this.messages.push({
667
816
  type: 'skill', name: payload.name || 'unknown', status: 'running',
668
- description: payload.description || '', ts: event.ts,
817
+ steps: [], ts: event.ts,
669
818
  });
819
+ this._activeSkillIdx = this.messages.length - 1;
670
820
  this.messageVersion++;
671
821
  this._notify();
672
822
  return;
@@ -680,6 +830,7 @@ export class EventsHandler {
680
830
  break;
681
831
  }
682
832
  }
833
+ this._activeSkillIdx = null;
683
834
  this.messageVersion++;
684
835
  this._notify();
685
836
  return;
@@ -699,27 +850,15 @@ export class EventsHandler {
699
850
  if (!found) {
700
851
  this.messages.push({
701
852
  type: 'skill', name: payload.name || 'unknown', status: 'error',
702
- error: payload.error || 'unknown error', ts: event.ts,
853
+ error: payload.error || 'unknown error', steps: [], ts: event.ts,
703
854
  });
704
855
  }
856
+ this._activeSkillIdx = null;
705
857
  this.messageVersion++;
706
858
  this._notify();
707
859
  return;
708
860
  }
709
861
 
710
- // Show tool error events
711
- if (type === 'primitive.failed') {
712
- // Update the last tool message for the same primitive if it was just invoked
713
- const last = this.messages[this.messages.length - 1];
714
- if (last && last.type === 'tool' && last.name === (payload.name || 'unknown') && last.status === 'invoked') {
715
- last.status = 'error';
716
- last.error = payload.error || 'unknown error';
717
- this.messageVersion++;
718
- this._notify();
719
- return;
720
- }
721
- }
722
-
723
862
  // Show error events always; show all events in debug mode
724
863
  if (ERROR_EVENTS.has(type) || this.debug) {
725
864
  this.messages.push({ type: 'event', eventType: type, payload, ts: event.ts });