@crewx/shared 0.0.6-rc.22 → 0.0.6-rc.24
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 +2 -2
- package/skill-tracer.js +46 -122
- package/tests/skill-tracer.test.ts +65 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewx/shared",
|
|
3
|
-
"version": "0.0.6-rc.
|
|
3
|
+
"version": "0.0.6-rc.24",
|
|
4
4
|
"main": "skill-tracer.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"description": "Shared utilities for CrewX built-in packages",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@crewx/sdk": "0.9.0-rc.
|
|
31
|
+
"@crewx/sdk": "0.9.0-rc.109"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^20.0.0",
|
package/skill-tracer.js
CHANGED
|
@@ -24,9 +24,7 @@ const { generateId } = require('@crewx/sdk');
|
|
|
24
24
|
* crewx.db 경로 찾기
|
|
25
25
|
*/
|
|
26
26
|
function getDbPath() {
|
|
27
|
-
if (process.env.
|
|
28
|
-
return process.env.CREWX_TRACES_DB || process.env.CREWX_DB;
|
|
29
|
-
}
|
|
27
|
+
if (process.env.CREWX_DB) return process.env.CREWX_DB;
|
|
30
28
|
|
|
31
29
|
return path.join(os.homedir(), '.crewx', 'crewx.db');
|
|
32
30
|
}
|
|
@@ -35,141 +33,67 @@ function getDbPath() {
|
|
|
35
33
|
const getTracesDbPath = getDbPath;
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
* crewx.db
|
|
39
|
-
*
|
|
36
|
+
* Open an already initialized crewx.db for trace writes.
|
|
37
|
+
* This function never creates a database, directory, or schema.
|
|
38
|
+
* Returns null if better-sqlite3 is unavailable, the database is missing, or
|
|
39
|
+
* the existing file does not contain the tracing tables used by this module.
|
|
40
40
|
*/
|
|
41
41
|
function getDb() {
|
|
42
42
|
if (!Database) return null;
|
|
43
43
|
|
|
44
44
|
const dbPath = getDbPath();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!fs.existsSync(dir)) {
|
|
48
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
-
}
|
|
45
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
db.exec('PRAGMA journal_mode = WAL');
|
|
53
|
-
db.exec('PRAGMA busy_timeout = 5000');
|
|
54
|
-
db.exec('PRAGMA foreign_keys = ON');
|
|
55
|
-
|
|
56
|
-
db.exec(`
|
|
57
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
58
|
-
id TEXT PRIMARY KEY,
|
|
59
|
-
agent_id TEXT NOT NULL,
|
|
60
|
-
user_id TEXT,
|
|
61
|
-
prompt TEXT NOT NULL,
|
|
62
|
-
mode TEXT NOT NULL DEFAULT 'execute',
|
|
63
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
64
|
-
result TEXT,
|
|
65
|
-
error TEXT,
|
|
66
|
-
started_at TEXT NOT NULL,
|
|
67
|
-
completed_at TEXT,
|
|
68
|
-
duration_ms INTEGER,
|
|
69
|
-
metadata TEXT,
|
|
70
|
-
project_id TEXT,
|
|
71
|
-
project_name TEXT
|
|
72
|
-
)
|
|
73
|
-
`);
|
|
74
|
-
|
|
75
|
-
db.exec(`
|
|
76
|
-
CREATE TABLE IF NOT EXISTS spans (
|
|
77
|
-
id TEXT PRIMARY KEY,
|
|
78
|
-
task_id TEXT,
|
|
79
|
-
parent_span_id TEXT,
|
|
80
|
-
name TEXT NOT NULL,
|
|
81
|
-
kind TEXT NOT NULL DEFAULT 'internal',
|
|
82
|
-
status TEXT NOT NULL DEFAULT 'ok',
|
|
83
|
-
started_at TEXT NOT NULL,
|
|
84
|
-
completed_at TEXT,
|
|
85
|
-
duration_ms INTEGER,
|
|
86
|
-
input TEXT,
|
|
87
|
-
output TEXT,
|
|
88
|
-
error TEXT,
|
|
89
|
-
attributes TEXT,
|
|
90
|
-
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
91
|
-
FOREIGN KEY (parent_span_id) REFERENCES spans(id) ON DELETE SET NULL
|
|
92
|
-
)
|
|
93
|
-
`);
|
|
94
|
-
|
|
95
|
-
ensureProjectColumns(db);
|
|
96
|
-
ensureSpansTaskIdNullable(db);
|
|
97
|
-
|
|
98
|
-
return db;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function ensureProjectColumns(db) {
|
|
47
|
+
let db;
|
|
102
48
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!columns.includes('project_name')) {
|
|
108
|
-
db.exec(`ALTER TABLE tasks ADD COLUMN project_name TEXT`);
|
|
109
|
-
}
|
|
110
|
-
db.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id)`);
|
|
111
|
-
} catch {
|
|
112
|
-
// Best-effort; ignore failures
|
|
113
|
-
}
|
|
114
|
-
}
|
|
49
|
+
db = new Database(dbPath, { fileMustExist: true });
|
|
50
|
+
db.exec('PRAGMA journal_mode = WAL');
|
|
51
|
+
db.exec('PRAGMA busy_timeout = 5000');
|
|
52
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
115
53
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const taskIdColumn = columns.find((col) => col.name === 'task_id');
|
|
120
|
-
if (!taskIdColumn || taskIdColumn.notnull === 0) {
|
|
121
|
-
return;
|
|
54
|
+
if (!hasTraceSchema(db)) {
|
|
55
|
+
db.close();
|
|
56
|
+
return null;
|
|
122
57
|
}
|
|
123
58
|
|
|
124
|
-
db
|
|
125
|
-
|
|
126
|
-
db.exec(`
|
|
127
|
-
CREATE TABLE spans_backup (
|
|
128
|
-
id TEXT PRIMARY KEY,
|
|
129
|
-
task_id TEXT,
|
|
130
|
-
parent_span_id TEXT,
|
|
131
|
-
name TEXT NOT NULL,
|
|
132
|
-
kind TEXT NOT NULL DEFAULT 'internal',
|
|
133
|
-
status TEXT NOT NULL DEFAULT 'ok',
|
|
134
|
-
started_at TEXT NOT NULL,
|
|
135
|
-
completed_at TEXT,
|
|
136
|
-
duration_ms INTEGER,
|
|
137
|
-
input TEXT,
|
|
138
|
-
output TEXT,
|
|
139
|
-
error TEXT,
|
|
140
|
-
attributes TEXT,
|
|
141
|
-
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
142
|
-
FOREIGN KEY (parent_span_id) REFERENCES spans_backup(id) ON DELETE SET NULL
|
|
143
|
-
)
|
|
144
|
-
`);
|
|
145
|
-
db.exec(`
|
|
146
|
-
INSERT INTO spans_backup (
|
|
147
|
-
id, task_id, parent_span_id, name, kind, status, started_at,
|
|
148
|
-
completed_at, duration_ms, input, output, error, attributes
|
|
149
|
-
)
|
|
150
|
-
SELECT
|
|
151
|
-
id, task_id, parent_span_id, name, kind, status, started_at,
|
|
152
|
-
completed_at, duration_ms, input, output, error, attributes
|
|
153
|
-
FROM spans
|
|
154
|
-
`);
|
|
155
|
-
db.exec('DROP TABLE spans');
|
|
156
|
-
db.exec('ALTER TABLE spans_backup RENAME TO spans');
|
|
157
|
-
db.exec('COMMIT');
|
|
158
|
-
db.exec('PRAGMA foreign_keys = ON');
|
|
159
|
-
} catch {
|
|
59
|
+
return db;
|
|
60
|
+
} catch (error) {
|
|
160
61
|
try {
|
|
161
|
-
db
|
|
62
|
+
db?.close();
|
|
162
63
|
} catch {
|
|
163
|
-
// Best-effort
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
db.exec('PRAGMA foreign_keys = ON');
|
|
167
|
-
} catch {
|
|
168
|
-
// Best-effort; ignore failures
|
|
64
|
+
// Best-effort cleanup after an open or pragma failure.
|
|
169
65
|
}
|
|
66
|
+
throw error;
|
|
170
67
|
}
|
|
171
68
|
}
|
|
172
69
|
|
|
70
|
+
function hasTraceSchema(db) {
|
|
71
|
+
const requiredColumns = {
|
|
72
|
+
tasks: ['id'],
|
|
73
|
+
spans: [
|
|
74
|
+
'id',
|
|
75
|
+
'task_id',
|
|
76
|
+
'name',
|
|
77
|
+
'kind',
|
|
78
|
+
'status',
|
|
79
|
+
'started_at',
|
|
80
|
+
'completed_at',
|
|
81
|
+
'duration_ms',
|
|
82
|
+
'input',
|
|
83
|
+
'output',
|
|
84
|
+
'error',
|
|
85
|
+
'attributes',
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return Object.entries(requiredColumns).every(([tableName, columns]) => {
|
|
90
|
+
const availableColumns = new Set(
|
|
91
|
+
db.prepare(`PRAGMA table_info(${tableName})`).all().map((column) => column.name),
|
|
92
|
+
);
|
|
93
|
+
return columns.every((column) => availableColumns.has(column));
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
173
97
|
function resolveProjectContext() {
|
|
174
98
|
const projectPath = path.resolve(process.cwd());
|
|
175
99
|
return {
|
|
@@ -6,6 +6,36 @@ import Database from 'better-sqlite3';
|
|
|
6
6
|
|
|
7
7
|
const SPAN_ID_PATTERN = /^spn_[A-Za-z0-9]{8}$/;
|
|
8
8
|
|
|
9
|
+
function createTracerDb(dbPath: string): void {
|
|
10
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
11
|
+
const db = new Database(dbPath);
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE tasks (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
agent_id TEXT,
|
|
16
|
+
prompt TEXT,
|
|
17
|
+
status TEXT,
|
|
18
|
+
started_at TEXT
|
|
19
|
+
);
|
|
20
|
+
CREATE TABLE spans (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
task_id TEXT,
|
|
23
|
+
parent_span_id TEXT,
|
|
24
|
+
name TEXT NOT NULL,
|
|
25
|
+
kind TEXT NOT NULL DEFAULT 'internal',
|
|
26
|
+
status TEXT NOT NULL DEFAULT 'ok',
|
|
27
|
+
started_at TEXT NOT NULL,
|
|
28
|
+
completed_at TEXT,
|
|
29
|
+
duration_ms INTEGER,
|
|
30
|
+
input TEXT,
|
|
31
|
+
output TEXT,
|
|
32
|
+
error TEXT,
|
|
33
|
+
attributes TEXT
|
|
34
|
+
);
|
|
35
|
+
`);
|
|
36
|
+
db.close();
|
|
37
|
+
}
|
|
38
|
+
|
|
9
39
|
describe('shared/skill-tracer', () => {
|
|
10
40
|
const sharedDir = path.resolve(__dirname, '..');
|
|
11
41
|
|
|
@@ -48,6 +78,7 @@ describe('shared/skill-tracer', () => {
|
|
|
48
78
|
|
|
49
79
|
process.env.CREWX_DB = dbPath;
|
|
50
80
|
delete process.env.CREWX_TASK_ID;
|
|
81
|
+
createTracerDb(dbPath);
|
|
51
82
|
|
|
52
83
|
try {
|
|
53
84
|
const trace = tracer.trace('memory', 'index core_sqa');
|
|
@@ -69,6 +100,39 @@ describe('shared/skill-tracer', () => {
|
|
|
69
100
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
70
101
|
}
|
|
71
102
|
});
|
|
103
|
+
|
|
104
|
+
it('does not create a missing target database and no-ops without throwing', () => {
|
|
105
|
+
const tracer = require(path.join(sharedDir, 'skill-tracer.js'));
|
|
106
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-tracer-missing-'));
|
|
107
|
+
const dbPath = path.join(tempDir, 'nested', 'crewx.db');
|
|
108
|
+
const previousDbPath = process.env.CREWX_DB;
|
|
109
|
+
const previousTaskId = process.env.CREWX_TASK_ID;
|
|
110
|
+
|
|
111
|
+
process.env.CREWX_DB = dbPath;
|
|
112
|
+
delete process.env.CREWX_TASK_ID;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
expect(() => {
|
|
116
|
+
const trace = tracer.trace('memory', 'index core_sqa');
|
|
117
|
+
expect(trace._skipped).toBe(true);
|
|
118
|
+
trace.ok('ignored');
|
|
119
|
+
}).not.toThrow();
|
|
120
|
+
expect(fs.existsSync(dbPath)).toBe(false);
|
|
121
|
+
expect(fs.existsSync(path.dirname(dbPath))).toBe(false);
|
|
122
|
+
} finally {
|
|
123
|
+
if (previousDbPath === undefined) {
|
|
124
|
+
delete process.env.CREWX_DB;
|
|
125
|
+
} else {
|
|
126
|
+
process.env.CREWX_DB = previousDbPath;
|
|
127
|
+
}
|
|
128
|
+
if (previousTaskId === undefined) {
|
|
129
|
+
delete process.env.CREWX_TASK_ID;
|
|
130
|
+
} else {
|
|
131
|
+
process.env.CREWX_TASK_ID = previousTaskId;
|
|
132
|
+
}
|
|
133
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
72
136
|
});
|
|
73
137
|
|
|
74
138
|
describe('shared/skill-tracer output capture', () => {
|
|
@@ -85,6 +149,7 @@ describe('shared/skill-tracer output capture', () => {
|
|
|
85
149
|
prevTaskId = process.env.CREWX_TASK_ID;
|
|
86
150
|
process.env.CREWX_DB = dbPath;
|
|
87
151
|
delete process.env.CREWX_TASK_ID;
|
|
152
|
+
createTracerDb(dbPath);
|
|
88
153
|
});
|
|
89
154
|
|
|
90
155
|
afterEach(() => {
|