groove-dev 0.27.171 → 0.27.174

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 (44) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +2 -0
  4. package/node_modules/@groove-dev/daemon/src/index.js +2 -0
  5. package/node_modules/@groove-dev/daemon/src/innerchat.js +100 -0
  6. package/node_modules/@groove-dev/daemon/src/process.js +5 -0
  7. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +2 -1
  8. package/node_modules/@groove-dev/daemon/src/routes/innerchat.js +34 -0
  9. package/node_modules/@groove-dev/daemon/src/validate.js +2 -2
  10. package/node_modules/@groove-dev/daemon/test/innerchat.test.js +203 -0
  11. package/node_modules/@groove-dev/gui/dist/assets/index-BvJwMNAX.css +1 -0
  12. package/node_modules/@groove-dev/gui/dist/assets/{index-BsCp-oqa.js → index-DZY-EWPs.js} +217 -217
  13. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  14. package/node_modules/@groove-dev/gui/package.json +1 -1
  15. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -0
  16. package/node_modules/@groove-dev/gui/src/components/agents/recommended-team-card.jsx +1 -1
  17. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +25 -1
  18. package/node_modules/@groove-dev/gui/src/stores/groove.js +51 -0
  19. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +14 -0
  20. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +9 -0
  21. package/node_modules/@groove-dev/gui/src/views/agents.jsx +1 -1
  22. package/package.json +1 -1
  23. package/packages/cli/package.json +1 -1
  24. package/packages/daemon/package.json +1 -1
  25. package/packages/daemon/src/api.js +2 -0
  26. package/packages/daemon/src/index.js +2 -0
  27. package/packages/daemon/src/innerchat.js +100 -0
  28. package/packages/daemon/src/process.js +5 -0
  29. package/packages/daemon/src/providers/claude-code.js +2 -1
  30. package/packages/daemon/src/routes/innerchat.js +34 -0
  31. package/packages/daemon/src/validate.js +2 -2
  32. package/packages/gui/dist/assets/index-BvJwMNAX.css +1 -0
  33. package/packages/gui/dist/assets/{index-BsCp-oqa.js → index-DZY-EWPs.js} +217 -217
  34. package/packages/gui/dist/index.html +2 -2
  35. package/packages/gui/package.json +1 -1
  36. package/packages/gui/src/components/agents/agent-config.jsx +1 -0
  37. package/packages/gui/src/components/agents/recommended-team-card.jsx +1 -1
  38. package/packages/gui/src/components/chat/chat-messages.jsx +25 -1
  39. package/packages/gui/src/stores/groove.js +51 -0
  40. package/packages/gui/src/stores/slices/agents-slice.js +14 -0
  41. package/packages/gui/src/stores/slices/ui-slice.js +9 -0
  42. package/packages/gui/src/views/agents.jsx +1 -1
  43. package/node_modules/@groove-dev/gui/dist/assets/index-BrMU-6gi.css +0 -1
  44. package/packages/gui/dist/assets/index-BrMU-6gi.css +0 -1
@@ -6,12 +6,12 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <link rel="icon" type="image/png" href="/favicon.png" />
8
8
  <title>Groove GUI</title>
9
- <script type="module" crossorigin src="/assets/index-BsCp-oqa.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-DZY-EWPs.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-BrMU-6gi.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BvJwMNAX.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.171",
3
+ "version": "0.27.174",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -548,6 +548,7 @@ export function AgentConfig({ agent }) {
548
548
  { value: 'default', label: 'Default' },
549
549
  { value: 'high', label: 'High' },
550
550
  { value: 'max', label: 'Max' },
551
+ { value: 'ultra', label: 'Ultra' },
551
552
  ]}
552
553
  value={effort}
553
554
  onChange={handleEffort}
