myagent-ai 1.6.7 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.6.7",
3
+ "version": "1.6.8",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -0,0 +1,33 @@
1
+ # Example Skill
2
+
3
+ A demonstration skill showing the SKILL.md format used by GLM Agent Engine.
4
+
5
+ ## Description
6
+
7
+ This is an example skill that demonstrates the expected structure and format for skills in the GLM Agent ecosystem. Skills are self-contained modules that extend the agent's capabilities with specialized functionality.
8
+
9
+ ## Capability
10
+
11
+ The agent can invoke this skill when users need demonstration or testing of the skill system. It serves as both documentation and a working example for skill developers.
12
+
13
+ ## Instructions
14
+
15
+ When this skill is loaded, the agent should:
16
+ 1. Acknowledge that the example skill has been invoked
17
+ 2. Explain the skill system architecture to the user
18
+ 3. Guide the user on how to create their own custom skills
19
+
20
+ ## Skill Structure
21
+
22
+ Each skill should contain:
23
+ - `SKILL.md` - This metadata file (required)
24
+ - Supporting scripts, configs, or data files as needed
25
+ - Any language-specific setup (package.json, requirements.txt, etc.)
26
+
27
+ ## Creating Custom Skills
28
+
29
+ To create a new skill:
30
+ 1. Create a directory under `/home/z/my-project/skills/`
31
+ 2. Add a `SKILL.md` file with proper metadata
32
+ 3. Include any necessary scripts or configurations
33
+ 4. The skill will be automatically detected by the agent engine
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ # Example skill execution script
3
+ # This demonstrates how skills can include executable logic
4
+
5
+ echo "Example Skill executed at: $(date)"
6
+ echo "Arguments: $@"
7
+ echo "Project directory: ${CLAWHUB_WORKDIR:-/home/z/my-project}"
8
+
9
+ # Your skill logic goes here
10
+ # Exit 0 for success, non-zero for failure
11
+ exit 0
package/web/api_server.py CHANGED
@@ -454,7 +454,11 @@ class ApiServer:
454
454
  # 支持 path 格式 (如 "coder/python-expert")
455
455
  agent_path = data.get("agent_path", agent_name)
456
456
  raw_session_id = data.get("session_id", "") or "web_default"
457
- session_id = f"{agent_path}_{raw_session_id}"
457
+ # Avoid double-prefixing: if the session_id already starts with agent_path_, use it directly
458
+ if raw_session_id.startswith(f"{agent_path}_"):
459
+ session_id = raw_session_id
460
+ else:
461
+ session_id = f"{agent_path}_{raw_session_id}"
458
462
  chat_mode = data.get("mode", "") # "exec" = 执行模式
459
463
  escalated = data.get("escalated", False) # 临时提权到 local
460
464
 
@@ -541,7 +545,11 @@ class ApiServer:
541
545
 
542
546
  agent_path = data.get("agent_path", data.get("agent_name", "default")) or "default"
543
547
  raw_session_id = data.get("session_id", "") or "web_default"
544
- session_id = f"{agent_path}_{raw_session_id}"
548
+ # Avoid double-prefixing: if the session_id already starts with agent_path_, use it directly
549
+ if raw_session_id.startswith(f"{agent_path}_"):
550
+ session_id = raw_session_id
551
+ else:
552
+ session_id = f"{agent_path}_{raw_session_id}"
545
553
  chat_mode = data.get("mode", "")
546
554
  escalated = data.get("escalated", False)
547
555
 
@@ -592,16 +600,7 @@ class ApiServer:
592
600
  agent_path=agent_path, agent_system_prompt=agent_system_prompt,
593
601
  chat_mode=chat_mode, stream_response=response,
594
602
  )
595
- elif self.core.main_agent and self.core.llm:
596
- # model_chain is empty (no model_id configured), but LLM is available
597
- # Use _stream_process_message for true token-by-token streaming
598
- full_response = await self._stream_process_message(
599
- clean_message, session_id, response,
600
- agent_path=agent_path, agent_system_prompt=agent_system_prompt,
601
- chat_mode=chat_mode,
602
- )
603
603
  else:
604
- # No LLM at all — non-streaming fallback
605
604
  full_response = await self.core.process_message(clean_message, session_id)
606
605
  await response.write(("data: " + json.dumps({"type": "text", "content": full_response}) + "\n\n").encode())
607
606
 
@@ -649,12 +648,6 @@ class ApiServer:
649
648
  agent_path=agent_path, agent_system_prompt=agent_system_prompt_q,
650
649
  chat_mode=chat_mode, stream_response=response,
651
650
  )
652
- elif self.core.main_agent and self.core.llm:
653
- full_response = await self._stream_process_message(
654
- clean_message_q, session_id, response,
655
- agent_path=agent_path, agent_system_prompt=agent_system_prompt_q,
656
- chat_mode=chat_mode,
657
- )
658
651
  else:
659
652
  full_response = await self.core.process_message(clean_message_q, session_id)
660
653
  await response.write(("data: " + json.dumps({"type": "text", "content": full_response}) + "\n\n").encode())
@@ -3885,7 +3878,6 @@ class ApiServer:
3885
3878
  # 获取agent的显示信息
