@chainingintention/pi-web-cn 1.202608.6 → 1.202608.8
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/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.js +69 -0
- package/dist/config.js.map +1 -1
- package/dist/server/app.js +4 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/audit/managementAuditStore.js +211 -0
- package/dist/server/audit/managementAuditStore.js.map +1 -0
- package/dist/server/audit/normalToolAuditCli.js +143 -0
- package/dist/server/audit/normalToolAuditCli.js.map +1 -0
- package/dist/server/audit/normalToolAuditStore.js +264 -0
- package/dist/server/audit/normalToolAuditStore.js.map +1 -0
- package/dist/server/managementEmbed.js +19 -1
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/requestLogging.js +10 -7
- package/dist/server/requestLogging.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +13 -7
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond.js +90 -4
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/managementPermissionSystem.js +8 -8
- package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +133 -19
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +6 -2
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/workbench/accessStateRoutes.js +30 -0
- package/dist/server/workbench/accessStateRoutes.js.map +1 -0
- package/dist/server/workbench/accessStateStore.js +29 -0
- package/dist/server/workbench/accessStateStore.js.map +1 -0
- package/dist/server/workbench/capabilitySearch.js +62 -0
- package/dist/server/workbench/capabilitySearch.js.map +1 -0
- package/dist/server/workbench/gatewayIntegration.js +56 -0
- package/dist/server/workbench/gatewayIntegration.js.map +1 -0
- package/dist/server/workbench/mcpClient.js +148 -0
- package/dist/server/workbench/mcpClient.js.map +1 -0
- package/dist/server/workbench/skillSync.js +375 -0
- package/dist/server/workbench/skillSync.js.map +1 -0
- package/dist/server/workbench/types.js +2 -0
- package/dist/server/workbench/types.js.map +1 -0
- package/dist/server/workbench/workbenchClient.js +136 -0
- package/dist/server/workbench/workbenchClient.js.map +1 -0
- package/dist/server/workbench/workbenchTools.js +145 -0
- package/dist/server/workbench/workbenchTools.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +25 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/piWebConfigParsing.js +94 -0
- package/dist/shared/piWebConfigParsing.js.map +1 -1
- package/docs/config.md +65 -0
- package/package.json +7 -3
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
3
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 1_000;
|
|
4
|
+
const DEFAULT_MAINTENANCE_INTERVAL_MS = DAY_MS;
|
|
5
|
+
const DEFAULT_MAX_QUEUE_SIZE = 10_000;
|
|
6
|
+
const TEMPLATE_VERSION = 1;
|
|
7
|
+
export class ManagementAuditStore {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.queue = [];
|
|
11
|
+
this.closed = false;
|
|
12
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
13
|
+
this.now = options.now ?? (() => new Date());
|
|
14
|
+
this.flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
15
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
16
|
+
}
|
|
17
|
+
async initialize() {
|
|
18
|
+
const interval = this.options.maintenanceIntervalMs ?? DEFAULT_MAINTENANCE_INTERVAL_MS;
|
|
19
|
+
if (interval > 0) {
|
|
20
|
+
this.maintenanceTimer = setInterval(() => {
|
|
21
|
+
void this.runMaintenanceCycle().catch((error) => { this.options.onError?.(error); });
|
|
22
|
+
}, interval);
|
|
23
|
+
this.maintenanceTimer.unref();
|
|
24
|
+
}
|
|
25
|
+
await this.runMaintenanceCycle();
|
|
26
|
+
}
|
|
27
|
+
record(event) {
|
|
28
|
+
if (this.closed)
|
|
29
|
+
return;
|
|
30
|
+
const timestamp = this.now();
|
|
31
|
+
const id = randomUUID();
|
|
32
|
+
this.queue.push({
|
|
33
|
+
id,
|
|
34
|
+
index: managementAuditIndexName(this.options.indexPrefix, timestamp),
|
|
35
|
+
document: managementAuditDocument(id, timestamp, event),
|
|
36
|
+
});
|
|
37
|
+
if (this.queue.length > this.maxQueueSize) {
|
|
38
|
+
this.queue.splice(0, this.queue.length - this.maxQueueSize);
|
|
39
|
+
this.options.onError?.(new Error("management audit queue reached its size limit; oldest records were dropped"));
|
|
40
|
+
}
|
|
41
|
+
this.scheduleFlush();
|
|
42
|
+
}
|
|
43
|
+
flush() {
|
|
44
|
+
if (this.flushPromise !== undefined)
|
|
45
|
+
return this.flushPromise;
|
|
46
|
+
this.flushPromise = this.flushQueued().finally(() => {
|
|
47
|
+
this.flushPromise = undefined;
|
|
48
|
+
if (this.queue.length > 0 && !this.closed)
|
|
49
|
+
this.scheduleFlush();
|
|
50
|
+
});
|
|
51
|
+
return this.flushPromise;
|
|
52
|
+
}
|
|
53
|
+
async maintain() {
|
|
54
|
+
const now = this.now();
|
|
55
|
+
const month = shanghaiMonth(now);
|
|
56
|
+
if (this.maintenanceMonth === month)
|
|
57
|
+
return;
|
|
58
|
+
const cutoff = new Date(now.getTime() - this.options.retentionDays * DAY_MS);
|
|
59
|
+
const pattern = encodeURIComponent(`${this.options.indexPrefix}-*`);
|
|
60
|
+
await this.request(`${pattern}/_delete_by_query?conflicts=proceed&refresh=false`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
body: JSON.stringify({ query: { range: { "@timestamp": { lt: cutoff.toISOString() } } } }),
|
|
63
|
+
headers: { "content-type": "application/json" },
|
|
64
|
+
}, [200, 404]);
|
|
65
|
+
this.maintenanceMonth = month;
|
|
66
|
+
}
|
|
67
|
+
async close() {
|
|
68
|
+
this.closed = true;
|
|
69
|
+
if (this.flushTimer !== undefined)
|
|
70
|
+
clearTimeout(this.flushTimer);
|
|
71
|
+
if (this.maintenanceTimer !== undefined)
|
|
72
|
+
clearInterval(this.maintenanceTimer);
|
|
73
|
+
do {
|
|
74
|
+
await this.flush();
|
|
75
|
+
} while (this.queue.length > 0);
|
|
76
|
+
}
|
|
77
|
+
scheduleFlush() {
|
|
78
|
+
if (this.flushTimer !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
this.flushTimer = setTimeout(() => {
|
|
81
|
+
this.flushTimer = undefined;
|
|
82
|
+
void this.flush().catch((error) => { this.options.onError?.(error); });
|
|
83
|
+
}, this.flushIntervalMs);
|
|
84
|
+
this.flushTimer.unref();
|
|
85
|
+
}
|
|
86
|
+
async flushQueued() {
|
|
87
|
+
if (this.queue.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
90
|
+
const body = `${batch.flatMap((entry) => [
|
|
91
|
+
JSON.stringify({ index: { _index: entry.index, _id: entry.id } }),
|
|
92
|
+
JSON.stringify(entry.document),
|
|
93
|
+
]).join("\n")}\n`;
|
|
94
|
+
try {
|
|
95
|
+
const response = await this.request("_bulk", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body,
|
|
98
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
99
|
+
});
|
|
100
|
+
const result = await response.json();
|
|
101
|
+
if (isRecord(result) && result["errors"] === true)
|
|
102
|
+
throw new Error("Elasticsearch rejected one or more management audit records");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
this.queue.unshift(...batch);
|
|
106
|
+
if (this.queue.length > this.maxQueueSize)
|
|
107
|
+
this.queue.length = this.maxQueueSize;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async installIndexTemplate() {
|
|
112
|
+
const name = encodeURIComponent(`${this.options.indexPrefix}-template`);
|
|
113
|
+
await this.request(`_index_template/${name}`, {
|
|
114
|
+
method: "PUT",
|
|
115
|
+
headers: { "content-type": "application/json" },
|
|
116
|
+
body: JSON.stringify({
|
|
117
|
+
index_patterns: [`${this.options.indexPrefix}-*`],
|
|
118
|
+
priority: 200,
|
|
119
|
+
version: TEMPLATE_VERSION,
|
|
120
|
+
template: {
|
|
121
|
+
settings: { number_of_shards: 1 },
|
|
122
|
+
mappings: {
|
|
123
|
+
dynamic: false,
|
|
124
|
+
properties: {
|
|
125
|
+
"@timestamp": { type: "date" },
|
|
126
|
+
event: { properties: { id: { type: "keyword" }, action: { type: "keyword" }, status: { type: "keyword" }, duration_ms: { type: "long" } } },
|
|
127
|
+
user: { properties: { id: { type: "keyword" }, root_user_id: { type: "keyword" } } },
|
|
128
|
+
project: { properties: { id: { type: "keyword" } } },
|
|
129
|
+
session: { properties: { id: { type: "keyword" }, agent_session_id: { type: "keyword" } } },
|
|
130
|
+
workspace: { properties: { cwd: { type: "keyword", ignore_above: 4096 } } },
|
|
131
|
+
tool: { properties: { name: { type: "keyword" }, call_id: { type: "keyword" } } },
|
|
132
|
+
workbench: { properties: {
|
|
133
|
+
authorization_revision: { type: "integer" }, capability_name: { type: "keyword" }, capability_version: { type: "keyword" },
|
|
134
|
+
skill_name: { type: "keyword" }, skill_version: { type: "keyword" }, run_id: { type: "keyword" }, trace_id: { type: "keyword" },
|
|
135
|
+
mcp_trace_id: { type: "keyword" }, status_code: { type: "keyword" }, retry_count: { type: "integer" },
|
|
136
|
+
} },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async runMaintenanceCycle() {
|
|
144
|
+
await this.installIndexTemplate();
|
|
145
|
+
await this.maintain();
|
|
146
|
+
}
|
|
147
|
+
async request(path, init, acceptedStatuses = [200]) {
|
|
148
|
+
const headers = new Headers(init.headers);
|
|
149
|
+
if (this.options.apiKey !== undefined)
|
|
150
|
+
headers.set("authorization", `ApiKey ${this.options.apiKey}`);
|
|
151
|
+
else if (this.options.username !== undefined && this.options.password !== undefined) {
|
|
152
|
+
headers.set("authorization", `Basic ${Buffer.from(`${this.options.username}:${this.options.password}`).toString("base64")}`);
|
|
153
|
+
}
|
|
154
|
+
const response = await this.fetchImpl(new URL(path, trailingSlash(this.options.baseUrl)), { ...init, headers, signal: AbortSignal.timeout(10_000) });
|
|
155
|
+
if (!acceptedStatuses.includes(response.status))
|
|
156
|
+
throw new Error(`Elasticsearch management audit request failed with HTTP ${String(response.status)}`);
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function managementAuditIndexName(prefix, date) {
|
|
161
|
+
const local = shanghaiDateParts(date);
|
|
162
|
+
const target = new Date(Date.UTC(local.year, local.month - 1, local.day));
|
|
163
|
+
const day = target.getUTCDay() || 7;
|
|
164
|
+
target.setUTCDate(target.getUTCDate() + 4 - day);
|
|
165
|
+
const isoYear = target.getUTCFullYear();
|
|
166
|
+
const yearStart = new Date(Date.UTC(isoYear, 0, 1));
|
|
167
|
+
const week = Math.ceil((((target.getTime() - yearStart.getTime()) / DAY_MS) + 1) / 7);
|
|
168
|
+
return `${prefix}-${String(isoYear)}-w${String(week).padStart(2, "0")}`;
|
|
169
|
+
}
|
|
170
|
+
function managementAuditDocument(id, timestamp, event) {
|
|
171
|
+
return {
|
|
172
|
+
"@timestamp": timestamp.toISOString(),
|
|
173
|
+
event: { id, action: event.action, status: event.status, ...(event.durationMs === undefined ? {} : { duration_ms: event.durationMs }) },
|
|
174
|
+
user: { id: event.userId, root_user_id: event.rootUserId },
|
|
175
|
+
project: { id: event.projectId },
|
|
176
|
+
session: { id: event.sessionId, ...(event.agentSessionId === undefined ? {} : { agent_session_id: event.agentSessionId }) },
|
|
177
|
+
workspace: { cwd: event.cwd },
|
|
178
|
+
...((event.toolName === undefined && event.toolCallId === undefined) ? {} : { tool: { ...(event.toolName === undefined ? {} : { name: event.toolName }), ...(event.toolCallId === undefined ? {} : { call_id: event.toolCallId }) } }),
|
|
179
|
+
workbench: compact({
|
|
180
|
+
authorization_revision: event.authorizationRevision,
|
|
181
|
+
capability_name: event.capabilityName,
|
|
182
|
+
capability_version: event.capabilityVersion,
|
|
183
|
+
skill_name: event.skillName,
|
|
184
|
+
skill_version: event.skillVersion,
|
|
185
|
+
run_id: event.runId,
|
|
186
|
+
trace_id: event.traceId,
|
|
187
|
+
mcp_trace_id: event.mcpTraceId,
|
|
188
|
+
status_code: event.statusCode === undefined ? undefined : String(event.statusCode),
|
|
189
|
+
retry_count: event.retryCount,
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function compact(value) {
|
|
194
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
195
|
+
}
|
|
196
|
+
function shanghaiDateParts(date) {
|
|
197
|
+
const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(date);
|
|
198
|
+
const number = (type) => Number(parts.find((part) => part.type === type)?.value);
|
|
199
|
+
return { year: number("year"), month: number("month"), day: number("day") };
|
|
200
|
+
}
|
|
201
|
+
function shanghaiMonth(date) {
|
|
202
|
+
const { year, month } = shanghaiDateParts(date);
|
|
203
|
+
return `${String(year)}-${String(month).padStart(2, "0")}`;
|
|
204
|
+
}
|
|
205
|
+
function trailingSlash(value) {
|
|
206
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
207
|
+
}
|
|
208
|
+
function isRecord(value) {
|
|
209
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=managementAuditStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"managementAuditStore.js","sourceRoot":"","sources":["../../../src/server/audit/managementAuditStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { DEFAULT_NORMAL_TOOL_AUDIT_MAX_ROWS, effectivePiWebConfig } from "../../config.js";
|
|
3
|
+
import { normalToolAuditDatabasePath, normalToolAuditStats, pruneNormalToolAudit, queryNormalToolAudit, vacuumNormalToolAudit, } from "./normalToolAuditStore.js";
|
|
4
|
+
export function runNormalToolAuditCommand(args, options = {}) {
|
|
5
|
+
const [subcommand, ...optionArgs] = args;
|
|
6
|
+
if (subcommand === undefined || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
7
|
+
(options.stdout ?? console.log)(normalToolAuditHelp());
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const values = parseOptions(optionArgs);
|
|
11
|
+
const path = options.databasePath ?? normalToolAuditDatabasePath();
|
|
12
|
+
const now = options.now?.() ?? new Date();
|
|
13
|
+
const stdout = options.stdout ?? console.log;
|
|
14
|
+
if (subcommand === "list") {
|
|
15
|
+
requireAllowedOptions(values, ["since", "status", "session", "tool", "limit"]);
|
|
16
|
+
for (const row of queryNormalToolAudit(path, auditQuery(values, now)))
|
|
17
|
+
stdout(JSON.stringify(row));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (subcommand === "stats") {
|
|
21
|
+
requireAllowedOptions(values, ["since"]);
|
|
22
|
+
stdout(JSON.stringify(normalToolAuditStats(path, optionalBoundary(values.get("since"), now)), null, 2));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (subcommand === "export") {
|
|
26
|
+
requireAllowedOptions(values, ["since", "status", "session", "tool", "limit", "format", "output"]);
|
|
27
|
+
const format = values.get("format") ?? "jsonl";
|
|
28
|
+
if (format !== "jsonl" && format !== "csv")
|
|
29
|
+
throw new Error("--format must be jsonl or csv");
|
|
30
|
+
const output = requiredOption(values, "output");
|
|
31
|
+
const rows = queryNormalToolAudit(path, auditQuery(values, now, 10_000));
|
|
32
|
+
(options.writeOutput ?? ((target, content) => { writeFileSync(target, content, "utf8"); }))(output, format === "csv" ? rowsCsv(rows) : rowsJsonl(rows));
|
|
33
|
+
stdout(`Exported ${String(rows.length)} audit rows to ${output}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (subcommand === "prune") {
|
|
37
|
+
requireAllowedOptions(values, ["before"]);
|
|
38
|
+
const before = parseBoundary(requiredOption(values, "before"), now);
|
|
39
|
+
const maxRows = options.maxRows ?? effectivePiWebConfig().config.auditLog?.normalMode?.maxRows ?? DEFAULT_NORMAL_TOOL_AUDIT_MAX_ROWS;
|
|
40
|
+
stdout(`Deleted ${String(pruneNormalToolAudit(path, before, maxRows))} audit rows`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (subcommand === "vacuum") {
|
|
44
|
+
requireAllowedOptions(values, []);
|
|
45
|
+
vacuumNormalToolAudit(path);
|
|
46
|
+
stdout("Audit database vacuum completed");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`Unknown audit command: ${subcommand}`);
|
|
50
|
+
}
|
|
51
|
+
export function normalToolAuditHelp() {
|
|
52
|
+
return `PI WEB ordinary-mode tool audit
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
pi-web audit list [--since 24h] [--status failed] [--session ID] [--tool NAME] [--limit 100]
|
|
56
|
+
pi-web audit stats [--since 30d]
|
|
57
|
+
pi-web audit export --output FILE [--format jsonl|csv] [--since 30d] [--status STATUS] [--session ID] [--tool NAME] [--limit 10000]
|
|
58
|
+
pi-web audit prune --before DATE
|
|
59
|
+
pi-web audit vacuum`;
|
|
60
|
+
}
|
|
61
|
+
function parseOptions(args) {
|
|
62
|
+
const values = new Map();
|
|
63
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
64
|
+
const argument = args[index];
|
|
65
|
+
if (argument === undefined || !argument.startsWith("--") || argument.length === 2)
|
|
66
|
+
throw new Error(`Unexpected audit argument: ${argument ?? ""}`);
|
|
67
|
+
const name = argument.slice(2);
|
|
68
|
+
if (values.has(name))
|
|
69
|
+
throw new Error(`Audit option --${name} was provided more than once`);
|
|
70
|
+
const value = args[index + 1];
|
|
71
|
+
if (value === undefined || value.startsWith("--"))
|
|
72
|
+
throw new Error(`Audit option --${name} requires a value`);
|
|
73
|
+
values.set(name, value);
|
|
74
|
+
index += 1;
|
|
75
|
+
}
|
|
76
|
+
return values;
|
|
77
|
+
}
|
|
78
|
+
function requireAllowedOptions(values, allowed) {
|
|
79
|
+
const allowedSet = new Set(allowed);
|
|
80
|
+
const unknown = [...values.keys()].find((key) => !allowedSet.has(key));
|
|
81
|
+
if (unknown !== undefined)
|
|
82
|
+
throw new Error(`Audit command does not support --${unknown}`);
|
|
83
|
+
}
|
|
84
|
+
function auditQuery(values, now, defaultLimit = 100) {
|
|
85
|
+
const status = values.get("status");
|
|
86
|
+
return {
|
|
87
|
+
...(values.get("since") === undefined ? {} : { since: parseBoundary(requiredOption(values, "since"), now) }),
|
|
88
|
+
...(status === undefined ? {} : { status: parseStatus(status) }),
|
|
89
|
+
...(values.get("session") === undefined ? {} : { sessionId: requiredOption(values, "session") }),
|
|
90
|
+
...(values.get("tool") === undefined ? {} : { toolName: requiredOption(values, "tool") }),
|
|
91
|
+
limit: values.get("limit") === undefined ? defaultLimit : positiveInteger(requiredOption(values, "limit"), "--limit"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function optionalBoundary(value, now) {
|
|
95
|
+
return value === undefined ? undefined : parseBoundary(value, now);
|
|
96
|
+
}
|
|
97
|
+
function parseBoundary(value, now) {
|
|
98
|
+
const duration = /^(\d+)(m|h|d)$/u.exec(value);
|
|
99
|
+
if (duration !== null) {
|
|
100
|
+
const amount = positiveInteger(duration[1] ?? "", "time range");
|
|
101
|
+
const unit = duration[2];
|
|
102
|
+
const multiplier = unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
|
|
103
|
+
return new Date(now.getTime() - amount * multiplier);
|
|
104
|
+
}
|
|
105
|
+
const parsed = new Date(value);
|
|
106
|
+
if (Number.isNaN(parsed.getTime()))
|
|
107
|
+
throw new Error(`Invalid audit time boundary: ${value}`);
|
|
108
|
+
return parsed;
|
|
109
|
+
}
|
|
110
|
+
function parseStatus(value) {
|
|
111
|
+
if (value === "started" || value === "completed" || value === "failed" || value === "interrupted")
|
|
112
|
+
return value;
|
|
113
|
+
throw new Error("--status must be started, completed, failed, or interrupted");
|
|
114
|
+
}
|
|
115
|
+
function positiveInteger(value, label) {
|
|
116
|
+
const number = Number(value);
|
|
117
|
+
if (!Number.isSafeInteger(number) || number < 1)
|
|
118
|
+
throw new Error(`${label} must be a positive integer`);
|
|
119
|
+
return number;
|
|
120
|
+
}
|
|
121
|
+
function requiredOption(values, name) {
|
|
122
|
+
const value = values.get(name);
|
|
123
|
+
if (value === undefined || value === "")
|
|
124
|
+
throw new Error(`Audit option --${name} is required`);
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function rowsJsonl(rows) {
|
|
128
|
+
return rows.length === 0 ? "" : `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`;
|
|
129
|
+
}
|
|
130
|
+
function rowsCsv(rows) {
|
|
131
|
+
const fields = ["id", "sessionId", "cwd", "toolName", "toolCallId", "status", "startedAt", "finishedAt", "durationMs", "createdAt"];
|
|
132
|
+
const lines = [fields.join(",")];
|
|
133
|
+
for (const row of rows)
|
|
134
|
+
lines.push(fields.map((field) => csvCell(row[field])).join(","));
|
|
135
|
+
return `${lines.join("\n")}\n`;
|
|
136
|
+
}
|
|
137
|
+
function csvCell(value) {
|
|
138
|
+
if (value !== undefined && typeof value !== "string" && typeof value !== "number")
|
|
139
|
+
throw new Error("Audit CSV contains an invalid value");
|
|
140
|
+
const text = value === undefined ? "" : String(value);
|
|
141
|
+
return /[",\r\n]/u.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=normalToolAuditCli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalToolAuditCli.js","sourceRoot":"","sources":["../../../src/server/audit/normalToolAuditCli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { piWebDataDir } from "../../config.js";
|
|
5
|
+
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
6
|
+
const DEFAULT_MAINTENANCE_INTERVAL_MS = DAY_MS;
|
|
7
|
+
export function normalToolAuditDatabasePath(env = process.env, cwd = process.cwd()) {
|
|
8
|
+
return join(piWebDataDir(env, cwd), "audit", "normal-tool-calls.sqlite");
|
|
9
|
+
}
|
|
10
|
+
export class NormalToolAuditStore {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.pending = new Map();
|
|
14
|
+
this.now = options.now ?? (() => new Date());
|
|
15
|
+
this.database = openWritableDatabase(options.path);
|
|
16
|
+
const now = this.now();
|
|
17
|
+
markIncompleteCallsInterrupted(this.database, now);
|
|
18
|
+
pruneDatabase(this.database, retentionCutoff(now, options.retentionDays), options.maxRows);
|
|
19
|
+
const interval = options.maintenanceIntervalMs ?? DEFAULT_MAINTENANCE_INTERVAL_MS;
|
|
20
|
+
if (interval > 0) {
|
|
21
|
+
this.maintenanceTimer = setInterval(() => {
|
|
22
|
+
try {
|
|
23
|
+
this.maintain();
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
options.onError?.(error);
|
|
27
|
+
}
|
|
28
|
+
}, interval);
|
|
29
|
+
this.maintenanceTimer.unref();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
record(event) {
|
|
33
|
+
const now = this.now();
|
|
34
|
+
const key = pendingKey(event.sessionId, event.toolCallId);
|
|
35
|
+
if (event.status === "started") {
|
|
36
|
+
const priorId = this.pending.get(key);
|
|
37
|
+
if (priorId !== undefined)
|
|
38
|
+
markRowsInterrupted(this.database, [priorId], now);
|
|
39
|
+
const result = this.database.prepare(`
|
|
40
|
+
INSERT INTO normal_tool_calls (
|
|
41
|
+
session_id, cwd, tool_name, tool_call_id, status, started_at_ms, created_at_ms
|
|
42
|
+
) VALUES (?, ?, ?, ?, 'started', ?, ?)
|
|
43
|
+
`).run(event.sessionId, event.cwd, event.toolName, event.toolCallId, now.getTime(), now.getTime());
|
|
44
|
+
this.pending.set(key, Number(result.lastInsertRowid));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const pendingId = this.pending.get(key);
|
|
48
|
+
this.pending.delete(key);
|
|
49
|
+
if (pendingId !== undefined) {
|
|
50
|
+
this.database.prepare(`
|
|
51
|
+
UPDATE normal_tool_calls
|
|
52
|
+
SET status = ?, finished_at_ms = ?, duration_ms = MAX(0, ? - started_at_ms)
|
|
53
|
+
WHERE id = ?
|
|
54
|
+
`).run(event.status, now.getTime(), now.getTime(), pendingId);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.database.prepare(`
|
|
58
|
+
INSERT INTO normal_tool_calls (
|
|
59
|
+
session_id, cwd, tool_name, tool_call_id, status, started_at_ms, finished_at_ms, duration_ms, created_at_ms
|
|
60
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)
|
|
61
|
+
`).run(event.sessionId, event.cwd, event.toolName, event.toolCallId, event.status, now.getTime(), now.getTime(), now.getTime());
|
|
62
|
+
}
|
|
63
|
+
maintain() {
|
|
64
|
+
const now = this.now();
|
|
65
|
+
pruneDatabase(this.database, retentionCutoff(now, this.options.retentionDays), this.options.maxRows);
|
|
66
|
+
}
|
|
67
|
+
close() {
|
|
68
|
+
if (this.maintenanceTimer !== undefined)
|
|
69
|
+
clearInterval(this.maintenanceTimer);
|
|
70
|
+
markRowsInterrupted(this.database, [...this.pending.values()], this.now());
|
|
71
|
+
this.pending.clear();
|
|
72
|
+
this.database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
73
|
+
this.database.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function queryNormalToolAudit(path, query = {}) {
|
|
77
|
+
if (!existsSync(path))
|
|
78
|
+
return [];
|
|
79
|
+
return withDatabase(path, true, (database) => {
|
|
80
|
+
const clauses = [];
|
|
81
|
+
const parameters = [];
|
|
82
|
+
if (query.since !== undefined) {
|
|
83
|
+
clauses.push("started_at_ms >= ?");
|
|
84
|
+
parameters.push(query.since.getTime());
|
|
85
|
+
}
|
|
86
|
+
if (query.status !== undefined) {
|
|
87
|
+
clauses.push("status = ?");
|
|
88
|
+
parameters.push(query.status);
|
|
89
|
+
}
|
|
90
|
+
if (query.sessionId !== undefined) {
|
|
91
|
+
clauses.push("session_id = ?");
|
|
92
|
+
parameters.push(query.sessionId);
|
|
93
|
+
}
|
|
94
|
+
if (query.toolName !== undefined) {
|
|
95
|
+
clauses.push("tool_name = ?");
|
|
96
|
+
parameters.push(query.toolName);
|
|
97
|
+
}
|
|
98
|
+
const limit = Math.max(1, Math.min(query.limit ?? 100, 10_000));
|
|
99
|
+
parameters.push(limit);
|
|
100
|
+
const rows = database.prepare(`
|
|
101
|
+
SELECT id, session_id, cwd, tool_name, tool_call_id, status,
|
|
102
|
+
started_at_ms, finished_at_ms, duration_ms, created_at_ms
|
|
103
|
+
FROM normal_tool_calls
|
|
104
|
+
${clauses.length === 0 ? "" : `WHERE ${clauses.join(" AND ")}`}
|
|
105
|
+
ORDER BY started_at_ms DESC, id DESC
|
|
106
|
+
LIMIT ?
|
|
107
|
+
`).all(...parameters);
|
|
108
|
+
return rows.map(normalToolAuditRow);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function normalToolAuditStats(path, since) {
|
|
112
|
+
const empty = () => ({ total: 0, byStatus: { started: 0, completed: 0, failed: 0, interrupted: 0 }, byTool: [] });
|
|
113
|
+
if (!existsSync(path))
|
|
114
|
+
return empty();
|
|
115
|
+
return withDatabase(path, true, (database) => {
|
|
116
|
+
const where = since === undefined ? "" : "WHERE started_at_ms >= ?";
|
|
117
|
+
const parameters = since === undefined ? [] : [since.getTime()];
|
|
118
|
+
const statusRows = database.prepare(`SELECT status, COUNT(*) AS count FROM normal_tool_calls ${where} GROUP BY status`).all(...parameters);
|
|
119
|
+
const toolRows = database.prepare(`SELECT tool_name, COUNT(*) AS count FROM normal_tool_calls ${where} GROUP BY tool_name ORDER BY count DESC, tool_name ASC`).all(...parameters);
|
|
120
|
+
const result = empty();
|
|
121
|
+
for (const row of statusRows) {
|
|
122
|
+
const status = auditStatus(row["status"]);
|
|
123
|
+
const count = outputNumber(row["count"], "count");
|
|
124
|
+
result.byStatus[status] = count;
|
|
125
|
+
result.total += count;
|
|
126
|
+
}
|
|
127
|
+
result.byTool = toolRows.map((row) => ({ toolName: outputString(row["tool_name"], "tool_name"), count: outputNumber(row["count"], "count") }));
|
|
128
|
+
return result;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
export function pruneNormalToolAudit(path, before, maxRows) {
|
|
132
|
+
if (!existsSync(path))
|
|
133
|
+
return 0;
|
|
134
|
+
return withDatabase(path, false, (database) => pruneDatabase(database, before, maxRows));
|
|
135
|
+
}
|
|
136
|
+
export function vacuumNormalToolAudit(path) {
|
|
137
|
+
if (!existsSync(path))
|
|
138
|
+
return;
|
|
139
|
+
withDatabase(path, false, (database) => {
|
|
140
|
+
database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
141
|
+
database.exec("VACUUM");
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function openWritableDatabase(path) {
|
|
145
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
146
|
+
const database = new DatabaseSync(path);
|
|
147
|
+
try {
|
|
148
|
+
if (process.platform !== "win32")
|
|
149
|
+
chmodSync(path, 0o600);
|
|
150
|
+
initializeDatabase(database);
|
|
151
|
+
return database;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
database.close();
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function initializeDatabase(database) {
|
|
159
|
+
database.exec("PRAGMA journal_mode = WAL");
|
|
160
|
+
database.exec("PRAGMA synchronous = NORMAL");
|
|
161
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
162
|
+
const version = outputNumber(database.prepare("PRAGMA user_version").get()?.["user_version"], "user_version");
|
|
163
|
+
if (version > 1)
|
|
164
|
+
throw new Error(`Normal tool audit database schema version ${String(version)} is newer than this PI WEB build`);
|
|
165
|
+
database.exec(`
|
|
166
|
+
CREATE TABLE IF NOT EXISTS normal_tool_calls (
|
|
167
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
168
|
+
session_id TEXT NOT NULL,
|
|
169
|
+
cwd TEXT NOT NULL,
|
|
170
|
+
tool_name TEXT NOT NULL,
|
|
171
|
+
tool_call_id TEXT NOT NULL,
|
|
172
|
+
status TEXT NOT NULL CHECK (status IN ('started', 'completed', 'failed', 'interrupted')),
|
|
173
|
+
started_at_ms INTEGER NOT NULL,
|
|
174
|
+
finished_at_ms INTEGER,
|
|
175
|
+
duration_ms INTEGER,
|
|
176
|
+
created_at_ms INTEGER NOT NULL
|
|
177
|
+
);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS normal_tool_calls_started_idx ON normal_tool_calls(started_at_ms DESC);
|
|
179
|
+
CREATE INDEX IF NOT EXISTS normal_tool_calls_session_idx ON normal_tool_calls(session_id, started_at_ms DESC);
|
|
180
|
+
CREATE INDEX IF NOT EXISTS normal_tool_calls_tool_idx ON normal_tool_calls(tool_name, started_at_ms DESC);
|
|
181
|
+
`);
|
|
182
|
+
if (version === 0)
|
|
183
|
+
database.exec("PRAGMA user_version = 1");
|
|
184
|
+
}
|
|
185
|
+
function withDatabase(path, readOnly, use) {
|
|
186
|
+
const database = new DatabaseSync(path, { readOnly });
|
|
187
|
+
try {
|
|
188
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
189
|
+
return use(database);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
database.close();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function markIncompleteCallsInterrupted(database, now) {
|
|
196
|
+
database.prepare(`
|
|
197
|
+
UPDATE normal_tool_calls
|
|
198
|
+
SET status = 'interrupted', finished_at_ms = ?, duration_ms = MAX(0, ? - started_at_ms)
|
|
199
|
+
WHERE status = 'started'
|
|
200
|
+
`).run(now.getTime(), now.getTime());
|
|
201
|
+
}
|
|
202
|
+
function markRowsInterrupted(database, ids, now) {
|
|
203
|
+
if (ids.length === 0)
|
|
204
|
+
return;
|
|
205
|
+
const statement = database.prepare(`
|
|
206
|
+
UPDATE normal_tool_calls
|
|
207
|
+
SET status = 'interrupted', finished_at_ms = ?, duration_ms = MAX(0, ? - started_at_ms)
|
|
208
|
+
WHERE id = ? AND status = 'started'
|
|
209
|
+
`);
|
|
210
|
+
for (const id of ids)
|
|
211
|
+
statement.run(now.getTime(), now.getTime(), id);
|
|
212
|
+
}
|
|
213
|
+
function pruneDatabase(database, before, maxRows) {
|
|
214
|
+
const expired = database.prepare("DELETE FROM normal_tool_calls WHERE started_at_ms < ?").run(before.getTime()).changes;
|
|
215
|
+
const excess = database.prepare(`
|
|
216
|
+
DELETE FROM normal_tool_calls
|
|
217
|
+
WHERE id IN (
|
|
218
|
+
SELECT id FROM normal_tool_calls ORDER BY started_at_ms DESC, id DESC LIMIT -1 OFFSET ?
|
|
219
|
+
)
|
|
220
|
+
`).run(maxRows).changes;
|
|
221
|
+
database.exec("PRAGMA wal_checkpoint(PASSIVE)");
|
|
222
|
+
return Number(expired) + Number(excess);
|
|
223
|
+
}
|
|
224
|
+
function retentionCutoff(now, retentionDays) {
|
|
225
|
+
return new Date(now.getTime() - retentionDays * DAY_MS);
|
|
226
|
+
}
|
|
227
|
+
function pendingKey(sessionId, toolCallId) {
|
|
228
|
+
return `${sessionId}\0${toolCallId}`;
|
|
229
|
+
}
|
|
230
|
+
function normalToolAuditRow(row) {
|
|
231
|
+
const finishedAt = optionalOutputNumber(row["finished_at_ms"], "finished_at_ms");
|
|
232
|
+
const durationMs = optionalOutputNumber(row["duration_ms"], "duration_ms");
|
|
233
|
+
return {
|
|
234
|
+
id: outputNumber(row["id"], "id"),
|
|
235
|
+
sessionId: outputString(row["session_id"], "session_id"),
|
|
236
|
+
cwd: outputString(row["cwd"], "cwd"),
|
|
237
|
+
toolName: outputString(row["tool_name"], "tool_name"),
|
|
238
|
+
toolCallId: outputString(row["tool_call_id"], "tool_call_id"),
|
|
239
|
+
status: auditStatus(row["status"]),
|
|
240
|
+
startedAt: new Date(outputNumber(row["started_at_ms"], "started_at_ms")).toISOString(),
|
|
241
|
+
...(finishedAt === undefined ? {} : { finishedAt: new Date(finishedAt).toISOString() }),
|
|
242
|
+
...(durationMs === undefined ? {} : { durationMs }),
|
|
243
|
+
createdAt: new Date(outputNumber(row["created_at_ms"], "created_at_ms")).toISOString(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function auditStatus(value) {
|
|
247
|
+
if (value === "started" || value === "completed" || value === "failed" || value === "interrupted")
|
|
248
|
+
return value;
|
|
249
|
+
throw new Error("Normal tool audit database contains an invalid status");
|
|
250
|
+
}
|
|
251
|
+
function outputString(value, field) {
|
|
252
|
+
if (typeof value !== "string")
|
|
253
|
+
throw new Error(`Normal tool audit database ${field} is invalid`);
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
function outputNumber(value, field) {
|
|
257
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
258
|
+
throw new Error(`Normal tool audit database ${field} is invalid`);
|
|
259
|
+
return value;
|
|
260
|
+
}
|
|
261
|
+
function optionalOutputNumber(value, field) {
|
|
262
|
+
return value === null || value === undefined ? undefined : outputNumber(value, field);
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=normalToolAuditStore.js.map
|