@@ -251,7 +251,7 @@ export function RecommendedTeamCard() {
251
251
  <div className="space-y-1">
252
252
  <label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Gauge size={10} />Effort Level</label>
253
253
  <div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
254
- {[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }].map((opt) => (
254
+ {[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }, { value: 'ultra', label: 'Ultra' }].map((opt) => (
255
255
  <button
256
256
  key={opt.value}
257
257
  onClick={() => handleAgentField(i, 'effort', opt.value)}
@@ -1,6 +1,6 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useRef, useEffect, useState, useCallback } from 'react';
3
- import { Copy, Check, ArrowRight, Download, Maximize2, X, Image as ImageIcon, RefreshCw, Loader2 } from 'lucide-react';
3
+ import { Copy, Check, ArrowRight, Download, Maximize2, X, Image as ImageIcon, RefreshCw, Loader2, MessageSquare } from 'lucide-react';
4
4
  import { cn } from '../../lib/cn';
5
5
  import { timeAgo } from '../../lib/format';
6
6
  import { ThinkingIndicator } from '../ui/thinking-indicator';
@@ -391,6 +391,29 @@ function SystemMessage({ msg }) {
391
391
  );
392
392
  }
393
393
 
394
+ function InnerChatMessage({ msg }) {
395
+ const senderName = msg.innerchat?.fromAgent?.name || 'Agent';
396
+ const senderRole = msg.innerchat?.fromAgent?.role;
397
+ const cleanText = stripEmojis(msg.text);
398
+ return (
399
+ <div className="max-w-[85%]">
400
+ <div className="flex items-center gap-1.5 mb-1">
401
+ <MessageSquare size={11} className="text-warning" />
402
+ <span className="text-2xs font-sans font-medium text-warning">InnerChat</span>
403
+ <span className="text-2xs font-sans text-text-3">from</span>
404
+ <span className="text-2xs font-sans font-medium text-text-1">{senderName}</span>
405
+ {senderRole && <span className="text-2xs font-sans text-text-3">{senderRole}</span>}
406
+ </div>
407
+ <div className="border-l-2 border-warning/40 pl-3.5 py-1">
408
+ <div className="text-sm text-text-1 font-sans whitespace-pre-wrap break-words leading-relaxed">
409
+ <RenderedMarkdown text={cleanText} />
410
+ </div>
411
+ </div>
412
+ <div className="text-2xs text-text-4 font-sans mt-1">{timeAgo(msg.timestamp)}</div>
413
+ </div>
414
+ );
415
+ }
416
+
394
417
  function StreamingCursor() {
395
418
  return (
396
419
  <span className="inline-block w-2 h-4 bg-accent/60 ml-0.5 animate-pulse rounded-sm" />
@@ -484,6 +507,7 @@ export function ChatMessages({ messages, isStreaming, model, mode, onImageReply,
484
507
  {messages.map((msg, i) => {
485
508
  if (msg.type === 'image-loading') return <ImageLoadingMessage key={i} msg={msg} />;
486
509
  if (msg.type === 'image') return <ImageMessage key={i} msg={msg} onReply={onImageReply} />;
510
+ if (msg.from === 'innerchat') return <InnerChatMessage key={i} msg={msg} />;
487
511
  if (msg.from === 'user') return <UserMessage key={i} msg={msg} />;
488
512
  if (msg.from === 'system') return <SystemMessage key={i} msg={msg} />;
489
513
  return <AssistantMessage key={i} msg={msg} model={model} role={role} />;
@@ -367,6 +367,57 @@ export const useGrooveStore = create((set, get) => ({
367
367
  break;
368
368
  }
369
369
 
370
+ case 'innerchat:sent': {
371
+ const ic = msg.data;
372
+ get().addChatMessage(ic.to.id, 'innerchat', ic.message, false);
373
+ set((s) => {
374
+ const icMsgs = { ...s.innerchatMessages };
375
+ icMsgs[ic.id] = ic;
376
+ persistJSON('groove:innerchatMessages', icMsgs);
377
+ return { innerchatMessages: icMsgs };
378
+ });
379
+ // Tag the chat message with innerchat metadata
380
+ set((s) => {
381
+ const history = { ...s.chatHistory };
382
+ const arr = history[ic.to.id] || [];
383
+ if (arr.length > 0) {
384
+ const last = arr[arr.length - 1];
385
+ if (last.from === 'innerchat' && last.text === ic.message) {
386
+ arr[arr.length - 1] = { ...last, innerchat: { messageId: ic.id, fromAgent: ic.from } };
387
+ history[ic.to.id] = arr;
388
+ persistJSON('groove:chatHistory', history);
389
+ return { chatHistory: history };
390
+ }
391
+ }
392
+ return {};
393
+ });
394
+ break;
395
+ }
396
+
397
+ case 'innerchat:response': {
398
+ const ic = msg.data;
399
+ get().addChatMessage(ic.from.id, 'innerchat', ic.response, false);
400
+ set((s) => {
401
+ const icMsgs = { ...s.innerchatMessages };
402
+ icMsgs[ic.id] = ic;
403
+ persistJSON('groove:innerchatMessages', icMsgs);
404
+ // Tag the response chat message with innerchat metadata
405
+ const history = { ...s.chatHistory };
406
+ const arr = history[ic.from.id] || [];
407
+ if (arr.length > 0) {
408
+ const last = arr[arr.length - 1];
409
+ if (last.from === 'innerchat' && last.text === ic.response) {
410
+ arr[arr.length - 1] = { ...last, innerchat: { messageId: ic.id, fromAgent: ic.to } };
411
+ history[ic.from.id] = arr;
412
+ persistJSON('groove:chatHistory', history);
413
+ }
414
+ }
415
+ return { innerchatMessages: icMsgs, chatHistory: history };
416
+ });
417
+ get().addToast('info', `${ic.to.name} replied to ${ic.from.name}`, 'InnerChat response received');
418
+ break;
419
+ }
420
+
370
421
  case 'recommended-team:ready':
371
422
  if (!get().recommendedTeam) get().checkRecommendedTeam();
372
423
  break;
@@ -102,6 +102,20 @@ export const createAgentsSlice = (set, get) => ({
102
102
  }
103
103
  },
104
104
 
105
+ // ── InnerChat ─────────────────────────────────────────────
106
+
107
+ innerchatMessages: loadJSON('groove:innerchatMessages') || {},
108
+
109
+ async sendInnerChat(fromId, toId, message) {
110
+ try {
111
+ const msg = await api.post('/innerchat/send', { from: fromId, to: toId, message });
112
+ return msg;
113
+ } catch (err) {
114
+ get().addToast('error', 'InnerChat failed', err.message);
115
+ throw err;
116
+ }
117
+ },
118
+
105
119
  // ── Chat ──────────────────────────────────────────────────
106
120
 
107
121
  addChatMessage(agentId, from, text, isQuery = false, attachments = undefined) {
@@ -120,6 +120,15 @@ export const createUiSlice = (set, get) => ({
120
120
  if (pane === 1 && agentId === null) {
121
121
  updates.fleetSplitMode = false;
122
122
  }
123
+ const primaryId = agentId || selected[0];
124
+ if (primaryId) {
125
+ const tid = get().activeTeamId;
126
+ const panel = { type: 'agent', agentId: primaryId };
127
+ updates.detailPanel = panel;
128
+ updates.teamDetailPanels = { ...get().teamDetailPanels, [tid]: panel };
129
+ } else {
130
+ updates.detailPanel = null;
131
+ }
123
132
  set(updates);
124
133
  },
125
134
  fleetToggleSplit() {
@@ -1470,7 +1470,7 @@ function _RecommendedTeamCard_removed() { return null; /* extracted to component
1470
1470
  <div className="space-y-1">
1471
1471
  <label className="flex items-center gap-1 text-2xs text-text-3 font-sans"><Gauge size={10} />Effort Level</label>
1472
1472
  <div className="flex bg-surface-3 rounded-md p-0.5 border border-border-subtle">
1473
- {[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }].map((opt) => (
1473
+ {[{ value: 'min', label: 'Min' }, { value: 'low', label: 'Low' }, { value: 'default', label: 'Default' }, { value: 'high', label: 'High' }, { value: 'max', label: 'Max' }, { value: 'ultra', label: 'Ultra' }].map((opt) => (
1474
1474
  <button
1475
1475
  key={opt.value}
1476
1476
  onClick={() => handleAgentField(i, 'effort', opt.value)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.171",
3
+ "version": "0.27.174",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.171",
3
+ "version": "0.27.174",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.171",
3
+ "version": "0.27.174",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -27,6 +27,7 @@ import { registerIntegrationRoutes } from './routes/integrations.js';
27
27
  import { registerFileRoutes, resetEditorRoot } from './routes/files.js';
28
28
  import { registerNetworkRoutes } from './routes/network.js';
29
29
  import { registerScheduleRoutes } from './routes/schedules.js';
30
+ import { registerInnerChatRoutes } from './routes/innerchat.js';
30
31
 
31
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
32
33
  const pkgVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
@@ -175,6 +176,7 @@ export function createApi(app, daemon) {
175
176
  registerFileRoutes(app, daemon);
176
177
  registerNetworkRoutes(app, daemon);
177
178
  registerScheduleRoutes(app, daemon);
179
+ registerInnerChatRoutes(app, daemon);
178
180
 
179
181
 
180
182
  // Token usage
@@ -46,6 +46,7 @@ import { MLXServerManager } from './mlx-server.js';
46
46
  import { RepoImporter } from './repo-import.js';
47
47
  import { ConversationManager } from './conversations.js';
48
48
  import { Toys } from './toys.js';
49
+ import { InnerChat } from './innerchat.js';
49
50
  import { TrajectoryCapture, ConsentManager } from '../../../moe-training/client/index.js';
50
51
  import { isFirstRun, runFirstTimeSetup, loadConfig, saveConfig, printWelcome } from './firstrun.js';
51
52
  import { bindDaemon as bindGrooveNetworkDaemon } from './providers/groove-network.js';
@@ -158,6 +159,7 @@ export class Daemon {
158
159
  this.repoImporter = new RepoImporter(this);
159
160
  this.modelLab = new ModelLab(this);
160
161
  this.toys = new Toys(this);
162
+ this.innerchat = new InnerChat(this);
161
163
  this.trajectoryCapture = null;
162
164
 
163
165
  // Hook teams.delete to clean up agent-loop session files
@@ -0,0 +1,100 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { randomUUID } from 'crypto';
4
+
5
+ export class InnerChat {
6
+ constructor(daemon) {
7
+ this.daemon = daemon;
8
+ this.messages = new Map();
9
+ this.pendingResponses = new Map();
10
+ }
11
+
12
+ async send(fromAgentId, toAgentId, message) {
13
+ const fromAgent = this.daemon.registry.get(fromAgentId);
14
+ const toAgent = this.daemon.registry.get(toAgentId);
15
+ if (!fromAgent) throw new Error(`Sender agent ${fromAgentId} not found`);
16
+ if (!toAgent) throw new Error(`Target agent ${toAgentId} not found`);
17
+
18
+ const id = randomUUID().slice(0, 12);
19
+ const msg = {
20
+ id,
21
+ from: { id: fromAgent.id, name: fromAgent.name, role: fromAgent.role },
22
+ to: { id: toAgent.id, name: toAgent.name, role: toAgent.role },
23
+ message,
24
+ response: null,
25
+ status: 'sent',
26
+ timestamp: Date.now(),
27
+ respondedAt: null,
28
+ };
29
+
30
+ this.messages.set(id, msg);
31
+ this.pendingResponses.set(toAgentId, id);
32
+
33
+ const wrapped = `[InnerChat from ${fromAgent.name} (${fromAgent.role})]\n\n${message}\n\nReply normally — your response will be relayed back to ${fromAgent.name}.`;
34
+
35
+ let deliveryStatus = 'sent';
36
+ if (this.daemon.processes.hasAgentLoop(toAgentId)) {
37
+ const sent = await this.daemon.processes.sendMessage(toAgentId, wrapped, 'agent');
38
+ deliveryStatus = sent ? 'delivered' : 'queued';
39
+ } else if (this.daemon.processes.isRunning(toAgentId)) {
40
+ this.daemon.processes.queueMessage(toAgentId, wrapped);
41
+ deliveryStatus = 'queued';
42
+ } else {
43
+ throw new Error(`Target agent ${toAgent.name} is not running`);
44
+ }
45
+
46
+ msg.status = deliveryStatus;
47
+ this.daemon.broadcast({ type: 'innerchat:sent', data: msg });
48
+ this.daemon.audit.log('innerchat.send', { id, from: fromAgentId, to: toAgentId });
49
+
50
+ return msg;
51
+ }
52
+
53
+ onAgentOutput(agentId, output) {
54
+ const messageId = this.pendingResponses.get(agentId);
55
+ if (!messageId) return;
56
+ if (output.type !== 'result') return;
57
+
58
+ const msg = this.messages.get(messageId);
59
+ if (!msg) return;
60
+
61
+ let responseText = '';
62
+ if (typeof output.data === 'string') {
63
+ responseText = output.data;
64
+ } else if (Array.isArray(output.data)) {
65
+ responseText = output.data.filter(b => b.type === 'text').map(b => b.text).join('\n');
66
+ }
67
+ if (!responseText.trim()) return;
68
+
69
+ msg.response = responseText.trim();
70
+ msg.status = 'responded';
71
+ msg.respondedAt = Date.now();
72
+ this.pendingResponses.delete(agentId);
73
+
74
+ const relay = `[InnerChat reply from ${msg.to.name} (${msg.to.role})]\n\n${msg.response}`;
75
+ const senderId = msg.from.id;
76
+ if (this.daemon.processes.hasAgentLoop(senderId)) {
77
+ this.daemon.processes.sendMessage(senderId, relay, 'agent').catch(() => {});
78
+ } else if (this.daemon.processes.isRunning(senderId)) {
79
+ this.daemon.processes.queueMessage(senderId, relay);
80
+ }
81
+
82
+ this.daemon.broadcast({ type: 'innerchat:response', data: msg });
83
+ this.daemon.audit.log('innerchat.response', { id: messageId, from: msg.from.id, to: msg.to.id });
84
+ }
85
+
86
+ getMessages(agentId = null) {
87
+ const all = Array.from(this.messages.values());
88
+ if (!agentId) return all;
89
+ return all.filter(m => m.from.id === agentId || m.to.id === agentId);
90
+ }
91
+
92
+ getMessage(id) {
93
+ return this.messages.get(id) || null;
94
+ }
95
+
96
+ getPending(agentId) {
97
+ const messageId = this.pendingResponses.get(agentId);
98
+ return messageId ? this.messages.get(messageId) : null;
99
+ }
100
+ }
@@ -1582,6 +1582,11 @@ For normal file edits within your scope, proceed without review.
1582
1582
 
1583
1583
  registry.update(agentId, updates);
1584
1584
 
1585
+ // Notify innerchat for response capture
1586
+ if (this.daemon.innerchat) {
1587
+ this.daemon.innerchat.onAgentOutput(agentId, output);
1588
+ }
1589
+
1585
1590
  // Throttle streaming broadcasts to 4/sec per agent
1586
1591
  const isStreaming = output.type !== 'result';
1587
1592
  if (isStreaming) {
@@ -39,6 +39,7 @@ export class ClaudeCodeProvider extends Provider {
39
39
  static authType = 'subscription';
40
40
  static managesOwnContext = true; // Claude Code compacts context internally (~25-37% → 2-8%)
41
41
  static models = [
42
+ { id: 'claude-fable-5', name: 'Claude Fable 5', tier: 'heavy', contextWindow: 1_000_000 },
42
43
  { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', tier: 'heavy', contextWindow: 1_000_000 },
43
44
  { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', tier: 'heavy', contextWindow: 1_000_000 },
44
45
  { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', tier: 'medium', contextWindow: 200_000 },
@@ -74,7 +75,7 @@ export class ClaudeCodeProvider extends Provider {
74
75
  normalizeConfig(config) {
75
76
  if (typeof config.reasoningEffort === 'number') {
76
77
  const e = config.reasoningEffort;
77
- config.effort = e <= 20 ? 'none' : e <= 40 ? 'low' : e <= 60 ? 'medium' : e <= 80 ? 'high' : 'xhigh';
78
+ config.effort = e <= 15 ? 'none' : e <= 30 ? 'low' : e <= 50 ? 'medium' : e <= 70 ? 'high' : e <= 85 ? 'xhigh' : 'ultra';
78
79
  }
79
80
  return config;
80
81
  }
@@ -0,0 +1,34 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ export function registerInnerChatRoutes(app, daemon) {
4
+ app.post('/api/innerchat/send', async (req, res) => {
5
+ try {
6
+ const { from, to, message } = req.body;
7
+ if (!from || typeof from !== 'string') return res.status(400).json({ error: 'from (agent ID) is required' });
8
+ if (!to || typeof to !== 'string') return res.status(400).json({ error: 'to (agent ID) is required' });
9
+ if (!message || typeof message !== 'string' || !message.trim()) return res.status(400).json({ error: 'message is required' });
10
+ if (from === to) return res.status(400).json({ error: 'cannot send a message to yourself' });
11
+
12
+ const msg = await daemon.innerchat.send(from, to, message.trim());
13
+ res.json(msg);
14
+ } catch (err) {
15
+ res.status(400).json({ error: err.message });
16
+ }
17
+ });
18
+
19
+ app.get('/api/innerchat/messages', (req, res) => {
20
+ const { agentId } = req.query;
21
+ res.json({ messages: daemon.innerchat.getMessages(agentId || null) });
22
+ });
23
+
24
+ app.get('/api/innerchat/messages/:id', (req, res) => {
25
+ const msg = daemon.innerchat.getMessage(req.params.id);
26
+ if (!msg) return res.status(404).json({ error: 'Message not found' });
27
+ res.json(msg);
28
+ });
29
+
30
+ app.get('/api/innerchat/pending/:agentId', (req, res) => {
31
+ const pending = daemon.innerchat.getPending(req.params.agentId);
32
+ res.json({ pending: pending || null });
33
+ });
34
+ }
@@ -112,7 +112,7 @@ export function validateAgentConfig(config) {
112
112
  if (!isNaN(v) && v >= 0 && v <= 100) verbosity = Math.round(v);
113
113
  }
114
114
 
115
- const validEffort = ['min', 'low', 'default', 'high', 'max'];
115
+ const validEffort = ['min', 'low', 'default', 'high', 'max', 'ultra'];
116
116
  const effort = validEffort.includes(config.effort) ? config.effort : undefined;
117
117
 
118
118
  const validRouting = ['fixed', 'auto', 'auto-floor'];
@@ -227,7 +227,7 @@ export function validateGatewayConfig(config) {
227
227
  };
228
228
  }
229
229
 
230
- const VALID_REASONING_EFFORTS = ['none', 'low', 'medium', 'high', 'xhigh'];
230
+ const VALID_REASONING_EFFORTS = ['none', 'low', 'medium', 'high', 'xhigh', 'ultra'];
231
231
  const VALID_VERBOSITIES = ['low', 'medium'];
232
232
 
233
233
  export function validateReasoningEffort(value) {