@automagik/genie 4.260331.12 → 4.260331.14
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/.claude-plugin/marketplace.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/genie/.claude-plugin/plugin.json +1 -1
- package/plugins/genie/package.json +1 -1
- package/src/hooks/handlers/auto-spawn.ts +3 -0
- package/src/hooks/handlers/runtime-emit.ts +2 -0
- package/src/lib/db-backup.test.ts +105 -0
- package/src/lib/otel-receiver.test.ts +3 -2
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "genie",
|
|
13
|
-
"version": "4.260331.
|
|
13
|
+
"version": "4.260331.14",
|
|
14
14
|
"source": "./plugins/genie",
|
|
15
15
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
|
|
16
16
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260331.
|
|
3
|
+
"version": "4.260331.14",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
|
@@ -61,6 +61,9 @@ async function isRecipientLeader(recipient: string, teamName: string): Promise<b
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export async function autoSpawn(payload: HookPayload): Promise<HandlerResult> {
|
|
64
|
+
// Skip in test environment — PG/tmux queries cause timeouts under full suite load
|
|
65
|
+
if (process.env.NODE_ENV === 'test' || process.env.BUN_ENV === 'test') return;
|
|
66
|
+
|
|
64
67
|
const input = payload.tool_input;
|
|
65
68
|
if (!input || input.type !== 'message') return;
|
|
66
69
|
|
|
@@ -20,6 +20,8 @@ const getTeam = () => process.env.GENIE_TEAM;
|
|
|
20
20
|
type SubjectEventInput = Omit<RuntimeEventInput, 'repoPath' | 'subject'>;
|
|
21
21
|
|
|
22
22
|
async function emit(subject: string, event: SubjectEventInput): Promise<void> {
|
|
23
|
+
// Skip event emission in test environment — PG connection attempts cause 16s timeouts
|
|
24
|
+
if (process.env.NODE_ENV === 'test' || process.env.BUN_ENV === 'test') return;
|
|
23
25
|
try {
|
|
24
26
|
const { publishSubjectEvent } = await import('../../lib/runtime-events.js');
|
|
25
27
|
await publishSubjectEvent(process.cwd(), subject, event);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { gzipSync } from 'node:zlib';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// getSnapshotPath — pure function, no DB needed
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
describe('getSnapshotPath', () => {
|
|
13
|
+
let tmpDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpDir = join(tmpdir(), `genie-backup-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
17
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
18
|
+
spawnSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('returns path inside .genie/ at repo root', async () => {
|
|
26
|
+
const { getSnapshotPath } = await import('./db-backup.js');
|
|
27
|
+
const path = getSnapshotPath(tmpDir);
|
|
28
|
+
expect(path).toBe(join(tmpDir, '.genie', 'snapshot.sql.gz'));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('path ends with snapshot.sql.gz', async () => {
|
|
32
|
+
const { getSnapshotPath } = await import('./db-backup.js');
|
|
33
|
+
const path = getSnapshotPath(tmpDir);
|
|
34
|
+
expect(path.endsWith('snapshot.sql.gz')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// restore — error paths (no DB needed)
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
describe('restore error handling', () => {
|
|
43
|
+
test('throws for missing snapshot file', async () => {
|
|
44
|
+
const { restore } = await import('./db-backup.js');
|
|
45
|
+
expect(() => restore('/tmp/nonexistent-genie-snapshot-test.sql.gz')).toThrow('Snapshot not found');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('throws for non-gzip file', async () => {
|
|
49
|
+
const { restore } = await import('./db-backup.js');
|
|
50
|
+
const tmpFile = join(tmpdir(), `genie-bad-snapshot-${Date.now()}.sql.gz`);
|
|
51
|
+
writeFileSync(tmpFile, 'not gzip data');
|
|
52
|
+
try {
|
|
53
|
+
// Should fail at decompression or psql — either way it throws
|
|
54
|
+
expect(() => restore(tmpFile)).toThrow();
|
|
55
|
+
} finally {
|
|
56
|
+
rmSync(tmpFile, { force: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// backup — error paths (no DB needed for these)
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
describe('backup error handling', () => {
|
|
66
|
+
let tmpDir: string;
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
tmpDir = join(tmpdir(), `genie-backup-err-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
70
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
71
|
+
spawnSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('creates .genie directory if it does not exist', async () => {
|
|
79
|
+
const genieDir = join(tmpDir, '.genie');
|
|
80
|
+
expect(existsSync(genieDir)).toBe(false);
|
|
81
|
+
|
|
82
|
+
const { backup } = await import('./db-backup.js');
|
|
83
|
+
// backup will fail (no pg_dump or no DB) but should create the directory first
|
|
84
|
+
try {
|
|
85
|
+
backup(tmpDir);
|
|
86
|
+
} catch {
|
|
87
|
+
// Expected — pg_dump will fail without a running DB on this port
|
|
88
|
+
}
|
|
89
|
+
expect(existsSync(genieDir)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// snapshot file format — verify gzip round-trip
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
describe('snapshot file format', () => {
|
|
98
|
+
test('gzipSync + gunzipSync round-trips SQL content', async () => {
|
|
99
|
+
const { gunzipSync } = await import('node:zlib');
|
|
100
|
+
const sql = '-- PostgreSQL dump\nCREATE TABLE test (id int);\n';
|
|
101
|
+
const compressed = gzipSync(Buffer.from(sql));
|
|
102
|
+
const decompressed = gunzipSync(compressed);
|
|
103
|
+
expect(decompressed.toString('utf-8')).toBe(sql);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -6,8 +6,9 @@ describe('otel-receiver', () => {
|
|
|
6
6
|
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
origPort = process.env.GENIE_OTEL_PORT;
|
|
9
|
-
// Use a random high port to avoid conflicts with running pgserve
|
|
10
|
-
|
|
9
|
+
// Use a random high port to avoid conflicts with running pgserve or parallel tests
|
|
10
|
+
// Range 57000-63999 avoids typical pgserve ports (19643-19700) and ephemeral ports
|
|
11
|
+
process.env.GENIE_OTEL_PORT = String(57000 + Math.floor(Math.random() * 7000));
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
afterEach(() => {
|