3886
3879
  avatar = "🤖"
3887
3880
  display_name = agent_path
3888
- agent_color = "var(--accent)"
3889
3881
  if agent_cfg:
3890
3882
  avatar = agent_cfg.get("avatar_emoji", "🤖") or "🤖"
3891
3883
  display_name = agent_cfg.get("name", agent_path)
@@ -3895,7 +3887,6 @@ class ApiServer:
3895
3887
  "agent_path": agent_path,
3896
3888
  "name": display_name,
3897
3889
  "avatar": avatar,
3898
- "agent_color": agent_color,
3899
3890
  "response": response,
3900
3891
  }
3901
3892
  except Exception as e:
package/web/ui/chat.html CHANGED
@@ -149,6 +149,26 @@ input,textarea,select{font:inherit}
149
149
  .session-item:hover .session-actions{opacity:1}
150
150
  .session-actions{opacity:0;display:flex;gap:2px;flex-shrink:0}
151
151
  .session-actions .session-delete:hover{background:var(--danger);color:#fff}
152
+ .session-more-btn{
153
+ width:24px;height:24px;border-radius:4px;display:grid;place-items:center;
154
+ background:var(--bg2);transition:var(--transition);font-size:16px;color:var(--text3);
155
+ flex-shrink:0;cursor:pointer;border:none;line-height:1;
156
+ }
157
+ .session-more-btn:hover{background:var(--bg3);color:var(--text)}
158
+ .session-context-menu{
159
+ position:fixed;z-index:1000;background:var(--card);border:1px solid var(--border);
160
+ border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.15);padding:4px;min-width:140px;
161
+ animation:menuFadeIn .15s ease-out;
162
+ }
163
+ @keyframes menuFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
164
+ .session-context-menu button{
165
+ display:flex;align-items:center;gap:8px;width:100%;padding:8px 12px;border:none;
166
+ background:none;color:var(--text);font-size:13px;cursor:pointer;border-radius:6px;
167
+ transition:var(--transition);text-align:left;
168
+ }
169
+ .session-context-menu button:hover{background:var(--bg3)}
170
+ .session-context-menu button.danger{color:var(--danger)}
171
+ .session-context-menu button.danger:hover{background:rgba(239,68,68,.1)}
152
172
  .session-rename{
153
173
  opacity:0;width:24px;height:24px;border-radius:4px;display:grid;place-items:center;
154
174
  background:var(--bg2);transition:var(--transition);font-size:12px;color:var(--text3);
@@ -3072,6 +3092,64 @@ async function loadSessions() {
3072
3092
  }
3073
3093
  }
3074
3094
 
