execbro 1.10.24 → 1.11.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/README.md +1 -1
- package/build/__tests__/helpers/fake-cdp-server.d.ts +9 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -1
- package/build/__tests__/helpers/fake-cdp-server.js +20 -2
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -1
- package/build/__tests__/integration/connection-health.test.js +8 -1
- package/build/__tests__/integration/connection-health.test.js.map +1 -1
- package/build/__tests__/integration/native-logs.test.d.ts +2 -0
- package/build/__tests__/integration/native-logs.test.d.ts.map +1 -0
- package/build/__tests__/integration/native-logs.test.js +39 -0
- package/build/__tests__/integration/native-logs.test.js.map +1 -0
- package/build/__tests__/integration/socket-close-midflight.test.d.ts +2 -0
- package/build/__tests__/integration/socket-close-midflight.test.d.ts.map +1 -0
- package/build/__tests__/integration/socket-close-midflight.test.js +106 -0
- package/build/__tests__/integration/socket-close-midflight.test.js.map +1 -0
- package/build/__tests__/integration/stale-target-probe.test.d.ts +2 -0
- package/build/__tests__/integration/stale-target-probe.test.d.ts.map +1 -0
- package/build/__tests__/integration/stale-target-probe.test.js +85 -0
- package/build/__tests__/integration/stale-target-probe.test.js.map +1 -0
- package/build/__tests__/unit/clearLogsNative.test.d.ts +2 -0
- package/build/__tests__/unit/clearLogsNative.test.d.ts.map +1 -0
- package/build/__tests__/unit/clearLogsNative.test.js +43 -0
- package/build/__tests__/unit/clearLogsNative.test.js.map +1 -0
- package/build/__tests__/unit/componentFormatNaming.test.d.ts +2 -0
- package/build/__tests__/unit/componentFormatNaming.test.d.ts.map +1 -0
- package/build/__tests__/unit/componentFormatNaming.test.js +32 -0
- package/build/__tests__/unit/componentFormatNaming.test.js.map +1 -0
- package/build/__tests__/unit/deviceResolution.test.d.ts +2 -0
- package/build/__tests__/unit/deviceResolution.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceResolution.test.js +108 -0
- package/build/__tests__/unit/deviceResolution.test.js.map +1 -0
- package/build/__tests__/unit/executor.test.js +48 -12
- package/build/__tests__/unit/executor.test.js.map +1 -1
- package/build/__tests__/unit/getLogsEmptyContract.test.d.ts +2 -0
- package/build/__tests__/unit/getLogsEmptyContract.test.d.ts.map +1 -0
- package/build/__tests__/unit/getLogsEmptyContract.test.js +112 -0
- package/build/__tests__/unit/getLogsEmptyContract.test.js.map +1 -0
- package/build/__tests__/unit/getLogsNativeSource.test.d.ts +2 -0
- package/build/__tests__/unit/getLogsNativeSource.test.d.ts.map +1 -0
- package/build/__tests__/unit/getLogsNativeSource.test.js +138 -0
- package/build/__tests__/unit/getLogsNativeSource.test.js.map +1 -0
- package/build/__tests__/unit/jsLogEvents.test.d.ts +2 -0
- package/build/__tests__/unit/jsLogEvents.test.d.ts.map +1 -0
- package/build/__tests__/unit/jsLogEvents.test.js +58 -0
- package/build/__tests__/unit/jsLogEvents.test.js.map +1 -0
- package/build/__tests__/unit/logDiagnosis.test.d.ts +2 -0
- package/build/__tests__/unit/logDiagnosis.test.d.ts.map +1 -0
- package/build/__tests__/unit/logDiagnosis.test.js +81 -0
- package/build/__tests__/unit/logDiagnosis.test.js.map +1 -0
- package/build/__tests__/unit/logEventFormat.test.d.ts +2 -0
- package/build/__tests__/unit/logEventFormat.test.d.ts.map +1 -0
- package/build/__tests__/unit/logEventFormat.test.js +210 -0
- package/build/__tests__/unit/logEventFormat.test.js.map +1 -0
- package/build/__tests__/unit/logEvents.test.d.ts +2 -0
- package/build/__tests__/unit/logEvents.test.d.ts.map +1 -0
- package/build/__tests__/unit/logEvents.test.js +118 -0
- package/build/__tests__/unit/logEvents.test.js.map +1 -0
- package/build/__tests__/unit/logGrouping.test.d.ts +2 -0
- package/build/__tests__/unit/logGrouping.test.d.ts.map +1 -0
- package/build/__tests__/unit/logGrouping.test.js +104 -0
- package/build/__tests__/unit/logGrouping.test.js.map +1 -0
- package/build/__tests__/unit/logOwnership.test.d.ts +2 -0
- package/build/__tests__/unit/logOwnership.test.d.ts.map +1 -0
- package/build/__tests__/unit/logOwnership.test.js +118 -0
- package/build/__tests__/unit/logOwnership.test.js.map +1 -0
- package/build/__tests__/unit/logRelevance.test.d.ts +2 -0
- package/build/__tests__/unit/logRelevance.test.d.ts.map +1 -0
- package/build/__tests__/unit/logRelevance.test.js +30 -0
- package/build/__tests__/unit/logRelevance.test.js.map +1 -0
- package/build/__tests__/unit/logSourceAndroid.test.d.ts +2 -0
- package/build/__tests__/unit/logSourceAndroid.test.d.ts.map +1 -0
- package/build/__tests__/unit/logSourceAndroid.test.js +61 -0
- package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -0
- package/build/__tests__/unit/logSourceIos.test.d.ts +2 -0
- package/build/__tests__/unit/logSourceIos.test.d.ts.map +1 -0
- package/build/__tests__/unit/logSourceIos.test.js +64 -0
- package/build/__tests__/unit/logSourceIos.test.js.map +1 -0
- package/build/__tests__/unit/logs.test.js +23 -0
- package/build/__tests__/unit/logs.test.js.map +1 -1
- package/build/__tests__/unit/nativeLogs.test.d.ts +2 -0
- package/build/__tests__/unit/nativeLogs.test.d.ts.map +1 -0
- package/build/__tests__/unit/nativeLogs.test.js +230 -0
- package/build/__tests__/unit/nativeLogs.test.js.map +1 -0
- package/build/__tests__/unit/pendingExecutions.test.d.ts +2 -0
- package/build/__tests__/unit/pendingExecutions.test.d.ts.map +1 -0
- package/build/__tests__/unit/pendingExecutions.test.js +70 -0
- package/build/__tests__/unit/pendingExecutions.test.js.map +1 -0
- package/build/__tests__/unit/reloadAppAutoConnect.test.d.ts +2 -0
- package/build/__tests__/unit/reloadAppAutoConnect.test.d.ts.map +1 -0
- package/build/__tests__/unit/reloadAppAutoConnect.test.js +63 -0
- package/build/__tests__/unit/reloadAppAutoConnect.test.js.map +1 -0
- package/build/__tests__/unit/toolsJson.test.js +11 -0
- package/build/__tests__/unit/toolsJson.test.js.map +1 -1
- package/build/__tests__/unit/transportClassify.test.js +108 -7
- package/build/__tests__/unit/transportClassify.test.js.map +1 -1
- package/build/core/componentSearch.d.ts +1 -1
- package/build/core/componentSearch.d.ts.map +1 -1
- package/build/core/componentSearch.js +8 -8
- package/build/core/componentSearch.js.map +1 -1
- package/build/core/componentTree.d.ts +1 -1
- package/build/core/componentTree.d.ts.map +1 -1
- package/build/core/componentTree.js +8 -8
- package/build/core/componentTree.js.map +1 -1
- package/build/core/connection.d.ts +30 -0
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +97 -28
- package/build/core/connection.js.map +1 -1
- package/build/core/debugGlobals.d.ts.map +1 -1
- package/build/core/debugGlobals.js +89 -32
- package/build/core/debugGlobals.js.map +1 -1
- package/build/core/errors.d.ts +7 -1
- package/build/core/errors.d.ts.map +1 -1
- package/build/core/errors.js +8 -1
- package/build/core/errors.js.map +1 -1
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +25 -0
- package/build/core/guides.js.map +1 -1
- package/build/core/index.d.ts +0 -2
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +0 -2
- package/build/core/index.js.map +1 -1
- package/build/core/jsExecute.d.ts +76 -1
- package/build/core/jsExecute.d.ts.map +1 -1
- package/build/core/jsExecute.js +193 -32
- package/build/core/jsExecute.js.map +1 -1
- package/build/core/jsLogEvents.d.ts +13 -0
- package/build/core/jsLogEvents.d.ts.map +1 -0
- package/build/core/jsLogEvents.js +65 -0
- package/build/core/jsLogEvents.js.map +1 -0
- package/build/core/logDiagnosis.d.ts +65 -0
- package/build/core/logDiagnosis.d.ts.map +1 -0
- package/build/core/logDiagnosis.js +51 -0
- package/build/core/logDiagnosis.js.map +1 -0
- package/build/core/logEventFormat.d.ts +14 -0
- package/build/core/logEventFormat.d.ts.map +1 -0
- package/build/core/logEventFormat.js +85 -0
- package/build/core/logEventFormat.js.map +1 -0
- package/build/core/logEvents.d.ts +115 -0
- package/build/core/logEvents.d.ts.map +1 -0
- package/build/core/logEvents.js +146 -0
- package/build/core/logEvents.js.map +1 -0
- package/build/core/logGrouping.d.ts +17 -0
- package/build/core/logGrouping.d.ts.map +1 -0
- package/build/core/logGrouping.js +134 -0
- package/build/core/logGrouping.js.map +1 -0
- package/build/core/logOwnership.d.ts +29 -0
- package/build/core/logOwnership.d.ts.map +1 -0
- package/build/core/logOwnership.js +83 -0
- package/build/core/logOwnership.js.map +1 -0
- package/build/core/logRelevance.d.ts +13 -0
- package/build/core/logRelevance.d.ts.map +1 -0
- package/build/core/logRelevance.js +23 -0
- package/build/core/logRelevance.js.map +1 -0
- package/build/core/logSourceAndroid.d.ts +27 -0
- package/build/core/logSourceAndroid.d.ts.map +1 -0
- package/build/core/logSourceAndroid.js +89 -0
- package/build/core/logSourceAndroid.js.map +1 -0
- package/build/core/logSourceIos.d.ts +26 -0
- package/build/core/logSourceIos.d.ts.map +1 -0
- package/build/core/logSourceIos.js +119 -0
- package/build/core/logSourceIos.js.map +1 -0
- package/build/core/logs.d.ts +5 -1
- package/build/core/logs.d.ts.map +1 -1
- package/build/core/logs.js +8 -1
- package/build/core/logs.js.map +1 -1
- package/build/core/nativeLogs.d.ts +75 -0
- package/build/core/nativeLogs.d.ts.map +1 -0
- package/build/core/nativeLogs.js +260 -0
- package/build/core/nativeLogs.js.map +1 -0
- package/build/core/register.d.ts.map +1 -1
- package/build/core/register.js +22 -6
- package/build/core/register.js.map +1 -1
- package/build/core/screenLayout.d.ts +1 -1
- package/build/core/screenLayout.d.ts.map +1 -1
- package/build/core/screenLayout.js +3 -3
- package/build/core/screenLayout.js.map +1 -1
- package/build/core/state.d.ts +10 -0
- package/build/core/state.d.ts.map +1 -1
- package/build/core/state.js +21 -0
- package/build/core/state.js.map +1 -1
- package/build/core/types.d.ts +11 -2
- package/build/core/types.d.ts.map +1 -1
- package/build/tools/componentTools.js +6 -6
- package/build/tools/componentTools.js.map +1 -1
- package/build/tools/connectionTools.d.ts.map +1 -1
- package/build/tools/connectionTools.js +7 -3
- package/build/tools/connectionTools.js.map +1 -1
- package/build/tools/executionTools.js +7 -7
- package/build/tools/executionTools.js.map +1 -1
- package/build/tools/logTools.d.ts +25 -0
- package/build/tools/logTools.d.ts.map +1 -1
- package/build/tools/logTools.js +316 -132
- package/build/tools/logTools.js.map +1 -1
- package/build/tools/networkTools.d.ts.map +1 -1
- package/build/tools/networkTools.js +65 -54
- package/build/tools/networkTools.js.map +1 -1
- package/package.json +5 -4
- package/tools.json +447 -0
- package/build/core/format.d.ts +0 -8
- package/build/core/format.d.ts.map +0 -1
- package/build/core/format.js +0 -34
- package/build/core/format.js.map +0 -1
package/build/tools/logTools.js
CHANGED
|
@@ -1,14 +1,120 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { registerToolWithTelemetry } from "../core/register.js";
|
|
3
|
-
import { getLogs, searchLogs, getLogSummary, getTotalLogCount, getConnectedAppByDevice, getFirstConnectedApp,
|
|
3
|
+
import { getLogs, searchLogs, getLogSummary, getTotalLogCount, getConnectedAppByDevice, getFirstConnectedApp, checkAndEnsureConnection, metroMissingHintIfAbsent, logBuffers, verifyLogPipeline, getPassiveConnectionStatus, connectedApps, getRecentGaps, formatDuration, getLogBuffer, } from "../core/index.js";
|
|
4
4
|
import { resolveLogBuffer } from "../core/toolHelpers.js";
|
|
5
|
+
import { diagnoseEmptyLogs } from "../core/logDiagnosis.js";
|
|
5
6
|
import { UserInputError } from "../core/errors.js";
|
|
6
7
|
import { isSDKInstalled, querySDKConsole, clearSDKConsole, getSDKConsoleStats, } from "../core/sdkBridge.js";
|
|
8
|
+
import { findLogEvent, nativeLogBuffers, getNativeLogBuffer } from "../core/logEvents.js";
|
|
9
|
+
import { jsEventsFromEntries } from "../core/jsLogEvents.js";
|
|
10
|
+
import { formatEventList, formatEventDetails } from "../core/logEventFormat.js";
|
|
11
|
+
import { collectNativeEvents, deviceKeyOf } from "../core/nativeLogs.js";
|
|
12
|
+
// Binds the live connection/pipeline probes for `diagnoseEmptyLogs`. Kept here
|
|
13
|
+
// so the diagnosis logic itself stays free of module-level state and testable.
|
|
14
|
+
function logDiagnosisDeps(device) {
|
|
15
|
+
return {
|
|
16
|
+
checkConnection: () => checkAndEnsureConnection(device),
|
|
17
|
+
resolveApp: () => (device ? getConnectedAppByDevice(device) : getFirstConnectedApp()),
|
|
18
|
+
verifyPipeline: verifyLogPipeline,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse "5m" / "90s" / an ISO timestamp into an absolute Date.
|
|
23
|
+
*
|
|
24
|
+
* Distinguishes "not provided" from "provided but unparseable" — the latter
|
|
25
|
+
* silently falling back to the default watermark would look, from the
|
|
26
|
+
* caller's side, like their filter was honored when it was actually ignored.
|
|
27
|
+
*/
|
|
28
|
+
function parseSince(since) {
|
|
29
|
+
if (!since)
|
|
30
|
+
return { date: undefined };
|
|
31
|
+
const rel = since.match(/^(\d+)([smh])$/);
|
|
32
|
+
if (rel) {
|
|
33
|
+
const mult = { s: 1000, m: 60_000, h: 3_600_000 }[rel[2]];
|
|
34
|
+
return { date: new Date(Date.now() - Number(rel[1]) * mult) };
|
|
35
|
+
}
|
|
36
|
+
const parsed = new Date(since);
|
|
37
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
38
|
+
return {
|
|
39
|
+
date: undefined,
|
|
40
|
+
note: `[NOTE] since="${since}" was not recognized (use an ISO timestamp or a duration like "5m"/"90s"/"2h") — showing the default window instead.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { date: parsed };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Bound the events shown on the native/merged path to `maxLogs`, applied
|
|
47
|
+
* AFTER kind-filtering and keeping the newest — when investigating a crash
|
|
48
|
+
* the latest one is the subject. Never truncates silently: an agent that
|
|
49
|
+
* cannot see the list was capped will assume it has the full picture and
|
|
50
|
+
* reason from a partial one. Exported (not a `__`-prefixed test hook) because
|
|
51
|
+
* it is a genuinely reusable, side-effect-free piece of the response shape.
|
|
52
|
+
*/
|
|
53
|
+
export function capEventsForDisplay(events, maxLogs) {
|
|
54
|
+
const capped = maxLogs > 0 && events.length > maxLogs ? events.slice(-maxLogs) : events;
|
|
55
|
+
const omitted = events.length - capped.length;
|
|
56
|
+
const note = omitted > 0
|
|
57
|
+
? `\n\n[${omitted} older event${omitted === 1 ? "" : "s"} not shown — raise maxLogs or narrow with kind/minLevel/since]`
|
|
58
|
+
: "";
|
|
59
|
+
return { capped, note };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Native acquisition costs a ~1-1.5s subprocess per device, so it must never
|
|
63
|
+
* run on the hot path. It earns its cost in exactly one situation: the JS
|
|
64
|
+
* buffer is empty AND the app is not connected — which is what a native crash
|
|
65
|
+
* looks like from here. Mirrors get_bundle_errors' screenshot fallback, which
|
|
66
|
+
* likewise fires only when the cheap path has demonstrably failed.
|
|
67
|
+
*/
|
|
68
|
+
function shouldEscalateToNative(bufferEmpty, connected) {
|
|
69
|
+
return bufferEmpty && !connected;
|
|
70
|
+
}
|
|
71
|
+
const ESCALATION_COOLDOWN_MS = 30_000;
|
|
72
|
+
/**
|
|
73
|
+
* Negative-result cooldown for the empty-and-disconnected escalation, keyed by
|
|
74
|
+
* the `device` argument that produced the miss.
|
|
75
|
+
*
|
|
76
|
+
* A miss (buffer empty, disconnected, no crash found) still costs a
|
|
77
|
+
* subprocess per device. Without a cooldown, every get_logs call made while
|
|
78
|
+
* the app stays crashed-and-disconnected would re-pay that cost forever. 30s
|
|
79
|
+
* is short enough that a real crash occurring during the cooldown is still
|
|
80
|
+
* caught by the very next get_logs call once it expires, and long enough to
|
|
81
|
+
* stop a tight investigation loop from thrashing the device.
|
|
82
|
+
*
|
|
83
|
+
* Keying matters: a miss for device="A" says nothing about device B, and a
|
|
84
|
+
* process-wide latch would let one device's quiet period suppress another
|
|
85
|
+
* device's real crash. An undefined device queries every device at once, so
|
|
86
|
+
* caching that miss under a single key is correct.
|
|
87
|
+
*
|
|
88
|
+
* Cleared for a key whenever its escalation DOES find crashes, so a genuine
|
|
89
|
+
* crash is never suppressed by a stale miss.
|
|
90
|
+
*/
|
|
91
|
+
const lastEmptyEscalationAt = new Map();
|
|
92
|
+
function escalationKey(device) {
|
|
93
|
+
return device ?? "*";
|
|
94
|
+
}
|
|
95
|
+
function isEscalationCoolingDown(device) {
|
|
96
|
+
const at = lastEmptyEscalationAt.get(escalationKey(device));
|
|
97
|
+
return at !== undefined && Date.now() - at < ESCALATION_COOLDOWN_MS;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Test-only. Exercises the per-device cooldown map directly, so the
|
|
101
|
+
* per-device keying can be pinned without standing up a full get_logs
|
|
102
|
+
* invocation (device connection, CDP session, etc).
|
|
103
|
+
*/
|
|
104
|
+
export const __escalationCooldownTestHooks = {
|
|
105
|
+
isEscalationCoolingDown,
|
|
106
|
+
recordMiss: (device) => {
|
|
107
|
+
lastEmptyEscalationAt.set(escalationKey(device), Date.now());
|
|
108
|
+
},
|
|
109
|
+
reset: () => {
|
|
110
|
+
lastEmptyEscalationAt.clear();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
7
113
|
export function registerLogTools(server) {
|
|
8
114
|
// Tool: Get console logs
|
|
9
115
|
registerToolWithTelemetry(server, "get_logs", {
|
|
10
116
|
description: "Retrieve console logs from connected React Native app. Tip: Use summary=true first for a quick overview (counts by level + last 5 messages), then fetch specific logs as needed.\n" +
|
|
11
|
-
"PURPOSE: Pull captured console output (log/warn/error/info/debug) from the in-memory buffer
|
|
117
|
+
"PURPOSE: Pull captured console output (log/warn/error/info/debug) from the in-memory buffer, and optionally native device logs (crashes, ANRs) via source=\"native\".\n" +
|
|
12
118
|
"WHEN TO USE: Start of any log-driven investigation, verifying a code change picked up via Fast Refresh, or confirming a reported error actually fires.\n" +
|
|
13
119
|
"WORKFLOW: scan_metro -> get_logs(summary=true) -> narrow with search_logs(text=\"...\") or get_logs(level=\"error\") -> clear_logs between reproductions.\n" +
|
|
14
120
|
"LIMITATIONS: Circular buffer (~500 entries). Only captures logs emitted after the app connected; pre-connect logs are lost.\n" +
|
|
@@ -37,24 +143,79 @@ export function registerLogTools(server) {
|
|
|
37
143
|
.optional()
|
|
38
144
|
.default(false)
|
|
39
145
|
.describe("Disable all truncation and return full messages. Tip: Use with lower maxLogs (e.g., 10) to avoid token overload when inspecting large objects."),
|
|
40
|
-
format: z
|
|
41
|
-
.enum(["text", "tonl"])
|
|
42
|
-
.optional()
|
|
43
|
-
.default("tonl")
|
|
44
|
-
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format, ~30-50% smaller)"),
|
|
45
146
|
summary: z
|
|
46
147
|
.boolean()
|
|
47
148
|
.optional()
|
|
48
149
|
.default(false)
|
|
49
150
|
.describe("Return summary statistics instead of full logs (count by level + last 5 messages). Use for quick overview."),
|
|
50
|
-
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
151
|
+
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices."),
|
|
152
|
+
source: z
|
|
153
|
+
.enum(["js", "native", "all"])
|
|
154
|
+
.optional()
|
|
155
|
+
.default("js")
|
|
156
|
+
.describe("Which log stream to read. 'js' (default) = console output over CDP, instant. " +
|
|
157
|
+
"'native' = device logs (Android logcat / iOS os_log) filtered to this app — surfaces crashes, ANRs and OOM kills that never reach JS; costs ~1-1.5s per device. " +
|
|
158
|
+
"'all' = both, merged. When the JS buffer is empty and the app is disconnected, native crash events are consulted automatically."),
|
|
159
|
+
kind: z
|
|
160
|
+
.enum(["crash", "anr", "exception", "lifecycle", "message"])
|
|
161
|
+
.optional()
|
|
162
|
+
.describe("Filter native/merged events by kind. Omit for all kinds."),
|
|
163
|
+
minLevel: z
|
|
164
|
+
.enum(["debug", "info", "warn", "error", "fatal"])
|
|
165
|
+
.optional()
|
|
166
|
+
.default("warn")
|
|
167
|
+
.describe("Relevance floor for native events (default: warn). Crashes and ANRs are always returned regardless. Lower to 'debug' to see native library loading."),
|
|
168
|
+
since: z
|
|
169
|
+
.string()
|
|
170
|
+
.optional()
|
|
171
|
+
.describe("Native acquisition window — ISO timestamp or a duration like \"5m\". Widens the device query; already-seen events are still deduped.")
|
|
51
172
|
}
|
|
52
|
-
}, async ({ maxLogs, level, startFromText, maxMessageLength, verbose,
|
|
173
|
+
}, async ({ maxLogs, level, startFromText, maxMessageLength, verbose, summary, device, source, kind, minLevel, since }) => {
|
|
53
174
|
// Check if SDK is installed — prefer SDK data for richer logs.
|
|
54
175
|
// `device` must be threaded through: without it the SDK bridge reads
|
|
55
176
|
// whichever app happens to be first, so a multi-device session
|
|
56
177
|
// silently answers with the wrong device's buffer.
|
|
57
178
|
const sdkAvailable = await isSDKInstalled(device);
|
|
179
|
+
// Prepended to every return's text below when source==="all".
|
|
180
|
+
let nativePrefix = "";
|
|
181
|
+
// Set when source==="native"/"all" already paid for a native fetch
|
|
182
|
+
// this call, so the empty+disconnected escalation below can reuse
|
|
183
|
+
// it instead of paying twice.
|
|
184
|
+
let nativeEventsForEscalation;
|
|
185
|
+
// Explicit native / merged read — the caller asked for the cost.
|
|
186
|
+
if (source === "native" || source === "all") {
|
|
187
|
+
const sinceParsed = parseSince(since);
|
|
188
|
+
const nativeOpts = { device, minLevel, since: sinceParsed.date };
|
|
189
|
+
const { events, notes } = await collectNativeEvents(nativeOpts);
|
|
190
|
+
nativeEventsForEscalation = events;
|
|
191
|
+
const filtered = kind ? events.filter((e) => e.kind === kind) : events;
|
|
192
|
+
const { capped, note: cappedNote } = capEventsForDisplay(filtered, maxLogs);
|
|
193
|
+
const showDevice = nativeLogBuffers.size > 1;
|
|
194
|
+
const noteText = notes.length > 0 ? `\n\n${notes.join("\n")}` : "";
|
|
195
|
+
const sinceNote = sinceParsed.note ? `\n\n${sinceParsed.note}` : "";
|
|
196
|
+
const rendered = formatEventList(capped, { showDevice, maxLength: maxMessageLength, verbose });
|
|
197
|
+
if (source === "native") {
|
|
198
|
+
// Emptiness is judged on the UNFILTERED events: a `kind`
|
|
199
|
+
// filter matching nothing is a filter outcome, not a
|
|
200
|
+
// capture failure — mirrors the js-buffer `filtered_out`
|
|
201
|
+
// handling below. Capping is a display bound, not a
|
|
202
|
+
// capture/filter outcome, so it plays no part in either
|
|
203
|
+
// signal.
|
|
204
|
+
const nativeEmpty = events.length === 0;
|
|
205
|
+
const nativeEmptyReason = !nativeEmpty && filtered.length === 0 ? "filtered_out" : undefined;
|
|
206
|
+
return {
|
|
207
|
+
_emptyResult: nativeEmpty,
|
|
208
|
+
...(nativeEmptyReason && { _emptyReason: nativeEmptyReason }),
|
|
209
|
+
content: [{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: `Native Log Events (${capped.length}):\n\n${rendered}${cappedNote}${noteText}${sinceNote}\n\nUse get_log_details("<id>") for a full backtrace.`
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// source === "all": fall through so JS output is appended below,
|
|
216
|
+
// carrying the native block with it.
|
|
217
|
+
nativePrefix = `Native Log Events (${capped.length}):\n\n${rendered}${cappedNote}${noteText}${sinceNote}\n\n`;
|
|
218
|
+
}
|
|
58
219
|
if (sdkAvailable) {
|
|
59
220
|
if (summary) {
|
|
60
221
|
const sdkStats = await getSDKConsoleStats(device);
|
|
@@ -69,23 +230,14 @@ export function registerLogTools(server) {
|
|
|
69
230
|
for (const [lvl, cnt] of Object.entries(s.byLevel))
|
|
70
231
|
lines.push(` ${lvl}: ${cnt}`);
|
|
71
232
|
}
|
|
72
|
-
|
|
233
|
+
// Served real SDK data — not empty, regardless of what the
|
|
234
|
+
// (separate) CDP buffer happens to hold.
|
|
235
|
+
return { _emptyResult: false, content: [{ type: "text", text: `${nativePrefix}Log Summary (SDK):\n\n${lines.join("\n")}` }] };
|
|
73
236
|
}
|
|
74
237
|
}
|
|
75
238
|
const sdkResult = await querySDKConsole({ count: maxLogs, level, text: startFromText }, device);
|
|
76
239
|
if (sdkResult.success && sdkResult.data.length > 0) {
|
|
77
240
|
const entries = sdkResult.data;
|
|
78
|
-
if (format === "tonl") {
|
|
79
|
-
const tonlLines = entries.map((e) => {
|
|
80
|
-
const time = new Date(e.timestamp).toLocaleTimeString();
|
|
81
|
-
let msg = e.message;
|
|
82
|
-
if (!verbose && maxMessageLength > 0 && msg.length > maxMessageLength) {
|
|
83
|
-
msg = msg.slice(0, maxMessageLength) + "...";
|
|
84
|
-
}
|
|
85
|
-
return `${time} [${e.level}] ${msg}`;
|
|
86
|
-
});
|
|
87
|
-
return { content: [{ type: "text", text: `Console Logs (${entries.length} entries, SDK):\n\n${tonlLines.join("\n")}` }] };
|
|
88
|
-
}
|
|
89
241
|
const lines = entries.map((e) => {
|
|
90
242
|
const time = new Date(e.timestamp).toLocaleTimeString();
|
|
91
243
|
let msg = e.message;
|
|
@@ -94,99 +246,122 @@ export function registerLogTools(server) {
|
|
|
94
246
|
}
|
|
95
247
|
return `[${time}] [${e.level.toUpperCase()}] ${msg}`;
|
|
96
248
|
});
|
|
97
|
-
return { content: [{ type: "text", text:
|
|
249
|
+
return { _emptyResult: false, content: [{ type: "text", text: `${nativePrefix}Console Logs (${entries.length} entries, SDK):\n\n${lines.join("\n")}` }] };
|
|
98
250
|
}
|
|
99
251
|
}
|
|
100
252
|
// Return summary if requested
|
|
101
253
|
if (summary) {
|
|
102
|
-
const
|
|
254
|
+
const buffer = resolveLogBuffer(device);
|
|
255
|
+
const summaryText = getLogSummary(buffer, { lastN: 5, maxMessageLength: 100 });
|
|
256
|
+
// Judge emptiness by the buffer this summary was actually built
|
|
257
|
+
// from, not the global count across every device.
|
|
258
|
+
const summaryEmpty = buffer.size === 0;
|
|
103
259
|
let connectionWarning = "";
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (targetApp) {
|
|
110
|
-
const pipeline = await verifyLogPipeline(targetApp);
|
|
111
|
-
if (pipeline.message) {
|
|
112
|
-
connectionWarning += `\n\n${pipeline.message}`;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
260
|
+
let emptyReason;
|
|
261
|
+
if (summaryEmpty) {
|
|
262
|
+
const diagnosis = await diagnoseEmptyLogs(logDiagnosisDeps(device));
|
|
263
|
+
connectionWarning = diagnosis.warning;
|
|
264
|
+
emptyReason = diagnosis.reason;
|
|
116
265
|
connectionWarning += await metroMissingHintIfAbsent("get_logs");
|
|
117
266
|
}
|
|
118
267
|
return {
|
|
268
|
+
_emptyResult: summaryEmpty,
|
|
269
|
+
...(emptyReason && { _emptyReason: emptyReason }),
|
|
119
270
|
content: [
|
|
120
271
|
{
|
|
121
272
|
type: "text",
|
|
122
|
-
text:
|
|
273
|
+
text: `${nativePrefix}Log Summary:\n\n${summaryText}${connectionWarning}`
|
|
123
274
|
}
|
|
124
275
|
]
|
|
125
276
|
};
|
|
126
277
|
}
|
|
127
|
-
|
|
278
|
+
// Resolved once: for the all-devices case this copies every buffered
|
|
279
|
+
// entry into a merged buffer, so it is not free to call repeatedly.
|
|
280
|
+
const logBuffer = resolveLogBuffer(device);
|
|
281
|
+
const { logs, count } = getLogs(logBuffer, {
|
|
128
282
|
maxLogs,
|
|
129
283
|
level,
|
|
130
284
|
startFromText,
|
|
131
285
|
maxMessageLength,
|
|
132
286
|
verbose
|
|
133
287
|
});
|
|
288
|
+
// `_emptyResult` measures CAPTURE reliability, not whether this
|
|
289
|
+
// particular call returned rows — see the 2026-03-19 empty-result
|
|
290
|
+
// spec. A filter that matches nothing is a natural outcome, so it is
|
|
291
|
+
// reported via `_emptyReason` (`filtered_out`) without setting the
|
|
292
|
+
// empty flag. Keeps the empty rate comparable across time.
|
|
293
|
+
const bufferEmpty = logBuffer.size === 0;
|
|
134
294
|
// Check connection health
|
|
135
295
|
let connectionWarning = "";
|
|
136
296
|
let emptyReason;
|
|
137
|
-
if (count === 0) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
emptyReason = "
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
if (
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
_emptyReason: "pipeline_recovered",
|
|
171
|
-
content: [{
|
|
172
|
-
type: "text",
|
|
173
|
-
text: `React Native Console Logs (${retryResult.count} entries):\n\n${retryResult.formatted}${connectionWarning}`
|
|
174
|
-
}]
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
297
|
+
if (count === 0 && !bufferEmpty) {
|
|
298
|
+
// Capture is demonstrably working — the level/startFromText filter
|
|
299
|
+
// simply matched nothing. Report that instead of probing the
|
|
300
|
+
// connection and blaming it for a filter miss.
|
|
301
|
+
emptyReason = "filtered_out";
|
|
302
|
+
connectionWarning = "";
|
|
303
|
+
}
|
|
304
|
+
else if (bufferEmpty) {
|
|
305
|
+
const diagnosis = await diagnoseEmptyLogs(logDiagnosisDeps(device));
|
|
306
|
+
connectionWarning = diagnosis.warning;
|
|
307
|
+
emptyReason = diagnosis.reason;
|
|
308
|
+
// If the pipeline recovered, re-read the buffer — new logs may have arrived
|
|
309
|
+
if (diagnosis.recovered) {
|
|
310
|
+
const retryResult = getLogs(resolveLogBuffer(device), {
|
|
311
|
+
maxLogs, level, startFromText, maxMessageLength, verbose
|
|
312
|
+
});
|
|
313
|
+
if (retryResult.count > 0) {
|
|
314
|
+
// Route through the same event pipeline as the final JS
|
|
315
|
+
// return below — retryResult.formatted is legacy text with
|
|
316
|
+
// no [jN] ids, which would leave get_log_details unusable
|
|
317
|
+
// for whatever this call surfaces.
|
|
318
|
+
const retryEvents = jsEventsFromEntries(retryResult.logs, device ?? "all devices");
|
|
319
|
+
const retryFiltered = kind ? retryEvents.filter((e) => e.kind === kind) : retryEvents;
|
|
320
|
+
const retryRendered = formatEventList(retryFiltered, { showDevice: logBuffers.size > 1, maxLength: maxMessageLength, verbose });
|
|
321
|
+
// Return the recovered logs instead of empty
|
|
322
|
+
return {
|
|
323
|
+
_emptyResult: false,
|
|
324
|
+
_emptyReason: "pipeline_recovered",
|
|
325
|
+
content: [{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: `${nativePrefix}React Native Console Logs (${retryFiltered.length} entries):\n\n${retryRendered}${connectionWarning}`
|
|
328
|
+
}]
|
|
329
|
+
};
|
|
178
330
|
}
|
|
179
331
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
332
|
+
if (shouldEscalateToNative(bufferEmpty, getPassiveConnectionStatus().connected) && !isEscalationCoolingDown(device)) {
|
|
333
|
+
// Safe to reuse a fetch taken at a higher minLevel: isRelevant
|
|
334
|
+
// exempts crash/anr from the floor entirely, so they are present
|
|
335
|
+
// regardless of what the caller asked for.
|
|
336
|
+
// Reuse the native fetch already paid for by source==="all"
|
|
337
|
+
// instead of running the subprocess a second time.
|
|
338
|
+
const events = nativeEventsForEscalation
|
|
339
|
+
?? (await collectNativeEvents({ device, minLevel: "warn" })).events;
|
|
340
|
+
const crashes = events.filter((e) => e.kind === "crash" || e.kind === "anr");
|
|
341
|
+
if (crashes.length > 0) {
|
|
342
|
+
lastEmptyEscalationAt.delete(escalationKey(device));
|
|
343
|
+
return {
|
|
344
|
+
_emptyResult: false,
|
|
345
|
+
_emptyReason: "native_crash_found",
|
|
346
|
+
content: [{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: `${nativePrefix}React Native Console Logs (0 entries) — app is not connected.\n\n` +
|
|
349
|
+
`Native crash found:\n${formatEventList(crashes, { showDevice: nativeLogBuffers.size > 1, maxLength: maxMessageLength, verbose })}\n\n` +
|
|
350
|
+
`Use get_log_details("${crashes[0].id}") for the full backtrace.`
|
|
351
|
+
}]
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
lastEmptyEscalationAt.set(escalationKey(device), Date.now());
|
|
189
355
|
}
|
|
356
|
+
// Diagnostic metadata for empty results (local dev dashboard only —
|
|
357
|
+
// responsePreview is never sent to the remote telemetry backend).
|
|
358
|
+
const diagParts = [
|
|
359
|
+
`empty_reason=${emptyReason}`,
|
|
360
|
+
`connection=${getPassiveConnectionStatus().reason}`,
|
|
361
|
+
`device_count=${connectedApps.size}`,
|
|
362
|
+
`buffer_sizes=${JSON.stringify(Object.fromEntries([...logBuffers.entries()].map(([k, v]) => [k, v.size])))}`,
|
|
363
|
+
];
|
|
364
|
+
connectionWarning += `\n\n[DIAG] ${diagParts.join(", ")}`;
|
|
190
365
|
connectionWarning += await metroMissingHintIfAbsent("get_logs");
|
|
191
366
|
}
|
|
192
367
|
else {
|
|
@@ -211,30 +386,23 @@ export function registerLogTools(server) {
|
|
|
211
386
|
}
|
|
212
387
|
}
|
|
213
388
|
const startNote = startFromText ? ` (starting from "${startFromText}")` : "";
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
...(emptyReason && { _emptyReason: emptyReason }),
|
|
219
|
-
content: [
|
|
220
|
-
{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: `React Native Console Logs (${count} entries)${startNote}:\n\n${tonlOutput}${gapWarning}${connectionWarning}`
|
|
223
|
-
}
|
|
224
|
-
]
|
|
225
|
-
};
|
|
226
|
-
}
|
|
389
|
+
const jsEvents = jsEventsFromEntries(logs, device ?? "all devices");
|
|
390
|
+
const jsFiltered = kind ? jsEvents.filter((e) => e.kind === kind) : jsEvents;
|
|
391
|
+
const jsRendered = formatEventList(jsFiltered, { showDevice: logBuffers.size > 1, maxLength: maxMessageLength, verbose });
|
|
227
392
|
return {
|
|
393
|
+
_emptyResult: bufferEmpty,
|
|
228
394
|
...(emptyReason && { _emptyReason: emptyReason }),
|
|
229
395
|
content: [
|
|
230
396
|
{
|
|
231
397
|
type: "text",
|
|
232
|
-
text:
|
|
398
|
+
text: `${nativePrefix}React Native Console Logs (${jsFiltered.length} entries)${startNote}:\n\n${jsRendered}${gapWarning}${connectionWarning}`
|
|
233
399
|
}
|
|
234
400
|
]
|
|
235
401
|
};
|
|
236
402
|
},
|
|
237
|
-
//
|
|
403
|
+
// Fallback only — every return path above sets `_emptyResult` explicitly,
|
|
404
|
+
// which takes precedence. Retained so a future path that forgets to set it
|
|
405
|
+
// still reports something rather than nothing.
|
|
238
406
|
() => getTotalLogCount() === 0);
|
|
239
407
|
// Tool: Search logs
|
|
240
408
|
registerToolWithTelemetry(server, "search_logs", {
|
|
@@ -259,14 +427,9 @@ export function registerLogTools(server) {
|
|
|
259
427
|
.default(500)
|
|
260
428
|
.describe("Max characters per message (default: 500, set to 0 for unlimited)"),
|
|
261
429
|
verbose: z.boolean().optional().default(false).describe("Disable all truncation and return full messages"),
|
|
262
|
-
format: z
|
|
263
|
-
.enum(["text", "tonl"])
|
|
264
|
-
.optional()
|
|
265
|
-
.default("tonl")
|
|
266
|
-
.describe("Output format: 'text' or 'tonl' (default, compact token-optimized format)"),
|
|
267
430
|
device: z.string().optional().describe("Target device name (substring match). Omit for all devices. Run get_apps to see connected devices.")
|
|
268
431
|
}
|
|
269
|
-
}, async ({ text, maxResults, maxMessageLength, verbose,
|
|
432
|
+
}, async ({ text, maxResults, maxMessageLength, verbose, device }) => {
|
|
270
433
|
// Check if SDK is installed — prefer SDK data. See get_logs: the
|
|
271
434
|
// device must be threaded through, and an empty SDK result falls
|
|
272
435
|
// through to the CDP buffer instead of short-circuiting.
|
|
@@ -275,17 +438,6 @@ export function registerLogTools(server) {
|
|
|
275
438
|
const sdkResult = await querySDKConsole({ count: maxResults, text }, device);
|
|
276
439
|
if (sdkResult.success && sdkResult.data.length > 0) {
|
|
277
440
|
const entries = sdkResult.data;
|
|
278
|
-
if (format === "tonl") {
|
|
279
|
-
const tonlLines = entries.map((e) => {
|
|
280
|
-
const time = new Date(e.timestamp).toLocaleTimeString();
|
|
281
|
-
let msg = e.message;
|
|
282
|
-
if (!verbose && maxMessageLength > 0 && msg.length > maxMessageLength) {
|
|
283
|
-
msg = msg.slice(0, maxMessageLength) + "...";
|
|
284
|
-
}
|
|
285
|
-
return `${time} [${e.level}] ${msg}`;
|
|
286
|
-
});
|
|
287
|
-
return { content: [{ type: "text", text: `Search results for "${text}" (${entries.length} matches, SDK):\n\n${tonlLines.join("\n")}` }] };
|
|
288
|
-
}
|
|
289
441
|
const lines = entries.map((e) => {
|
|
290
442
|
const time = new Date(e.timestamp).toLocaleTimeString();
|
|
291
443
|
let msg = e.message;
|
|
@@ -297,7 +449,7 @@ export function registerLogTools(server) {
|
|
|
297
449
|
return { content: [{ type: "text", text: `Search results for "${text}" (${entries.length} matches, SDK):\n\n${lines.join("\n")}` }] };
|
|
298
450
|
}
|
|
299
451
|
}
|
|
300
|
-
const {
|
|
452
|
+
const { count, formatted } = searchLogs(resolveLogBuffer(device), text, { maxResults, maxMessageLength, verbose });
|
|
301
453
|
// Check connection health
|
|
302
454
|
let connectionWarning = "";
|
|
303
455
|
if (count === 0) {
|
|
@@ -311,18 +463,6 @@ export function registerLogTools(server) {
|
|
|
311
463
|
? "\n\n[CONNECTION] Disconnected. Showing cached data. New data is not being captured."
|
|
312
464
|
: "";
|
|
313
465
|
}
|
|
314
|
-
// Use TONL format if requested
|
|
315
|
-
if (format === "tonl") {
|
|
316
|
-
const tonlOutput = formatLogsAsTonl(logs, { maxMessageLength: verbose ? 0 : maxMessageLength });
|
|
317
|
-
return {
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: "text",
|
|
321
|
-
text: `Search results for "${text}" (${count} matches):\n\n${tonlOutput}${connectionWarning}`
|
|
322
|
-
}
|
|
323
|
-
]
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
466
|
return {
|
|
327
467
|
content: [
|
|
328
468
|
{
|
|
@@ -359,6 +499,16 @@ export function registerLogTools(server) {
|
|
|
359
499
|
if (sdkResult.success && sdkResult.count)
|
|
360
500
|
count += sdkResult.count;
|
|
361
501
|
}
|
|
502
|
+
// Clear that device's native buffer too, otherwise a targeted
|
|
503
|
+
// clear leaves native (logcat/os_log) events behind and
|
|
504
|
+
// get_logs(source="native") still shows pre-repro entries. The
|
|
505
|
+
// native buffer deliberately keeps its watermark on clear (see
|
|
506
|
+
// the all-devices branch below) so cleared events are not
|
|
507
|
+
// re-ingested on the next fetch.
|
|
508
|
+
const deviceKey = deviceKeyOf(app);
|
|
509
|
+
if (deviceKey) {
|
|
510
|
+
count += getNativeLogBuffer(deviceKey).clear();
|
|
511
|
+
}
|
|
362
512
|
return { content: [{ type: "text", text: `Cleared ${count} log entries from ${deviceName}.` }] };
|
|
363
513
|
}
|
|
364
514
|
// Clear all
|
|
@@ -374,7 +524,41 @@ export function registerLogTools(server) {
|
|
|
374
524
|
total += sdkResult.count;
|
|
375
525
|
}
|
|
376
526
|
}
|
|
527
|
+
// Native buffers keep their watermark so cleared events are not
|
|
528
|
+
// re-ingested on the next fetch.
|
|
529
|
+
for (const buffer of nativeLogBuffers.values()) {
|
|
530
|
+
total += buffer.clear();
|
|
531
|
+
}
|
|
377
532
|
return { content: [{ type: "text", text: `Cleared ${total} log entries from all devices.` }] };
|
|
378
533
|
});
|
|
534
|
+
// Tool: Get full payload for one log event
|
|
535
|
+
registerToolWithTelemetry(server, "get_log_details", {
|
|
536
|
+
description: "Get the full payload of a single log event — complete stack trace, backtrace, or oversized message.\n" +
|
|
537
|
+
"PURPOSE: Expand one row from get_logs into its full text. A crash row collapses a 60-line backtrace; this returns all of it.\n" +
|
|
538
|
+
"WHEN TO USE: After get_logs shows an event you need to read in full (a crash, an exception, a large payload).\n" +
|
|
539
|
+
"WORKFLOW: get_logs -> copy the id (e.g. \"n7\") -> get_log_details(id=\"n7\").\n" +
|
|
540
|
+
"LIMITATIONS: Ids are valid until that device's buffer rolls over or clear_logs runs, not for the whole server session — call get_logs again to get fresh ones. Reads the buffer — it does not re-query the device.\n" +
|
|
541
|
+
"GOOD: get_log_details({ id: \"n7\" })\n" +
|
|
542
|
+
"BAD: Guessing ids — always take them from get_logs.\n" +
|
|
543
|
+
"SEE ALSO: call get_usage_guide(topic=\"logs\") for the full console-debugging playbook.",
|
|
544
|
+
inputSchema: {
|
|
545
|
+
id: z.string().describe("Event id from get_logs (e.g. \"n7\")"),
|
|
546
|
+
maxLength: z.coerce
|
|
547
|
+
.number()
|
|
548
|
+
.optional()
|
|
549
|
+
.default(4000)
|
|
550
|
+
.describe("Max characters of payload (default: 4000, 0 for unlimited)"),
|
|
551
|
+
verbose: z.boolean().optional().default(false).describe("Disable truncation entirely")
|
|
552
|
+
}
|
|
553
|
+
}, async ({ id, maxLength, verbose }) => {
|
|
554
|
+
const event = findLogEvent(id);
|
|
555
|
+
if (!event) {
|
|
556
|
+
throw new UserInputError(`No log event with id "${id}". Ids come from get_logs — "n7" for native events, "j12" for console entries — ` +
|
|
557
|
+
`and are valid until that device's buffer rolls over or clear_logs runs. Call get_logs again to refresh them.`);
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: "text", text: formatEventDetails(event, { maxLength, verbose }) }]
|
|
561
|
+
};
|
|
562
|
+
});
|
|
379
563
|
}
|
|
380
564
|
//# sourceMappingURL=logTools.js.map
|