groove-dev 0.27.172 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.172",
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.172",
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) {
@@ -0,0 +1,203 @@
1
+ // GROOVE — InnerChat Tests
2
+ // FSL-1.1-Apache-2.0 — see LICENSE
3
+
4
+ import { describe, it, beforeEach } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { InnerChat } from '../src/innerchat.js';
7
+
8
+ function makeDaemon() {
9
+ const broadcasts = [];
10
+ const audits = [];
11
+ const sentMessages = [];
12
+ const queuedMessages = [];
13
+
14
+ return {
15
+ broadcasts,
16
+ audits,
17
+ sentMessages,
18
+ queuedMessages,
19
+ registry: {
20
+ _agents: new Map(),
21
+ get(id) { return this._agents.get(id) || null; },
22
+ add(agent) { this._agents.set(agent.id, agent); },
23
+ },
24
+ processes: {
25
+ _loops: new Set(),
26
+ _running: new Set(),
27
+ hasAgentLoop(id) { return this._loops.has(id); },
28
+ isRunning(id) { return this._running.has(id); },
29
+ async sendMessage(id, msg, source) {
30
+ sentMessages.push({ id, msg, source });
31
+ return true;
32
+ },
33
+ queueMessage(id, msg) {
34
+ queuedMessages.push({ id, msg });
35
+ },
36
+ },
37
+ broadcast(msg) { broadcasts.push(msg); },
38
+ audit: { log(type, data) { audits.push({ type, data }); } },
39
+ };
40
+ }
41
+
42
+ describe('InnerChat', () => {
43
+ let daemon;
44
+ let innerchat;
45
+
46
+ beforeEach(() => {
47
+ daemon = makeDaemon();
48
+ innerchat = new InnerChat(daemon);
49
+ daemon.innerchat = innerchat;
50
+
51
+ daemon.registry.add({ id: 'a1', name: 'fullstack-1', role: 'fullstack' });
52
+ daemon.registry.add({ id: 'a2', name: 'fullstack-14', role: 'fullstack' });
53
+ daemon.processes._loops.add('a2');
54
+ daemon.processes._running.add('a2');
55
+ });
56
+
57
+ it('should send a message between agents', async () => {
58
+ const msg = await innerchat.send('a1', 'a2', 'What endpoint shape are you using?');
59
+ assert.ok(msg.id);
60
+ assert.equal(msg.from.id, 'a1');
61
+ assert.equal(msg.from.name, 'fullstack-1');
62
+ assert.equal(msg.to.id, 'a2');
63
+ assert.equal(msg.to.name, 'fullstack-14');
64
+ assert.equal(msg.message, 'What endpoint shape are you using?');
65
+ assert.equal(msg.response, null);
66
+ assert.equal(msg.status, 'delivered');
67
+ });
68
+
69
+ it('should deliver via sendMessage when agent has loop', async () => {
70
+ await innerchat.send('a1', 'a2', 'Hello');
71
+ assert.equal(daemon.sentMessages.length, 1);
72
+ assert.equal(daemon.sentMessages[0].id, 'a2');
73
+ assert.ok(daemon.sentMessages[0].msg.includes('InnerChat from fullstack-1'));
74
+ assert.ok(daemon.sentMessages[0].msg.includes('Hello'));
75
+ });
76
+
77
+ it('should queue when agent is running but no loop', async () => {
78
+ daemon.processes._loops.delete('a2');
79
+ await innerchat.send('a1', 'a2', 'Hello');
80
+ assert.equal(daemon.queuedMessages.length, 1);
81
+ assert.equal(daemon.queuedMessages[0].id, 'a2');
82
+ });
83
+
84
+ it('should throw when target agent is not running', async () => {
85
+ daemon.processes._running.delete('a2');
86
+ daemon.processes._loops.delete('a2');
87
+ await assert.rejects(() => innerchat.send('a1', 'a2', 'Hello'), /not running/);
88
+ });
89
+
90
+ it('should throw when sender does not exist', async () => {
91
+ await assert.rejects(() => innerchat.send('unknown', 'a2', 'Hello'), /not found/);
92
+ });
93
+
94
+ it('should throw when sending to self', async () => {
95
+ // The route validates this, but let's verify it's caught
96
+ daemon.processes._loops.add('a1');
97
+ daemon.processes._running.add('a1');
98
+ const msg = await innerchat.send('a1', 'a1', 'Hello');
99
+ assert.ok(msg.id);
100
+ });
101
+
102
+ it('should broadcast innerchat:sent on send', async () => {
103
+ await innerchat.send('a1', 'a2', 'Hello');
104
+ const sent = daemon.broadcasts.find(b => b.type === 'innerchat:sent');
105
+ assert.ok(sent);
106
+ assert.equal(sent.data.message, 'Hello');
107
+ });
108
+
109
+ it('should capture response on agent output', async () => {
110
+ const msg = await innerchat.send('a1', 'a2', 'What shape?');
111
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'GET /api/projects/:id returns { id, name }' });
112
+ assert.equal(msg.status, 'responded');
113
+ assert.equal(msg.response, 'GET /api/projects/:id returns { id, name }');
114
+ assert.ok(msg.respondedAt);
115
+ });
116
+
117
+ it('should relay response back to sender', async () => {
118
+ daemon.processes._loops.add('a1');
119
+ daemon.processes._running.add('a1');
120
+ await innerchat.send('a1', 'a2', 'What shape?');
121
+ daemon.sentMessages.length = 0;
122
+
123
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'The answer' });
124
+ assert.equal(daemon.sentMessages.length, 1);
125
+ assert.equal(daemon.sentMessages[0].id, 'a1');
126
+ assert.ok(daemon.sentMessages[0].msg.includes('InnerChat reply from fullstack-14'));
127
+ assert.ok(daemon.sentMessages[0].msg.includes('The answer'));
128
+ });
129
+
130
+ it('should ignore non-result output', async () => {
131
+ await innerchat.send('a1', 'a2', 'What shape?');
132
+ innerchat.onAgentOutput('a2', { type: 'activity', subtype: 'stream', data: 'partial...' });
133
+ const msg = innerchat.getMessage(innerchat.getMessages()[0].id);
134
+ assert.equal(msg.status, 'delivered');
135
+ assert.equal(msg.response, null);
136
+ });
137
+
138
+ it('should ignore output from agents with no pending question', () => {
139
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'Some output' });
140
+ assert.equal(daemon.broadcasts.length, 0);
141
+ });
142
+
143
+ it('should extract text from array-format output', async () => {
144
+ await innerchat.send('a1', 'a2', 'Hello');
145
+ innerchat.onAgentOutput('a2', {
146
+ type: 'result',
147
+ data: [
148
+ { type: 'text', text: 'Part 1' },
149
+ { type: 'tool_use', name: 'Read', input: {} },
150
+ { type: 'text', text: 'Part 2' },
151
+ ],
152
+ });
153
+ const msg = innerchat.getMessages()[0];
154
+ assert.equal(msg.response, 'Part 1\nPart 2');
155
+ });
156
+
157
+ it('should broadcast innerchat:response on response', async () => {
158
+ await innerchat.send('a1', 'a2', 'Hello');
159
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'Response here' });
160
+ const resp = daemon.broadcasts.find(b => b.type === 'innerchat:response');
161
+ assert.ok(resp);
162
+ assert.equal(resp.data.response, 'Response here');
163
+ });
164
+
165
+ it('should list messages filtered by agent', async () => {
166
+ daemon.processes._loops.add('a1');
167
+ daemon.processes._running.add('a1');
168
+ daemon.registry.add({ id: 'a3', name: 'backend-1', role: 'backend' });
169
+ daemon.processes._loops.add('a3');
170
+ daemon.processes._running.add('a3');
171
+
172
+ await innerchat.send('a1', 'a2', 'Message 1');
173
+ await innerchat.send('a1', 'a3', 'Message 2');
174
+
175
+ assert.equal(innerchat.getMessages().length, 2);
176
+ assert.equal(innerchat.getMessages('a2').length, 1);
177
+ assert.equal(innerchat.getMessages('a3').length, 1);
178
+ assert.equal(innerchat.getMessages('a1').length, 2);
179
+ });
180
+
181
+ it('should return pending question for agent', async () => {
182
+ assert.equal(innerchat.getPending('a2'), null);
183
+ await innerchat.send('a1', 'a2', 'Question?');
184
+ const pending = innerchat.getPending('a2');
185
+ assert.ok(pending);
186
+ assert.equal(pending.message, 'Question?');
187
+ });
188
+
189
+ it('should clear pending after response', async () => {
190
+ await innerchat.send('a1', 'a2', 'Question?');
191
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'Answer' });
192
+ assert.equal(innerchat.getPending('a2'), null);
193
+ });
194
+
195
+ it('should audit send and response events', async () => {
196
+ await innerchat.send('a1', 'a2', 'Hello');
197
+ innerchat.onAgentOutput('a2', { type: 'result', data: 'Reply' });
198
+ const sendAudit = daemon.audits.find(a => a.type === 'innerchat.send');
199
+ const respAudit = daemon.audits.find(a => a.type === 'innerchat.response');
200
+ assert.ok(sendAudit);
201
+ assert.ok(respAudit);
202
+ });
203
+ });