3095
+ // ── Session context menu (three dots) ──
3096
+ let _sessionMenuEl = null;
3097
+ function closeSessionMenu() {
3098
+ if (_sessionMenuEl) { _sessionMenuEl.remove(); _sessionMenuEl = null; }
3099
+ }
3100
+ function toggleSessionMenu(event, sessionId) {
3101
+ event.preventDefault();
3102
+ event.stopPropagation();
3103
+ closeSessionMenu();
3104
+ const menu = document.createElement('div');
3105
+ menu.className = 'session-context-menu';
3106
+ menu.innerHTML = `
3107
+ <button onclick="event.stopPropagation();closeSessionMenu();startRenameSession('${escapeHtml(sessionId)}')">
3108
+ <span>✏️</span> 重命名
3109
+ </button>
3110
+ <button onclick="event.stopPropagation();closeSessionMenu();clearSessionFromMenu('${escapeHtml(sessionId)}')">
3111
+ <span>🗑️</span> 清空消息
3112
+ </button>
3113
+ <button class="danger" onclick="event.stopPropagation();closeSessionMenu();deleteSession('${escapeHtml(sessionId)}')">
3114
+ <span>✕</span> 删除对话
3115
+ </button>
3116
+ `;
3117
+ // Position near the button
3118
+ const btn = event.currentTarget || event.target;
3119
+ const rect = btn.getBoundingClientRect();
3120
+ menu.style.top = rect.bottom + 4 + 'px';
3121
+ menu.style.right = (window.innerWidth - rect.right) + 'px';
3122
+ document.body.appendChild(menu);
3123
+ _sessionMenuEl = menu;
3124
+ // Close on outside click
3125
+ setTimeout(() => {
3126
+ document.addEventListener('click', closeSessionMenu, { once: true });
3127
+ }, 10);
3128
+ }
3129
+ async function clearSessionFromMenu(id) {
3130
+ if (!id || id === '__new__') return;
3131
+ if (!confirm('确定清空此对话的消息?会话本身不会被删除。')) return;
3132
+ try {
3133
+ const resp = await fetch(`/api/sessions/${encodeURIComponent(id)}/messages`, {
3134
+ method: 'DELETE',
3135
+ headers: { 'Content-Type': 'application/json' }
3136
+ });
3137
+ if (!resp.ok) {
3138
+ const errData = await resp.json().catch(() => ({}));
3139
+ throw new Error(errData.error || 'HTTP ' + resp.status);
3140
+ }
3141
+ if (state.activeSessionId === id) {
3142
+ state.messages = [];
3143
+ renderMessages();
3144
+ }
3145
+ toast('消息已清空', 'success');
3146
+ await loadSessions();
3147
+ } catch (e) {
3148
+ console.error('Clear session error:', e);
3149
+ toast('清空失败: ' + e.message, 'error');
3150
+ }
3151
+ }
3152
+
3075
3153
  function formatSessionName(id) {
3076
3154
  if (id.startsWith('web_')) return id.replace('web_', '').replace(/_/g, ' ');
3077
3155
  if (id.startsWith('cli_')) return 'CLI: ' + id.replace('cli_', '');
@@ -3096,8 +3174,7 @@ function renderSessions(filter = '') {
3096
3174
  </div>
3097
3175
  ${s.id !== '__new__' ? `
3098
3176
  <div class="session-actions">
3099
- <button class="session-rename" onclick="event.stopPropagation();startRenameSession('${escapeHtml(s.id)}')" title="重命名">✏️</button>
3100
- <button class="session-delete" onclick="event.stopPropagation();deleteSession('${escapeHtml(s.id)}')" title="删除">✕</button>
3177
+ <button class="session-more-btn" onclick="event.stopPropagation();toggleSessionMenu(event,'${escapeHtml(s.id)}')" title="更多操作">⋮</button>
3101
3178
  </div>
3102
3179
  ` : ''}
3103
3180
  </div>
@@ -4952,22 +5029,7 @@ async function selectGroup(gid) {
4952
5029
 
4953
5030
  // Load messages
4954
5031
  var msgsData = await getGroupMessages(gid);
4955
- // Normalize backend message format: map sender->role, sender_name->agent_name, sender_avatar->agent_emoji
4956
- groupMessages = (msgsData || []).map(function(m) {
4957
- if (m.sender) {
4958
- return {
4959
- role: m.sender === 'user' ? 'user' : 'assistant',
4960
- agent: m.agent_path || '',
4961
- agent_name: m.sender_name || '',
4962
- agent_emoji: m.sender_avatar || '🤖',
4963
- agent_color: m.agent_color || '',
4964
- content: m.content || '',
4965
- time: m.timestamp ? new Date(m.timestamp * 1000).toISOString() : (m.time || ''),
4966
- type: m.msg_type === 'system' ? 'system' : '',
4967
- };
4968
- }
4969
- return m;
4970
- });
5032
+ groupMessages = msgsData || [];
4971
5033
  renderGroupMessages();
4972
5034
  } catch (e) {
4973
5035
  toast('加载群聊失败: ' + e.message, 'error');
@@ -5026,11 +5088,11 @@ function renderGroupMessages() {
5026
5088
  + '<div><div class="message-bubble">' + renderMarkdown(msg.content || '') + '</div>'
5027
5089
  + (msg.time ? '<div class="message-time" style="text-align:right">' + formatTime(msg.time) + '</div>' : '')
5028
5090
  + '</div></div>';
5029
- } else if (msg.role === 'assistant' || msg.agent) {
5030
- var agentName = msg.agent_name || msg.agent || 'Agent';
5031
- var agentEmoji = msg.agent_emoji || '🤖';
5032
- var agentColor = msg.agent_color || 'var(--accent)';
5033
- var agentRole = msg.agent_role || '';
5091
+ } else if (msg.role === 'assistant' || msg.sender === 'agent' || msg.agent) {
5092
+ var agentName = msg.agent_name || msg.sender_name || msg.name || msg.agent || 'Agent';
5093
+ var agentEmoji = msg.agent_emoji || msg.sender_avatar || msg.avatar || '🤖';
5094
+ var agentColor = msg.agent_color || msg.color || 'var(--accent)';
5095
+ var agentRole = msg.agent_role || msg.role_detail || '';
5034
5096
  html += '<div class="group-msg-row">'
5035
5097
  + '<div class="group-msg-avatar" style="background:' + agentColor + ';color:#fff">' + agentEmoji + '</div>'
5036
5098
  + '<div>'
@@ -5044,10 +5106,9 @@ function renderGroupMessages() {
5044
5106
  // Multiple agent responses (broadcast response)
5045
5107
  for (var j = 0; j < msg.responses.length; j++) {
5046
5108
  var r = msg.responses[j];
5047
- // Backend returns "name" and "avatar" fields, with "agent_name"/"agent_emoji" as fallback
5048
- var rName = r.name || r.agent_name || r.agent || 'Agent';
5049
- var rEmoji = r.avatar || r.agent_emoji || '🤖';
5050
- var rColor = r.agent_color || 'var(--accent)';
5109
+ var rName = r.agent_name || r.sender_name || r.name || r.agent || 'Agent';
5110
+ var rEmoji = r.agent_emoji || r.sender_avatar || r.avatar || '🤖';
5111
+ var rColor = r.agent_color || r.color || 'var(--accent)';
5051
5112
  html += '<div class="group-msg-row">'
5052
5113
  + '<div class="group-msg-avatar" style="background:' + rColor + ';color:#fff">' + rEmoji + '</div>'
5053
5114
  + '<div>'