@agimon-ai/po-log 0.0.0-alpha.0
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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/apiContracts.cjs +164 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +159 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/server.cjs +1034 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +1006 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +1542 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index-CTZMUfTZ.d.mts +185 -0
- package/dist/index-CTZMUfTZ.d.mts.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/metrics-DywCpDJ_.mjs +508 -0
- package/dist/metrics-DywCpDJ_.mjs.map +1 -0
- package/dist/metrics-jCZe13Xg.cjs +573 -0
- package/dist/metrics-jCZe13Xg.cjs.map +1 -0
- package/dist/metrics.cjs +13 -0
- package/dist/metrics.d.mts +2 -0
- package/dist/metrics.mjs +2 -0
- package/dist/metricsSource-DXZLawQu.mjs +130 -0
- package/dist/metricsSource-DXZLawQu.mjs.map +1 -0
- package/dist/metricsSource-DdwQNzoY.cjs +135 -0
- package/dist/metricsSource-DdwQNzoY.cjs.map +1 -0
- package/dist/metricsSource-cZnErdv4.d.mts +51 -0
- package/dist/metricsSource-cZnErdv4.d.mts.map +1 -0
- package/dist/metricsSource.cjs +3 -0
- package/dist/metricsSource.d.mts +18 -0
- package/dist/metricsSource.d.mts.map +1 -0
- package/dist/metricsSource.mjs +2 -0
- package/llms.txt +7 -0
- package/package.json +140 -0
- package/src/prompts/po-debug-agent/SKILL.md +28 -0
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
import { t as createMetricsSource } from "../metricsSource-DXZLawQu.mjs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { resolveLogSinkInstance } from "@agimon-ai/log-sink-mcp";
|
|
4
|
+
import { composeServerProviderOverrides, defineServerPlugin, defineTool, packageResourcePath, readPackageResource } from "@agimon-ai/po-core/serverFacet";
|
|
5
|
+
import { composeRootHooks, defineHook, defineResource, defineRoot, defineRoute, defineServerCommand, defineTool as defineTool$1 } from "@agimon-ai/po-core/extensionFile";
|
|
6
|
+
import { isRecord } from "@agimon-ai/po-core/runtimeJson";
|
|
7
|
+
import { createDoomTelemetry } from "@agimon-ai/po-telemetry";
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { apiResponse, defineApiRoutes } from "@agimon-ai/po-core/web";
|
|
12
|
+
import { DOOM_HELP_WHEN } from "@agimon-ai/po-core/help";
|
|
13
|
+
import { Check } from "typebox/value";
|
|
14
|
+
import { Type } from "typebox";
|
|
15
|
+
//#region src/constants/telemetry.ts
|
|
16
|
+
const PACKAGE_NAME$1 = "@agimon-ai/po-log";
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/services/telemetryEnabled/index.ts
|
|
19
|
+
function isEnabled(value) {
|
|
20
|
+
return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/services/serverTelemetry/index.ts
|
|
24
|
+
function contextAttributes(execution) {
|
|
25
|
+
return {
|
|
26
|
+
"pi.mode": "json",
|
|
27
|
+
"pi.session.id": execution.sessionId,
|
|
28
|
+
...execution.model === void 0 ? {} : {
|
|
29
|
+
"gen_ai.provider.name": execution.model.provider,
|
|
30
|
+
"gen_ai.request.model": execution.model.id
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Headless hooks carry untrusted records, not Pi's typed assistant messages. */
|
|
35
|
+
function usageAttributes(message) {
|
|
36
|
+
const usage = isRecord(message.usage) ? message.usage : {};
|
|
37
|
+
const attributes = {};
|
|
38
|
+
for (const [field, attribute] of Object.entries({
|
|
39
|
+
input: "gen_ai.usage.input_tokens",
|
|
40
|
+
output: "gen_ai.usage.output_tokens",
|
|
41
|
+
cacheRead: "gen_ai.usage.cache_read_tokens",
|
|
42
|
+
cacheWrite: "gen_ai.usage.cache_write_tokens",
|
|
43
|
+
totalTokens: "gen_ai.usage.total_tokens",
|
|
44
|
+
reasoning: "gen_ai.usage.reasoning_tokens"
|
|
45
|
+
})) {
|
|
46
|
+
const value = usage[field];
|
|
47
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) attributes[attribute] = value;
|
|
48
|
+
}
|
|
49
|
+
const cost = isRecord(usage.cost) ? usage.cost.total : void 0;
|
|
50
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost >= 0) attributes["gen_ai.usage.cost"] = cost;
|
|
51
|
+
return attributes;
|
|
52
|
+
}
|
|
53
|
+
function createServerTelemetry(options = {}) {
|
|
54
|
+
let active = false;
|
|
55
|
+
let finished = false;
|
|
56
|
+
let queue = Promise.resolve();
|
|
57
|
+
let disposal;
|
|
58
|
+
let thinkingLevel;
|
|
59
|
+
const sessionAttributes = (execution) => ({
|
|
60
|
+
...contextAttributes(execution),
|
|
61
|
+
...thinkingLevel ? { "pi.thinking_level": thinkingLevel } : {}
|
|
62
|
+
});
|
|
63
|
+
const enqueue = (operation) => {
|
|
64
|
+
const pending = queue.then(operation);
|
|
65
|
+
queue = pending.catch((error) => options.onDiagnostic?.(String(error)));
|
|
66
|
+
return pending;
|
|
67
|
+
};
|
|
68
|
+
let telemetry;
|
|
69
|
+
const turns = /* @__PURE__ */ new Map();
|
|
70
|
+
const tools = /* @__PURE__ */ new Map();
|
|
71
|
+
let agentStartedAt;
|
|
72
|
+
let turnIndex = 0;
|
|
73
|
+
const activity = {
|
|
74
|
+
name: PACKAGE_NAME$1,
|
|
75
|
+
start(execution) {
|
|
76
|
+
const current = createDoomTelemetry({
|
|
77
|
+
serviceName: options.serviceName ?? "pi",
|
|
78
|
+
packageName: PACKAGE_NAME$1,
|
|
79
|
+
cwd: execution.cwd,
|
|
80
|
+
env: {
|
|
81
|
+
...options.env ?? execution.environment,
|
|
82
|
+
PI_SESSION_ID: execution.sessionId
|
|
83
|
+
},
|
|
84
|
+
allowFileFallback: options.allowFileFallback,
|
|
85
|
+
telemetryFactory: options.telemetryFactory,
|
|
86
|
+
warn: options.onDiagnostic ?? (() => void 0),
|
|
87
|
+
onRecord: (record) => options.metrics?.record(record.event, record.attributes),
|
|
88
|
+
onStatus: (status) => options.onSinkStatus?.({
|
|
89
|
+
service: status.serviceName,
|
|
90
|
+
backend: status.backend,
|
|
91
|
+
endpoint: status.endpoint ?? "none",
|
|
92
|
+
endpointSource: status.endpointSource,
|
|
93
|
+
traces: status.traces,
|
|
94
|
+
redaction: true,
|
|
95
|
+
fileFallback: status.fileFallback
|
|
96
|
+
}),
|
|
97
|
+
enableLogs: true,
|
|
98
|
+
enableTraces: (options.env ?? execution.environment).AGENT_OTEL_TRACES === void 0 || isEnabled((options.env ?? execution.environment).AGENT_OTEL_TRACES)
|
|
99
|
+
});
|
|
100
|
+
telemetry = current;
|
|
101
|
+
active = true;
|
|
102
|
+
finished = false;
|
|
103
|
+
return () => disposal ??= (async () => {
|
|
104
|
+
active = false;
|
|
105
|
+
await queue;
|
|
106
|
+
if (telemetry === current) {
|
|
107
|
+
telemetry = void 0;
|
|
108
|
+
turns.clear();
|
|
109
|
+
tools.clear();
|
|
110
|
+
agentStartedAt = void 0;
|
|
111
|
+
}
|
|
112
|
+
await current.shutdown();
|
|
113
|
+
})();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const command = {
|
|
117
|
+
name: "log-metrics",
|
|
118
|
+
description: "Show headless telemetry sink status.",
|
|
119
|
+
async execute(_args, execution) {
|
|
120
|
+
await execution.client.notify({
|
|
121
|
+
title: "Po telemetry",
|
|
122
|
+
body: JSON.stringify(telemetry?.status() ?? { active: false }, null, 2),
|
|
123
|
+
level: "info"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const hooks = [
|
|
128
|
+
{
|
|
129
|
+
event: "session_start",
|
|
130
|
+
async handle(_event, execution) {
|
|
131
|
+
thinkingLevel = (await execution.session?.readModelSettings?.())?.thinkingLevel;
|
|
132
|
+
await telemetry?.recordEvent("pi.session.started", {
|
|
133
|
+
...sessionAttributes(execution),
|
|
134
|
+
"pi.session.reason": "startup",
|
|
135
|
+
"pi.session.previous": false
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
event: "agent_settled",
|
|
141
|
+
async handle(_event, execution) {
|
|
142
|
+
await telemetry?.recordDebug("pi.agent.settled", sessionAttributes(execution));
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
event: "session_shutdown",
|
|
147
|
+
async handle(_event, execution) {
|
|
148
|
+
if (finished) return;
|
|
149
|
+
finished = true;
|
|
150
|
+
await telemetry?.recordEvent("pi.session.finished", {
|
|
151
|
+
...sessionAttributes(execution),
|
|
152
|
+
"pi.session.reason": "quit"
|
|
153
|
+
});
|
|
154
|
+
execution.client.setStatus("po-log", void 0);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
event: "turn_start",
|
|
159
|
+
async handle(event, execution) {
|
|
160
|
+
if (!telemetry) return;
|
|
161
|
+
await telemetry.recordDebug("pi.turn.started", {
|
|
162
|
+
...sessionAttributes(execution),
|
|
163
|
+
"pi.turn.index": event.turnIndex,
|
|
164
|
+
...typeof event.turnId === "string" ? { "pi.turn.id": event.turnId } : {}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
event: "turn_end",
|
|
170
|
+
async handle(event, execution) {
|
|
171
|
+
const current = telemetry;
|
|
172
|
+
if (!current) return;
|
|
173
|
+
const duration = event.telemetryDurationMs;
|
|
174
|
+
const message = isRecord(event.message) && event.message.role === "assistant" ? event.message : void 0;
|
|
175
|
+
const usage = message === void 0 ? {} : usageAttributes(message);
|
|
176
|
+
const attributes = {
|
|
177
|
+
...sessionAttributes(execution),
|
|
178
|
+
"pi.turn.index": event.turnIndex,
|
|
179
|
+
...typeof event.turnId === "string" ? { "pi.turn.id": event.turnId } : {},
|
|
180
|
+
...message === void 0 ? {} : {
|
|
181
|
+
"gen_ai.provider.name": message.provider ?? execution.model?.provider,
|
|
182
|
+
"gen_ai.response.model": message.responseModel ?? message.model,
|
|
183
|
+
"gen_ai.response.finish_reasons": message.stopReason
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const results = Array.isArray(event.toolResults) ? event.toolResults.filter(isRecord) : [];
|
|
187
|
+
await current.recordEvent("pi.turn.finished", {
|
|
188
|
+
...attributes,
|
|
189
|
+
...usage,
|
|
190
|
+
"pi.tool_result.count": results.length,
|
|
191
|
+
...typeof duration === "number" ? { "pi.turn.duration_ms": duration } : {}
|
|
192
|
+
});
|
|
193
|
+
if (message?.stopReason === "error" || message?.stopReason === "aborted") {
|
|
194
|
+
const failure = {
|
|
195
|
+
...attributes,
|
|
196
|
+
"error.type": "ProviderError",
|
|
197
|
+
"error.code": message.stopReason,
|
|
198
|
+
outcome: message.stopReason === "aborted" ? "aborted" : "error"
|
|
199
|
+
};
|
|
200
|
+
if (message.stopReason === "aborted") await current.recordWarning("pi.turn.failed", void 0, failure);
|
|
201
|
+
else await current.recordError("pi.turn.failed", void 0, failure);
|
|
202
|
+
}
|
|
203
|
+
if (Object.keys(usage).length > 0) for (const result of results) await current.recordDebug("pi.tool_token_sample", {
|
|
204
|
+
...attributes,
|
|
205
|
+
...usage,
|
|
206
|
+
"tool.name": result.toolName,
|
|
207
|
+
token_attribution: "toolCallTurn"
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
event: "tool_execution_start",
|
|
213
|
+
async handle(event, execution) {
|
|
214
|
+
if (!telemetry) return;
|
|
215
|
+
await telemetry.recordDebug("pi.tool_call", {
|
|
216
|
+
...sessionAttributes(execution),
|
|
217
|
+
"tool.name": event.toolName,
|
|
218
|
+
"tool.call.id": event.toolCallId
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
event: "tool_execution_end",
|
|
224
|
+
async handle(event, execution) {
|
|
225
|
+
const current = telemetry;
|
|
226
|
+
if (!current) return;
|
|
227
|
+
const duration = event.telemetryDurationMs;
|
|
228
|
+
await current.recordEvent("pi.tool_result", {
|
|
229
|
+
...sessionAttributes(execution),
|
|
230
|
+
"tool.name": event.toolName,
|
|
231
|
+
"tool.call.id": event.toolCallId,
|
|
232
|
+
success: event.isError !== true,
|
|
233
|
+
"tool.result.error": event.isError === true,
|
|
234
|
+
...typeof duration === "number" ? { "tool.duration_ms": duration } : {}
|
|
235
|
+
});
|
|
236
|
+
if (typeof event.toolCallId === "string" && event.toolCallId.startsWith("external-")) await current.flush();
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
event: "agent_start",
|
|
241
|
+
async handle(_event, execution) {
|
|
242
|
+
if (!telemetry) return;
|
|
243
|
+
await telemetry.recordEvent("pi.agent.started", sessionAttributes(execution));
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
event: "model_select",
|
|
248
|
+
async handle(event, execution) {
|
|
249
|
+
await telemetry?.recordEvent("pi.model.selected", {
|
|
250
|
+
...sessionAttributes(execution),
|
|
251
|
+
"pi.model.source": "set",
|
|
252
|
+
"gen_ai.provider.name": event.model.provider,
|
|
253
|
+
"gen_ai.request.model": event.model.id
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
event: "agent_end",
|
|
259
|
+
async handle(event, execution) {
|
|
260
|
+
await telemetry?.recordEvent("pi.agent.finished", {
|
|
261
|
+
...sessionAttributes(execution),
|
|
262
|
+
"pi.message.count": Array.isArray(event.messages) ? event.messages.length : 0,
|
|
263
|
+
...typeof event.telemetryDurationMs === "number" ? { "pi.agent.duration_ms": event.telemetryDurationMs } : {}
|
|
264
|
+
});
|
|
265
|
+
await telemetry?.flush();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
];
|
|
269
|
+
for (const hook of hooks) {
|
|
270
|
+
const handle = hook.handle.bind(hook);
|
|
271
|
+
hook.handle = (event, execution) => {
|
|
272
|
+
if (!active) return;
|
|
273
|
+
const now = Date.now();
|
|
274
|
+
if (hook.event === "agent_start") turnIndex = 0;
|
|
275
|
+
const index = typeof event.turnIndex === "number" ? event.turnIndex : turnIndex;
|
|
276
|
+
const turnKey = typeof event.turnId === "string" ? event.turnId : String(index);
|
|
277
|
+
if (hook.event === "turn_start") turns.set(turnKey, now);
|
|
278
|
+
if (hook.event === "tool_execution_start" && typeof event.toolCallId === "string") tools.set(event.toolCallId, now);
|
|
279
|
+
if (hook.event === "agent_start") agentStartedAt = now;
|
|
280
|
+
let duration;
|
|
281
|
+
if (hook.event === "turn_end") {
|
|
282
|
+
const start = turns.get(turnKey);
|
|
283
|
+
turns.delete(turnKey);
|
|
284
|
+
turnIndex += 1;
|
|
285
|
+
if (start !== void 0) duration = now - start;
|
|
286
|
+
}
|
|
287
|
+
if (hook.event === "tool_execution_end" && typeof event.toolCallId === "string") {
|
|
288
|
+
const start = tools.get(event.toolCallId);
|
|
289
|
+
tools.delete(event.toolCallId);
|
|
290
|
+
if (start !== void 0) duration = now - start;
|
|
291
|
+
}
|
|
292
|
+
if (hook.event === "agent_end") {
|
|
293
|
+
if (agentStartedAt !== void 0) duration = now - agentStartedAt;
|
|
294
|
+
agentStartedAt = void 0;
|
|
295
|
+
}
|
|
296
|
+
return enqueue(async () => {
|
|
297
|
+
thinkingLevel = (await execution.session?.readModelSettings?.())?.thinkingLevel;
|
|
298
|
+
await handle({
|
|
299
|
+
...event,
|
|
300
|
+
turnIndex: index,
|
|
301
|
+
telemetryDurationMs: duration
|
|
302
|
+
}, execution);
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
activities: [activity],
|
|
308
|
+
commands: [command],
|
|
309
|
+
hooks
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts
|
|
314
|
+
const createLogServerRoot = () => {
|
|
315
|
+
const runtime = createServerTelemetry();
|
|
316
|
+
return {
|
|
317
|
+
value: runtime,
|
|
318
|
+
activities: runtime.activities
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/extensions/workspaces/sessions/(backend)/root.server.ts
|
|
323
|
+
var root_server_default = defineRoot(createLogServerRoot);
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/services/issuesSource/index.ts
|
|
326
|
+
/**
|
|
327
|
+
* Agent-issue analysis over the sink's CLI.
|
|
328
|
+
*
|
|
329
|
+
* One transport, not two, because the daemon serves only /api/metrics and
|
|
330
|
+
* /api/logs: there is no issues route to prefer. Every call is a subprocess,
|
|
331
|
+
* which is why the page asks for this only when a reader opens the section
|
|
332
|
+
* rather than alongside each report.
|
|
333
|
+
*/
|
|
334
|
+
const PACKAGE_NAME = "@agimon-ai/log-sink-mcp";
|
|
335
|
+
const CLI_TIMEOUT_MS = 6e4;
|
|
336
|
+
/** The analysis scans the whole window, so its output is larger than a metrics report. */
|
|
337
|
+
const MAX_CLI_BUFFER = 33554432;
|
|
338
|
+
function cliEntryPoint() {
|
|
339
|
+
const packageJson = createRequire(import.meta.url).resolve(`${PACKAGE_NAME}/package.json`);
|
|
340
|
+
return path.join(path.dirname(packageJson), "dist", "cli.mjs");
|
|
341
|
+
}
|
|
342
|
+
function runCli(params, dbPath) {
|
|
343
|
+
return new Promise((resolve, reject) => {
|
|
344
|
+
execFile(process.execPath, [
|
|
345
|
+
cliEntryPoint(),
|
|
346
|
+
"logs",
|
|
347
|
+
"agent-issues",
|
|
348
|
+
"--limit",
|
|
349
|
+
String(params.limit),
|
|
350
|
+
...params.sessionId === void 0 ? [] : ["--session-id", params.sessionId],
|
|
351
|
+
...params.startTime === void 0 ? [] : ["--start-time", params.startTime],
|
|
352
|
+
...params.endTime === void 0 ? [] : ["--end-time", params.endTime],
|
|
353
|
+
...dbPath === void 0 ? [] : ["--db-path", dbPath]
|
|
354
|
+
], {
|
|
355
|
+
timeout: CLI_TIMEOUT_MS,
|
|
356
|
+
maxBuffer: MAX_CLI_BUFFER
|
|
357
|
+
}, (error, stdout, stderr) => {
|
|
358
|
+
if (error) {
|
|
359
|
+
reject(new Error(stderr.trim() || error.message));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
resolve(JSON.parse(stdout));
|
|
364
|
+
} catch {
|
|
365
|
+
reject(/* @__PURE__ */ new Error("The log sink returned output this version cannot read."));
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function isRecord$1(value) {
|
|
371
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
372
|
+
}
|
|
373
|
+
function counts(value) {
|
|
374
|
+
if (!isRecord$1(value)) return {};
|
|
375
|
+
const entries = Object.entries(value).flatMap(([key, count]) => typeof count === "number" ? [[key, count]] : []);
|
|
376
|
+
return Object.fromEntries(entries);
|
|
377
|
+
}
|
|
378
|
+
function text(value) {
|
|
379
|
+
return typeof value === "string" ? value : "";
|
|
380
|
+
}
|
|
381
|
+
function nullableText(value) {
|
|
382
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
383
|
+
}
|
|
384
|
+
function toSample(value) {
|
|
385
|
+
if (!isRecord$1(value)) return [];
|
|
386
|
+
return [{
|
|
387
|
+
fingerprint: text(value.fingerprint),
|
|
388
|
+
occurrenceCount: typeof value.occurrenceCount === "number" ? value.occurrenceCount : 1,
|
|
389
|
+
category: text(value.category),
|
|
390
|
+
timestamp: text(value.timestamp),
|
|
391
|
+
level: text(value.level),
|
|
392
|
+
message: text(value.message),
|
|
393
|
+
detail: nullableText(value.detail) ?? text(value.message),
|
|
394
|
+
tool: nullableText(value.tool),
|
|
395
|
+
errorType: nullableText(value.errorType),
|
|
396
|
+
agentName: nullableText(value.agentName),
|
|
397
|
+
model: nullableText(value.model),
|
|
398
|
+
statusCode: nullableText(value.statusCode)
|
|
399
|
+
}];
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The CLI's report is a foreign process's JSON, so every field is narrowed
|
|
403
|
+
* here rather than cast. A sink one version ahead may add fields, and a sink
|
|
404
|
+
* one version behind may omit them; neither should throw.
|
|
405
|
+
*/
|
|
406
|
+
function toReport$1(raw) {
|
|
407
|
+
if (!isRecord$1(raw)) throw new Error("The log sink returned output this version cannot read.");
|
|
408
|
+
const samples = Array.isArray(raw.issues) ? raw.issues.flatMap(toSample) : [];
|
|
409
|
+
return {
|
|
410
|
+
totalIssues: typeof raw.totalIssues === "number" ? raw.totalIssues : 0,
|
|
411
|
+
uniqueIncidents: typeof raw.uniqueIncidents === "number" ? raw.uniqueIncidents : 0,
|
|
412
|
+
byCategory: counts(raw.byCategory),
|
|
413
|
+
byTool: counts(raw.byTool),
|
|
414
|
+
byErrorType: counts(raw.byErrorType),
|
|
415
|
+
samples
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function createIssuesSource(options = {}) {
|
|
419
|
+
const instance = resolveLogSinkInstance({
|
|
420
|
+
env: options.env,
|
|
421
|
+
cwd: options.cwd,
|
|
422
|
+
packageName: options.packageName,
|
|
423
|
+
serviceName: options.serviceName
|
|
424
|
+
});
|
|
425
|
+
const cliRunner = options.cliRunner ?? ((params) => runCli(params, instance?.dbPath));
|
|
426
|
+
return { async query(params) {
|
|
427
|
+
return toReport$1(await cliRunner(params));
|
|
428
|
+
} };
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/types/webMetrics.ts
|
|
432
|
+
/**
|
|
433
|
+
* The dimensions the page offers.
|
|
434
|
+
*
|
|
435
|
+
* log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and
|
|
436
|
+
* 'step'. Those are omitted on purpose: no Po package emits the matching
|
|
437
|
+
* attributes today, so offering them would draw an empty chart and read as a
|
|
438
|
+
* bug rather than as a gap.
|
|
439
|
+
*/
|
|
440
|
+
const METRICS_DIMENSIONS = [
|
|
441
|
+
"session",
|
|
442
|
+
"agent",
|
|
443
|
+
"model",
|
|
444
|
+
"provider"
|
|
445
|
+
];
|
|
446
|
+
const METRICS_PERIODS = [
|
|
447
|
+
"day",
|
|
448
|
+
"week",
|
|
449
|
+
"month",
|
|
450
|
+
"all"
|
|
451
|
+
];
|
|
452
|
+
const METRICS_QUERY_PARAMS = {
|
|
453
|
+
dimension: "dimension",
|
|
454
|
+
period: "period",
|
|
455
|
+
/** Narrows the whole report to one value of the current dimension. */
|
|
456
|
+
focus: "focus"
|
|
457
|
+
};
|
|
458
|
+
function isMetricsDimension(value) {
|
|
459
|
+
return METRICS_DIMENSIONS.includes(value);
|
|
460
|
+
}
|
|
461
|
+
function isMetricsPeriod(value) {
|
|
462
|
+
return METRICS_PERIODS.includes(value);
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
//#region src/types/apiRoutes.ts
|
|
466
|
+
/**
|
|
467
|
+
* This package's routes, as data.
|
|
468
|
+
*
|
|
469
|
+
* No scope and no base path. The build reads both off
|
|
470
|
+
* `src/extensions/(backend)/api/log/`, so the mount is stated once, by the
|
|
471
|
+
* folder that creates it. They used to be written here, in the contract, and
|
|
472
|
+
* again inside every URL the page built, and the copies were free to disagree.
|
|
473
|
+
*
|
|
474
|
+
* This is the one file the routes, the Hono app and the browser all read, so a
|
|
475
|
+
* path cannot move on one side alone.
|
|
476
|
+
*/
|
|
477
|
+
var apiRoutes_default = defineApiRoutes({
|
|
478
|
+
metrics: {
|
|
479
|
+
method: "GET",
|
|
480
|
+
path: "/metrics",
|
|
481
|
+
query: [
|
|
482
|
+
METRICS_QUERY_PARAMS.dimension,
|
|
483
|
+
METRICS_QUERY_PARAMS.period,
|
|
484
|
+
METRICS_QUERY_PARAMS.focus
|
|
485
|
+
],
|
|
486
|
+
response: apiResponse()
|
|
487
|
+
},
|
|
488
|
+
/**
|
|
489
|
+
* Separate from the report because the hub answers it from a subprocess: the
|
|
490
|
+
* running daemon exposes no issues route, so folding it in would make every
|
|
491
|
+
* refresh wait on the slowest transport.
|
|
492
|
+
*/
|
|
493
|
+
issues: {
|
|
494
|
+
method: "GET",
|
|
495
|
+
path: "/issues",
|
|
496
|
+
query: [METRICS_QUERY_PARAMS.focus],
|
|
497
|
+
response: apiResponse()
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/services/hubApi/index.ts
|
|
502
|
+
/**
|
|
503
|
+
* The cockpit's half of this package's metrics access.
|
|
504
|
+
*
|
|
505
|
+
* Hub-scoped rather than session-scoped: the questions this answers, where the
|
|
506
|
+
* tokens went and which agents failed, are about the machine over time, and a
|
|
507
|
+
* session server can only see itself. The sink is already machine-wide, so the
|
|
508
|
+
* hub is the only place the two agree.
|
|
509
|
+
*
|
|
510
|
+
* The query path is not reimplemented here. `createMetricsSource` already owns
|
|
511
|
+
* the sink's two transports and its fallback order, and the TUI overlay reads
|
|
512
|
+
* through the same source, so both surfaces answer from one implementation.
|
|
513
|
+
*/
|
|
514
|
+
const DEFAULT_DIMENSION = "model";
|
|
515
|
+
const DEFAULT_PERIOD = "week";
|
|
516
|
+
/** The sink groups sessions under a different name than the page's dimension. */
|
|
517
|
+
const SINK_GROUP_BY = {
|
|
518
|
+
session: "session",
|
|
519
|
+
agent: "agent",
|
|
520
|
+
model: "model",
|
|
521
|
+
provider: "provider"
|
|
522
|
+
};
|
|
523
|
+
function toBucket(bucket) {
|
|
524
|
+
return {
|
|
525
|
+
label: bucket.label,
|
|
526
|
+
totalTokens: bucket.totalTokens,
|
|
527
|
+
inputTokens: bucket.inputTokens,
|
|
528
|
+
outputTokens: bucket.outputTokens
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function toGroup(row) {
|
|
532
|
+
return {
|
|
533
|
+
key: row.key,
|
|
534
|
+
totalTokens: row.totalTokens,
|
|
535
|
+
inputTokens: row.inputTokens,
|
|
536
|
+
outputTokens: row.outputTokens,
|
|
537
|
+
issueCount: row.issueCount,
|
|
538
|
+
failed: row.failed
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* The turn's p90 rather than a sum: the sink attributes a whole turn's tokens
|
|
543
|
+
* to every tool that ran in it, so adding them across calls would multiply the
|
|
544
|
+
* same tokens by however many tools shared the turn.
|
|
545
|
+
*/
|
|
546
|
+
function toTool(row) {
|
|
547
|
+
return {
|
|
548
|
+
name: row.toolName,
|
|
549
|
+
calls: row.invocationCount,
|
|
550
|
+
p90TotalTokens: row.toolCallTurn.p90TotalTokens
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/** Which filter field carries a focus value, per dimension. */
|
|
554
|
+
const FOCUS_FIELD = {
|
|
555
|
+
session: "sessionId",
|
|
556
|
+
agent: "agentName",
|
|
557
|
+
model: "model",
|
|
558
|
+
provider: "provider"
|
|
559
|
+
};
|
|
560
|
+
/**
|
|
561
|
+
* The focus the sink actually applied, read from its own echo.
|
|
562
|
+
*
|
|
563
|
+
* A daemon that predates a filter ignores it and answers with everything. If
|
|
564
|
+
* the page trusted the request instead of this echo, it would label the whole
|
|
565
|
+
* machine's numbers as one model's.
|
|
566
|
+
*/
|
|
567
|
+
function appliedFocus(report, dimension) {
|
|
568
|
+
const filters = report.filters;
|
|
569
|
+
if (filters === void 0) return void 0;
|
|
570
|
+
if (dimension === "session") return filters.sessionId;
|
|
571
|
+
if (dimension === "agent") return filters.agentName;
|
|
572
|
+
const applied = dimension === "model" ? filters.model : filters.provider;
|
|
573
|
+
if (applied === void 0) return void 0;
|
|
574
|
+
return Array.isArray(applied) ? applied[0] : applied;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* The sink's own type says Date, but both transports parse JSON, so what
|
|
578
|
+
* arrives is an ISO string. Trusting the declared type here throws on every
|
|
579
|
+
* real response, which no test using a hand-built Date would ever catch.
|
|
580
|
+
*/
|
|
581
|
+
function generatedAtIso(value) {
|
|
582
|
+
if (value instanceof Date) return value.toISOString();
|
|
583
|
+
const parsed = new Date(value);
|
|
584
|
+
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date(0)).toISOString() : parsed.toISOString();
|
|
585
|
+
}
|
|
586
|
+
function toReport(report, source, dimension) {
|
|
587
|
+
const focus = appliedFocus(report, dimension);
|
|
588
|
+
return {
|
|
589
|
+
generatedAt: generatedAtIso(report.generatedAt),
|
|
590
|
+
...report.filters?.startTime === void 0 ? {} : { startTime: generatedAtIso(report.filters.startTime) },
|
|
591
|
+
endTime: generatedAtIso(report.filters?.endTime ?? report.generatedAt),
|
|
592
|
+
dimension: report.groupBy,
|
|
593
|
+
period: report.period,
|
|
594
|
+
bucketUnit: String(report.bucket),
|
|
595
|
+
transport: source.lastTransport(),
|
|
596
|
+
...focus === void 0 ? {} : { focus },
|
|
597
|
+
totals: {
|
|
598
|
+
usageEventCount: report.totals.usageEventCount,
|
|
599
|
+
totalRecords: report.totals.totalRecords,
|
|
600
|
+
totalTokens: report.totals.totalTokens,
|
|
601
|
+
inputTokens: report.totals.inputTokens,
|
|
602
|
+
outputTokens: report.totals.outputTokens,
|
|
603
|
+
cachedTokens: report.totals.cachedInputTokens,
|
|
604
|
+
reasoningTokens: report.totals.reasoningOutputTokens,
|
|
605
|
+
groupCount: report.totals.groupCount,
|
|
606
|
+
failedGroups: report.totals.failedGroups,
|
|
607
|
+
issueCount: report.totals.issueCount
|
|
608
|
+
},
|
|
609
|
+
timeline: report.timeline.map(toBucket),
|
|
610
|
+
groups: report.groups.map(toGroup),
|
|
611
|
+
tools: report.tools.rows.map(toTool)
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* A report with nothing in it reads as "no data", not as an empty chart. The
|
|
616
|
+
* reader may have used the HTTP sink or a worker against an empty history, so
|
|
617
|
+
* the empty-state detail must not claim the sink is running.
|
|
618
|
+
*/
|
|
619
|
+
function isEmpty(report) {
|
|
620
|
+
return report.totals.totalTokens === 0 && report.groups.length === 0 && report.tools.length === 0;
|
|
621
|
+
}
|
|
622
|
+
function createLogHubApi(options = {}) {
|
|
623
|
+
const app = new Hono();
|
|
624
|
+
const identity = {
|
|
625
|
+
packageName: PACKAGE_NAME$1,
|
|
626
|
+
serviceName: "pi",
|
|
627
|
+
...options.env === void 0 ? {} : { env: options.env }
|
|
628
|
+
};
|
|
629
|
+
const source = options.source ?? createMetricsSource(identity);
|
|
630
|
+
const issues = options.issues ?? createIssuesSource(identity);
|
|
631
|
+
app.get(apiRoutes_default.metrics.path, async (context) => {
|
|
632
|
+
const requestedDimension = context.req.query(METRICS_QUERY_PARAMS.dimension) ?? DEFAULT_DIMENSION;
|
|
633
|
+
const requestedPeriod = context.req.query(METRICS_QUERY_PARAMS.period) ?? DEFAULT_PERIOD;
|
|
634
|
+
if (!isMetricsDimension(requestedDimension)) return context.json({ error: `Unknown dimension '${requestedDimension}'.` }, 400);
|
|
635
|
+
if (!isMetricsPeriod(requestedPeriod)) return context.json({ error: `Unknown period '${requestedPeriod}'.` }, 400);
|
|
636
|
+
const focus = context.req.query(METRICS_QUERY_PARAMS.focus);
|
|
637
|
+
const filter = focus === void 0 || focus === "" ? void 0 : { [FOCUS_FIELD[requestedDimension]]: focus };
|
|
638
|
+
let report;
|
|
639
|
+
try {
|
|
640
|
+
report = await source.query({
|
|
641
|
+
groupBy: SINK_GROUP_BY[requestedDimension],
|
|
642
|
+
period: requestedPeriod,
|
|
643
|
+
limit: 20,
|
|
644
|
+
toolLimit: 15,
|
|
645
|
+
...filter === void 0 ? {} : { filter }
|
|
646
|
+
});
|
|
647
|
+
} catch (error) {
|
|
648
|
+
const unavailable = {
|
|
649
|
+
unavailable: "query-error",
|
|
650
|
+
detail: error instanceof Error ? error.message : "The metrics query failed."
|
|
651
|
+
};
|
|
652
|
+
return context.json(unavailable);
|
|
653
|
+
}
|
|
654
|
+
const body = toReport(report, source, requestedDimension);
|
|
655
|
+
if (isEmpty(body)) return context.json({
|
|
656
|
+
unavailable: "no-data",
|
|
657
|
+
detail: "No recorded usage was found for this period."
|
|
658
|
+
});
|
|
659
|
+
return context.json(body);
|
|
660
|
+
});
|
|
661
|
+
/**
|
|
662
|
+
* The detail behind the count the report carries. Separate from /metrics
|
|
663
|
+
* because it costs a subprocess: the running daemon has no issues route, so
|
|
664
|
+
* folding it into every report would make the whole page as slow as its
|
|
665
|
+
* slowest transport.
|
|
666
|
+
*/
|
|
667
|
+
app.get(apiRoutes_default.issues.path, async (context) => {
|
|
668
|
+
const focus = context.req.query(METRICS_QUERY_PARAMS.focus);
|
|
669
|
+
const startTime = context.req.query("startTime");
|
|
670
|
+
const endTime = context.req.query("endTime");
|
|
671
|
+
const validTime = (value) => value === void 0 || /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) && Number.isFinite(Date.parse(value));
|
|
672
|
+
if (!validTime(startTime) || !validTime(endTime) || startTime !== void 0 && endTime !== void 0 && Date.parse(startTime) > Date.parse(endTime)) return context.json({ error: "Issue time bounds must be valid ISO instants in chronological order." }, 400);
|
|
673
|
+
try {
|
|
674
|
+
const report = await issues.query({
|
|
675
|
+
limit: 50,
|
|
676
|
+
...startTime === void 0 ? {} : { startTime },
|
|
677
|
+
...endTime === void 0 ? {} : { endTime },
|
|
678
|
+
...focus === void 0 || focus === "" ? {} : { sessionId: focus }
|
|
679
|
+
});
|
|
680
|
+
return context.json(report);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
const unavailable = {
|
|
683
|
+
unavailable: "query-error",
|
|
684
|
+
detail: error instanceof Error ? error.message : "The issues query failed."
|
|
685
|
+
};
|
|
686
|
+
return context.json(unavailable);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
return app;
|
|
690
|
+
}
|
|
691
|
+
//#endregion
|
|
692
|
+
//#region src/extensions/(backend)/api/log/route.server.ts
|
|
693
|
+
var route_server_default = defineRoute({
|
|
694
|
+
basePath: "log",
|
|
695
|
+
start(context) {
|
|
696
|
+
const env = { ...context.environment };
|
|
697
|
+
const source = createMetricsSource({
|
|
698
|
+
env,
|
|
699
|
+
packageName: PACKAGE_NAME$1,
|
|
700
|
+
serviceName: "pi"
|
|
701
|
+
});
|
|
702
|
+
const app = createLogHubApi({
|
|
703
|
+
source,
|
|
704
|
+
env
|
|
705
|
+
});
|
|
706
|
+
return {
|
|
707
|
+
fetch: (request) => app.fetch(request),
|
|
708
|
+
close: () => source.close?.()
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region src/extensions/workspaces/sessions/(backend)/command/log-metrics.server.ts
|
|
714
|
+
var log_metrics_server_default = defineServerCommand((context) => context.root.commands[0]);
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/agent-end.server.ts
|
|
717
|
+
var agent_end_server_default = defineHook((context) => context.root.hooks[9]);
|
|
718
|
+
//#endregion
|
|
719
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/agent-settled.server.ts
|
|
720
|
+
var agent_settled_server_default = defineHook((context) => context.root.hooks[1]);
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/agent-start.server.ts
|
|
723
|
+
var agent_start_server_default = defineHook((context) => context.root.hooks[7]);
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/model-select.server.ts
|
|
726
|
+
var model_select_server_default = defineHook((context) => context.root.hooks[8]);
|
|
727
|
+
//#endregion
|
|
728
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.ts
|
|
729
|
+
var session_shutdown_server_default = defineHook((context) => context.root.hooks[2]);
|
|
730
|
+
//#endregion
|
|
731
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/session-start.server.ts
|
|
732
|
+
var session_start_server_default = defineHook((context) => context.root.hooks[0]);
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts
|
|
735
|
+
var tool_execution_end_server_default = defineHook((context) => context.root.hooks[6]);
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server.ts
|
|
738
|
+
var tool_execution_start_server_default = defineHook((context) => context.root.hooks[5]);
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/turn-end.server.ts
|
|
741
|
+
var turn_end_server_default = defineHook((context) => context.root.hooks[4]);
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/turn-start.server.ts
|
|
744
|
+
var turn_start_server_default = defineHook((context) => context.root.hooks[3]);
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/constants/diagnostics.ts
|
|
747
|
+
const DIAGNOSE_AGENT_NAME = "diagnose_agent";
|
|
748
|
+
const DIAGNOSE_AGENT_DESCRIPTION = "Inspect captured failures and token usage for the current Po session. Read-only, bounded to the last day or week; omits prompts, tool arguments, credentials, and unrelated sessions.";
|
|
749
|
+
const AGENT_DIAGNOSTIC_TIMEOUT_MS = 15e3;
|
|
750
|
+
const AGENT_ISSUE_CATEGORIES = [
|
|
751
|
+
"tool_failure",
|
|
752
|
+
"infrastructure_failure",
|
|
753
|
+
"caller_error",
|
|
754
|
+
"expected_failure",
|
|
755
|
+
"page_state",
|
|
756
|
+
"api_error",
|
|
757
|
+
"api_refusal",
|
|
758
|
+
"api_retries_exhausted",
|
|
759
|
+
"tool_rejected",
|
|
760
|
+
"log_error"
|
|
761
|
+
];
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/extensions/workspaces/sessions/(backend)/resource/po-debug-agent.server.ts
|
|
764
|
+
var po_debug_agent_server_default = defineResource({
|
|
765
|
+
name: "po-debug-agent",
|
|
766
|
+
description: "Debug a Po agent using session-scoped failure and usage evidence. Distinguish missing telemetry from a healthy run, diagnose expensive or failed tools, and investigate deeper traces without exposing secrets or changing the sink.",
|
|
767
|
+
when: DOOM_HELP_WHEN,
|
|
768
|
+
kind: "skill",
|
|
769
|
+
path: packageResourcePath(import.meta.url, "src/prompts/po-debug-agent/SKILL.md"),
|
|
770
|
+
read: () => readPackageResource(import.meta.url, "src/prompts/po-debug-agent/SKILL.md")
|
|
771
|
+
});
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/schemas/agentDiagnostics.ts
|
|
774
|
+
const count = Type.Number({ minimum: 0 });
|
|
775
|
+
const coverage = Type.Number({
|
|
776
|
+
minimum: 0,
|
|
777
|
+
maximum: 100
|
|
778
|
+
});
|
|
779
|
+
const agentDiagnosticParameters = Type.Object({
|
|
780
|
+
period: Type.Optional(Type.Union([Type.Literal("day"), Type.Literal("week")])),
|
|
781
|
+
toolLimit: Type.Optional(Type.Integer({
|
|
782
|
+
minimum: 1,
|
|
783
|
+
maximum: 20
|
|
784
|
+
}))
|
|
785
|
+
}, { additionalProperties: false });
|
|
786
|
+
/** Validate only the fields we return. Extra upstream fields never cross this boundary. */
|
|
787
|
+
const scopedMetricsSchema = Type.Object({
|
|
788
|
+
groupBy: Type.Literal("session"),
|
|
789
|
+
period: Type.Union([Type.Literal("day"), Type.Literal("week")]),
|
|
790
|
+
filters: Type.Object({ sessionId: Type.String({ minLength: 1 }) }),
|
|
791
|
+
totals: Type.Object({
|
|
792
|
+
totalRecords: count,
|
|
793
|
+
issueCount: count,
|
|
794
|
+
usageEventCount: count,
|
|
795
|
+
inputTokens: count,
|
|
796
|
+
outputTokens: count,
|
|
797
|
+
totalTokens: count,
|
|
798
|
+
cachedInputTokens: count,
|
|
799
|
+
reasoningOutputTokens: count,
|
|
800
|
+
tokenCoveragePercent: coverage
|
|
801
|
+
}),
|
|
802
|
+
groups: Type.Array(Type.Object({
|
|
803
|
+
sessionId: Type.String({ minLength: 1 }),
|
|
804
|
+
byCategory: Type.Record(Type.String(), count)
|
|
805
|
+
}), { maxItems: 1 }),
|
|
806
|
+
tools: Type.Object({
|
|
807
|
+
returnedTools: count,
|
|
808
|
+
rows: Type.Array(Type.Object({
|
|
809
|
+
toolName: Type.String({
|
|
810
|
+
minLength: 1,
|
|
811
|
+
maxLength: 128,
|
|
812
|
+
pattern: "^[A-Za-z0-9_./:-]+$"
|
|
813
|
+
}),
|
|
814
|
+
invocationCount: count,
|
|
815
|
+
toolCallTurn: Type.Object({
|
|
816
|
+
samples: count,
|
|
817
|
+
avgTotalTokens: count,
|
|
818
|
+
p90TotalTokens: count
|
|
819
|
+
})
|
|
820
|
+
}), { maxItems: 20 })
|
|
821
|
+
})
|
|
822
|
+
});
|
|
823
|
+
//#endregion
|
|
824
|
+
//#region src/services/agentDiagnostics/index.ts
|
|
825
|
+
async function readAgentDiagnostics(source, sessionId, input, signal) {
|
|
826
|
+
const period = input.period ?? "day";
|
|
827
|
+
const limit = input.toolLimit ?? 10;
|
|
828
|
+
signal.throwIfAborted();
|
|
829
|
+
let onAbort = () => {};
|
|
830
|
+
try {
|
|
831
|
+
const report = await Promise.race([source.query({
|
|
832
|
+
groupBy: "session",
|
|
833
|
+
period,
|
|
834
|
+
limit: 1,
|
|
835
|
+
toolLimit: limit,
|
|
836
|
+
filter: { sessionId }
|
|
837
|
+
}), new Promise((_resolve, reject) => {
|
|
838
|
+
onAbort = () => reject(signal.reason);
|
|
839
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
840
|
+
if (signal.aborted) onAbort();
|
|
841
|
+
})]);
|
|
842
|
+
signal.throwIfAborted();
|
|
843
|
+
if (!Check(scopedMetricsSchema, report) || report.filters.sessionId !== sessionId || report.period !== period || report.groups.some((group) => group.sessionId !== sessionId)) return {
|
|
844
|
+
evidence: "unavailable",
|
|
845
|
+
code: "METRICS_SCOPE_NOT_CONFIRMED",
|
|
846
|
+
scope: "current-session",
|
|
847
|
+
period
|
|
848
|
+
};
|
|
849
|
+
const totals = report.totals;
|
|
850
|
+
return {
|
|
851
|
+
scope: "current-session",
|
|
852
|
+
period,
|
|
853
|
+
evidence: totals.totalRecords === 0 ? "empty" : "available",
|
|
854
|
+
transport: source.lastTransport(),
|
|
855
|
+
capturedRecords: totals.totalRecords,
|
|
856
|
+
capturedIssues: totals.issueCount,
|
|
857
|
+
issuesByCategory: Object.fromEntries(AGENT_ISSUE_CATEGORIES.map((category) => [category, report.groups[0]?.byCategory[category] ?? 0])),
|
|
858
|
+
usage: {
|
|
859
|
+
inputTokens: totals.inputTokens,
|
|
860
|
+
outputTokens: totals.outputTokens,
|
|
861
|
+
totalTokens: totals.totalTokens,
|
|
862
|
+
cachedInputTokens: totals.cachedInputTokens,
|
|
863
|
+
reasoningOutputTokens: totals.reasoningOutputTokens,
|
|
864
|
+
usageEventCount: totals.usageEventCount,
|
|
865
|
+
tokenCoveragePercent: totals.tokenCoveragePercent
|
|
866
|
+
},
|
|
867
|
+
tools: report.tools.rows.slice(0, limit).map((tool) => ({
|
|
868
|
+
name: tool.toolName,
|
|
869
|
+
invocations: tool.invocationCount,
|
|
870
|
+
sampledTurns: tool.toolCallTurn.samples,
|
|
871
|
+
averageTurnTokens: tool.toolCallTurn.avgTotalTokens,
|
|
872
|
+
p90TurnTokens: tool.toolCallTurn.p90TotalTokens
|
|
873
|
+
})),
|
|
874
|
+
toolRowLimit: limit,
|
|
875
|
+
limitReached: report.tools.rows.length >= limit,
|
|
876
|
+
captureCompleteness: "not-reported",
|
|
877
|
+
limitations: "Only captured records in this window are represented. Empty data is not proof of a healthy run. Token coverage is not event-capture coverage; per-tool token samples describe the containing turn, not exclusive tool cost. Raw prompts, error messages, tool arguments and traces are omitted. Use po-debug-agent for deeper authorized investigation."
|
|
878
|
+
};
|
|
879
|
+
} catch (error) {
|
|
880
|
+
if (signal.aborted) throw error;
|
|
881
|
+
return {
|
|
882
|
+
evidence: "unavailable",
|
|
883
|
+
code: "METRICS_UNAVAILABLE",
|
|
884
|
+
scope: "current-session",
|
|
885
|
+
period
|
|
886
|
+
};
|
|
887
|
+
} finally {
|
|
888
|
+
signal.removeEventListener("abort", onAbort);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function createAgentDiagnosticsTool(readScope, authorize) {
|
|
892
|
+
return defineTool({
|
|
893
|
+
name: DIAGNOSE_AGENT_NAME,
|
|
894
|
+
description: DIAGNOSE_AGENT_DESCRIPTION,
|
|
895
|
+
parameters: agentDiagnosticParameters,
|
|
896
|
+
when: DOOM_HELP_WHEN,
|
|
897
|
+
executionMode: "serial",
|
|
898
|
+
async execute(input, execution) {
|
|
899
|
+
const active = authorize(execution.signal);
|
|
900
|
+
const signal = AbortSignal.any([active, AbortSignal.timeout(AGENT_DIAGNOSTIC_TIMEOUT_MS)]);
|
|
901
|
+
const scope = readScope();
|
|
902
|
+
if (!scope.sessionId) throw new Error("Agent diagnostics require an active session.");
|
|
903
|
+
const source = createMetricsSource({
|
|
904
|
+
packageName: PACKAGE_NAME$1,
|
|
905
|
+
serviceName: "pi",
|
|
906
|
+
...scope.options
|
|
907
|
+
});
|
|
908
|
+
try {
|
|
909
|
+
const report = await readAgentDiagnostics(source, scope.sessionId, input, signal);
|
|
910
|
+
signal.throwIfAborted();
|
|
911
|
+
authorize(execution.signal);
|
|
912
|
+
return {
|
|
913
|
+
content: [{
|
|
914
|
+
type: "text",
|
|
915
|
+
text: JSON.stringify(report, null, 2)
|
|
916
|
+
}],
|
|
917
|
+
details: report
|
|
918
|
+
};
|
|
919
|
+
} finally {
|
|
920
|
+
await source.close?.();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool/diagnose_agent.server.ts
|
|
927
|
+
var diagnose_agent_server_default = defineTool$1(({ agent, signal: ownerSignal }) => {
|
|
928
|
+
if (!agent) throw new Error("Agent diagnostics require a session host.");
|
|
929
|
+
return createAgentDiagnosticsTool(() => ({
|
|
930
|
+
sessionId: agent.context.sessionId,
|
|
931
|
+
options: {
|
|
932
|
+
cwd: agent.context.cwd,
|
|
933
|
+
env: {
|
|
934
|
+
...agent.context.environment,
|
|
935
|
+
PI_SESSION_ID: agent.context.sessionId
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}), (signal) => {
|
|
939
|
+
ownerSignal.throwIfAborted();
|
|
940
|
+
signal?.throwIfAborted();
|
|
941
|
+
agent.assertActive(PACKAGE_NAME$1);
|
|
942
|
+
if (!(agent.context.selection.state?.["minor-mode"] ?? []).includes("help")) throw new Error("Help mode is not active.");
|
|
943
|
+
return signal ? AbortSignal.any([signal, ownerSignal]) : ownerSignal;
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region generated/server.ts
|
|
948
|
+
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
949
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
950
|
+
...identity,
|
|
951
|
+
...value
|
|
952
|
+
};
|
|
953
|
+
const facet = defineServerPlugin({
|
|
954
|
+
name: "@agimon-ai/po-log",
|
|
955
|
+
providerOverrides: composeServerProviderOverrides(root_server_default),
|
|
956
|
+
global: (context) => ({ get api() {
|
|
957
|
+
return [at(route_server_default, context)];
|
|
958
|
+
} }),
|
|
959
|
+
workspace: (context) => ({ get api() {
|
|
960
|
+
return [at(route_server_default, context)];
|
|
961
|
+
} }),
|
|
962
|
+
session: async (context) => {
|
|
963
|
+
const scopeSession = await root_server_default(context);
|
|
964
|
+
const contextSession = {
|
|
965
|
+
...context,
|
|
966
|
+
root: scopeSession.value
|
|
967
|
+
};
|
|
968
|
+
return {
|
|
969
|
+
...composeRootHooks(scopeSession),
|
|
970
|
+
get api() {
|
|
971
|
+
return [at(route_server_default, context)];
|
|
972
|
+
},
|
|
973
|
+
get commands() {
|
|
974
|
+
return [via({ name: "log-metrics" }, at(log_metrics_server_default, contextSession))];
|
|
975
|
+
},
|
|
976
|
+
get hooks() {
|
|
977
|
+
return [
|
|
978
|
+
via({ event: "agent_end" }, at(agent_end_server_default, contextSession)),
|
|
979
|
+
via({ event: "agent_settled" }, at(agent_settled_server_default, contextSession)),
|
|
980
|
+
via({ event: "agent_start" }, at(agent_start_server_default, contextSession)),
|
|
981
|
+
via({ event: "model_select" }, at(model_select_server_default, contextSession)),
|
|
982
|
+
via({ event: "session_shutdown" }, at(session_shutdown_server_default, contextSession)),
|
|
983
|
+
via({ event: "session_start" }, at(session_start_server_default, contextSession)),
|
|
984
|
+
via({ event: "tool_execution_end" }, at(tool_execution_end_server_default, contextSession)),
|
|
985
|
+
via({ event: "tool_execution_start" }, at(tool_execution_start_server_default, contextSession)),
|
|
986
|
+
via({ event: "turn_end" }, at(turn_end_server_default, contextSession)),
|
|
987
|
+
via({ event: "turn_start" }, at(turn_start_server_default, contextSession))
|
|
988
|
+
];
|
|
989
|
+
},
|
|
990
|
+
get resources() {
|
|
991
|
+
return [at(po_debug_agent_server_default, contextSession)];
|
|
992
|
+
},
|
|
993
|
+
get tools() {
|
|
994
|
+
return [via({ name: "diagnose_agent" }, at(diagnose_agent_server_default, contextSession))];
|
|
995
|
+
},
|
|
996
|
+
services: [...scopeSession.services ?? []],
|
|
997
|
+
get activities() {
|
|
998
|
+
return [...scopeSession.activities ?? []];
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
//#endregion
|
|
1004
|
+
export { facet as default, facet };
|
|
1005
|
+
|
|
1006
|
+
//# sourceMappingURL=server.mjs.map
|