discoclaw 1.2.0 → 1.2.1

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/README.md CHANGED
@@ -77,6 +77,14 @@ Voice is **off by default**. Enable with `DISCOCLAW_VOICE_ENABLED=1` plus API ke
77
77
 
78
78
  Full setup guide: [docs/voice.md](docs/voice.md)
79
79
 
80
+ ## Self-management — the bot maintains itself
81
+
82
+ - **Self-update** — `!update` checks for new npm versions; `!update apply` downloads, installs, and restarts without leaving Discord
83
+ - **Health checks** — `!health`, `!doctor`, `!status` for diagnostics
84
+ - **Secret management** — `!secret` manages `.env` entries from DMs
85
+ - **Model switching** — `!models` swaps AI models per role at runtime
86
+ - **Restart** — `!restart` restarts the service on demand
87
+
80
88
  ## How it works
81
89
 
82
90
  DiscoClaw orchestrates the flow between Discord and AI runtimes (Claude Code by default, with `gemini-api`, OpenAI, Codex, and OpenRouter adapters available via `PRIMARY_RUNTIME`). For 1.0, `Claude CLI` on a source checkout is the explicitly supported default path, and `Codex CLI` on a source checkout is the explicitly supported secondary path. See [docs/audit/provider-auth-1.0-matrix.md](docs/audit/provider-auth-1.0-matrix.md) for the full consolidated matrix. The OpenAI-compatible and OpenRouter adapters can expose optional tool use when `OPENAI_COMPAT_TOOLS_ENABLED=1` is set, but OpenRouter support claims stop at the narrower audited boundary described below. It doesn't contain intelligence itself — it decides *when* to call the AI, *what context* to give it, and *what to do* with the output. When you send a message, the orchestrator:
@@ -406,15 +414,22 @@ Do not treat `codex --version`, `OPENAI_API_KEY` presence, `pnpm preflight*`, or
406
414
 
407
415
  ## Updating
408
416
 
409
- **Global install:**
417
+ DiscoClaw can check for and apply updates from inside Discord — no SSH or terminal needed.
418
+
419
+ | Command | Description |
420
+ |---------|-------------|
421
+ | `!update` | Check if a newer version is available on npm |
422
+ | `!update apply` | Download the update, reinstall, and restart the service |
423
+ | `!update audit` | Show npm-managed runtime audit details |
424
+ | `!update help` | Show usage |
410
425
 
411
- If DiscoClaw is running, update from Discord:
426
+ **Global install (from Discord):**
412
427
 
413
428
  ```
414
429
  !update apply
415
430
  ```
416
431
 
417
- Or from the command line:
432
+ **Global install (from the command line):**
418
433
 
419
434
  ```bash
420
435
  npm update -g discoclaw
@@ -72,7 +72,10 @@ export class ToolAwareQueue {
72
72
  switch (this.state) {
73
73
  case 'idle':
74
74
  case 'buffering_text':
75
- // Discard buffered narration text.
75
+ // Flush buffered narration text before switching to tool_active.
76
+ if (this.buffer) {
77
+ this.emit({ type: 'stream_text', text: this.buffer });
78
+ }
76
79
  this.buffer = '';
77
80
  this.state = 'tool_active';
78
81
  this.emit({ type: 'show_activity', label });
@@ -96,15 +96,16 @@ describe('ToolAwareQueue', () => {
96
96
  expect(actions[1]).toEqual({ type: 'stream_text', text: '!' });
97
97
  taq.dispose();
98
98
  });
99
- it('text then tool: narration discarded, activity shown', () => {
99
+ it('text then tool: narration flushed as stream_text, then activity shown', () => {
100
100
  const { actions, emit } = collect();
101
101
  const taq = new ToolAwareQueue(emit, { flushDelayMs: 800, postToolDelayMs: 500 });
102
102
  taq.handleEvent({ type: 'text_delta', text: 'Let me read the file...' });
103
103
  taq.handleEvent({ type: 'tool_start', name: 'Read', input: { file_path: '/tmp/foo.ts' } });
104
- // Narration was discarded, only show_activity emitted.
105
- expect(actions).toHaveLength(1);
106
- expect(actions[0]).toMatchObject({ type: 'show_activity' });
107
- expect(actions[0].label).toContain('Reading');
104
+ // Narration flushed as stream_text, then show_activity emitted.
105
+ expect(actions).toHaveLength(2);
106
+ expect(actions[0]).toEqual({ type: 'stream_text', text: 'Let me read the file...' });
107
+ expect(actions[1]).toMatchObject({ type: 'show_activity' });
108
+ expect(actions[1].label).toContain('Reading');
108
109
  taq.dispose();
109
110
  });
110
111
  it('tool then text: activity during tool, text streams after', () => {
@@ -176,7 +177,7 @@ describe('ToolAwareQueue', () => {
176
177
  vi.advanceTimersByTime(5000);
177
178
  expect(actions).toHaveLength(0);
178
179
  });
179
- it('post-tool delay prevents flashing narration between tools', () => {
180
+ it('inter-tool narration is flushed before second tool activity', () => {
180
181
  const { actions, emit } = collect();
181
182
  const taq = new ToolAwareQueue(emit, { flushDelayMs: 800, postToolDelayMs: 500 });
182
183
  taq.handleEvent({ type: 'tool_start', name: 'Read' });
@@ -186,8 +187,12 @@ describe('ToolAwareQueue', () => {
186
187
  // Before post-tool delay fires, second tool starts.
187
188
  vi.advanceTimersByTime(200);
188
189
  taq.handleEvent({ type: 'tool_start', name: 'Bash' });
189
- // The narration text should not have been streamed.
190
- expect(actions.filter((a) => a.type === 'stream_text')).toHaveLength(0);
190
+ // Inter-tool narration flushed as stream_text before second tool.
191
+ expect(actions.filter((a) => a.type === 'stream_text')).toHaveLength(1);
192
+ expect(actions.filter((a) => a.type === 'stream_text')[0]).toEqual({
193
+ type: 'stream_text',
194
+ text: 'Now let me run...',
195
+ });
191
196
  // Two show_activity actions.
192
197
  expect(actions.filter((a) => a.type === 'show_activity')).toHaveLength(2);
193
198
  taq.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discoclaw",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Personal AI orchestrator that turns Discord into a persistent workspace",
5
5
  "license": "MIT",
6
6
  "repository": {