tracer-sh 0.3.3 → 0.3.5
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 +76 -81
- package/package.json +1 -1
- package/packages/server/dist/{chunk-XFJVNMJI.js → chunk-LDYGR5S3.js} +5 -5
- package/packages/server/dist/{domain-knowledge-6MEHHDWL.js → domain-knowledge-KF3XTKCK.js} +1 -1
- package/packages/server/dist/index.js +74 -23
- package/packages/web/dist/assets/{SearchableSelect-BfcFMllA.js → SearchableSelect-C0HvVPdQ.js} +1 -1
- package/packages/web/dist/assets/{Settings-BcbW-KcM.js → Settings-CWfkpcQA.js} +1 -1
- package/packages/web/dist/assets/{highlighted-body-OFNGDK62-BA7knzgi.js → highlighted-body-OFNGDK62-DTgnP3WX.js} +1 -1
- package/packages/web/dist/assets/index-CatFLQpv.css +1 -0
- package/packages/web/dist/assets/index-DWtKdjfG.js +48 -0
- package/packages/web/dist/assets/mermaid-GHXKKRXX-VdzAklnF.js +191 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-BMMlPHJO.css +0 -1
- package/packages/web/dist/assets/index-L_n_IFmZ.js +0 -48
- package/packages/web/dist/assets/mermaid-GHXKKRXX-C5BzUoQE.js +0 -191
package/README.md
CHANGED
|
@@ -10,14 +10,32 @@ During an incident, most time goes to switching between observability tools
|
|
|
10
10
|
and gathering context — not fixing the problem. Tracer connects your providers
|
|
11
11
|
to a single AI chat interface so you find the root cause in one place.
|
|
12
12
|
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌─────────┐ your API keys ┌──────────────────┐
|
|
17
|
+
│ │ ◄──────────────────────────►│ Observability │
|
|
18
|
+
│ Tracer │ │ Providers │
|
|
19
|
+
│ local │ your API keys ├──────────────────┤
|
|
20
|
+
│ │ ◄──────────────────────────►│ LLM Providers │
|
|
21
|
+
└─────────┘ └──────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Everything runs on your machine. Your data stays local in an encrypted SQLite
|
|
25
|
+
database. Tracer talks directly to your provider and LLM APIs using your own
|
|
26
|
+
API keys — no intermediary servers, no telemetry, no data leaves your machine
|
|
27
|
+
except the API calls you control.
|
|
28
|
+
|
|
13
29
|
## Debug
|
|
14
30
|
|
|
15
|
-
Chat with an AI agent that queries your providers in real
|
|
31
|
+
Chat with an AI agent that queries your providers in real time and finds root
|
|
32
|
+
causes — all from a single conversation.
|
|
16
33
|
|
|
17
|
-
- Natural language investigation
|
|
34
|
+
- Natural language investigation across all connected providers
|
|
18
35
|
- Live query execution with inline charts
|
|
36
|
+
- Attach evidence to any message — screenshots, log files, code, PDFs — via paperclip, drag-and-drop, or paste
|
|
19
37
|
- Post-mortem reports — download as Markdown to share
|
|
20
|
-
- Share investigations as PNG — drop the exported image
|
|
38
|
+
- Share investigations as PNG — drop the exported image onto the sidebar to re-open the full analysis
|
|
21
39
|
- Agent memory across sessions
|
|
22
40
|
- Session history and cost tracking
|
|
23
41
|
|
|
@@ -25,65 +43,26 @@ Chat with an AI agent that queries your providers in real-time and finds root ca
|
|
|
25
43
|
|
|
26
44
|
## Settings
|
|
27
45
|
|
|
28
|
-
Configure providers, LLM credentials,
|
|
46
|
+
Configure providers, LLM credentials, integrations, and agent behavior. Each
|
|
47
|
+
provider setup includes connectivity tests and guidance on creating
|
|
48
|
+
least-privilege API keys.
|
|
29
49
|
|
|
30
|
-
- Anthropic (Claude)
|
|
50
|
+
- LLM backends: Anthropic (Claude), Google (Gemini via AI Studio or Vertex AI)
|
|
31
51
|
- Data provider setup with connectivity tests
|
|
52
|
+
- Jira integration
|
|
32
53
|
- Thinking budgets and step limits
|
|
33
54
|
- Agent memory management
|
|
34
55
|
|
|
35
56
|

|
|
36
57
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
┌─────────┐ your API keys ┌──────────────────┐
|
|
41
|
-
│ │ ◄──────────────────────────►│ Observability │
|
|
42
|
-
│ Tracer │ │ Providers │
|
|
43
|
-
│ local │ your API keys ├──────────────────┤
|
|
44
|
-
│ │ ◄──────────────────────────►│ LLM Providers │
|
|
45
|
-
└─────────┘ └──────────────────┘
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Everything runs on your machine. Your data stays local in an encrypted SQLite
|
|
49
|
-
database. Tracer talks directly to your provider and LLM APIs using your own API
|
|
50
|
-
keys — no intermediary servers, no data leaves your machine except API calls you control.
|
|
51
|
-
|
|
52
|
-
## Security
|
|
58
|
+
## Supported providers
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
integration tokens, chat history, and agent memory — sit behind several independent
|
|
56
|
-
layers, so no single failure exposes them.
|
|
57
|
-
|
|
58
|
-
- **Local-only.** Nothing leaves your machine except the provider and LLM API calls you configure. No Tracer servers, no telemetry, no sync.
|
|
59
|
-
- **Encrypted at rest.** The entire SQLite database is encrypted with SQLCipher (AES-256). On disk it is ciphertext — a stolen laptop, a copied `.db` file, or a backup is useless without the key.
|
|
60
|
-
- **Machine-bound, user-scoped key.** A random 256-bit key is generated on first run and stored in your OS keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service). It never leaves the machine and is scoped to your OS user, so another user on the same box can't read the database either.
|
|
61
|
-
- **Hardened on disk.** The data directory is created owner-only (`0700`); if no keychain is available, the fallback key file is written `0600`.
|
|
62
|
-
|
|
63
|
-
**Automatic and transparent.** New installs are encrypted from the first run. An
|
|
64
|
-
existing plaintext database is migrated on the first launch of this version — the
|
|
65
|
-
encrypted copy is verified and atomically swapped in, and no plaintext is left behind.
|
|
66
|
-
There's nothing to enable.
|
|
67
|
-
|
|
68
|
-
**CI / headless.** Where no keychain exists, supply the key yourself with
|
|
69
|
-
`TRACER_DB_KEY` — a 64-character hex string (e.g. `openssl rand -hex 32`).
|
|
70
|
-
|
|
71
|
-
**What this protects — and what it doesn't.** Encryption at rest defends the file
|
|
72
|
-
(theft, backups, copies) and blocks other users on the same machine. It does **not**
|
|
73
|
-
defend against you, the logged-in user, running code as yourself: the app must hold the
|
|
74
|
-
key at runtime to read its own data, so anyone controlling your user session can too. No
|
|
75
|
-
local-first app escapes this — it's the honest boundary of on-device encryption.
|
|
60
|
+
**Data:** New Relic (NRQL), Google Cloud (Logs, Traces, Metrics, Errors), PostHog (HogQL)
|
|
76
61
|
|
|
77
|
-
**
|
|
78
|
-
OS reinstall or keychain reset) makes the database unrecoverable. For a safety net, back
|
|
79
|
-
up the `tracer-sh` / `db-key` keychain value somewhere secure.
|
|
62
|
+
**LLM:** Anthropic (Claude), Google (Gemini — AI Studio or Vertex AI)
|
|
80
63
|
|
|
81
|
-
**
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
sqlite3 ~/.tracer/data/tracer.db '.tables' # → "Error: file is not a database"
|
|
85
|
-
head -c 16 ~/.tracer/data/tracer.db | od -c # → random bytes, not "SQLite format 3"
|
|
86
|
-
```
|
|
64
|
+
**Integrations:** Jira — the agent reads issue details and comment threads for
|
|
65
|
+
incident context, and posts comments back only when you explicitly ask.
|
|
87
66
|
|
|
88
67
|
## Install
|
|
89
68
|
|
|
@@ -95,8 +74,6 @@ Requires [Node.js 20+](https://nodejs.org/).
|
|
|
95
74
|
npx tracer-sh@latest
|
|
96
75
|
```
|
|
97
76
|
|
|
98
|
-
`npx` is ephemeral — it runs the newest published version on every launch and isn't a pinned version.
|
|
99
|
-
|
|
100
77
|
**Install a pinned copy:**
|
|
101
78
|
|
|
102
79
|
```bash
|
|
@@ -104,42 +81,69 @@ npm install -g tracer-sh
|
|
|
104
81
|
tracer-sh
|
|
105
82
|
```
|
|
106
83
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
84
|
+
Either way, Tracer stays on its installed version until you explicitly update:
|
|
85
|
+
click the version in the sidebar and hit **Update now**, or re-run the install
|
|
86
|
+
command. Note that bare `npx tracer-sh` reuses npm's cached copy and does NOT
|
|
87
|
+
check for new releases — use the in-app update or `npx tracer-sh@latest` to get
|
|
88
|
+
the newest version.
|
|
110
89
|
|
|
111
|
-
Open `http://localhost:3579`, go to **Settings** to add your API keys and
|
|
90
|
+
Open `http://localhost:3579`, go to **Settings** to add your API keys and
|
|
91
|
+
choose an LLM — done.
|
|
112
92
|
|
|
113
93
|
## Headless / CLI
|
|
114
94
|
|
|
115
|
-
Run an investigation from the terminal and get back the final analysis — so
|
|
95
|
+
Run an investigation from the terminal and get back the final analysis — so
|
|
96
|
+
other tools and agents (including Claude Code) can drive Tracer:
|
|
116
97
|
|
|
117
98
|
```bash
|
|
118
99
|
tracer-sh analyze "Why did checkout error rate spike after 14:00 UTC?"
|
|
119
100
|
```
|
|
120
101
|
|
|
121
|
-
|
|
102
|
+
- `--session <id>` — continue a prior run with full context
|
|
103
|
+
- `--provider <name>` — scope the investigation to one provider
|
|
104
|
+
- `--json` — full response envelope (session id, queries, usage)
|
|
105
|
+
- `tracer-sh --help` — usage for every subcommand
|
|
122
106
|
|
|
123
|
-
|
|
107
|
+
Requires a running server.
|
|
124
108
|
|
|
125
|
-
|
|
109
|
+
## Security
|
|
126
110
|
|
|
127
|
-
|
|
111
|
+
Your secrets — API keys, integration tokens, chat history, agent memory — sit
|
|
112
|
+
behind several independent layers:
|
|
128
113
|
|
|
129
|
-
|
|
114
|
+
- **Local-only.** No Tracer servers, no telemetry, no sync.
|
|
115
|
+
- **Encrypted at rest.** The entire SQLite database is encrypted with SQLCipher (AES-256). A stolen laptop, a copied `.db` file, or a backup is ciphertext without the key.
|
|
116
|
+
- **Machine-bound, user-scoped key.** A random 256-bit key is generated on first run and stored in your OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service). It never leaves the machine and other OS users can't read it.
|
|
117
|
+
- **Hardened on disk.** The data directory is owner-only (`0700`); a keychain-less fallback key file is `0600`.
|
|
118
|
+
|
|
119
|
+
Encryption is automatic — new installs are encrypted from the first run, and
|
|
120
|
+
an existing plaintext database is migrated in place on first launch. In CI or
|
|
121
|
+
headless environments without a keychain, supply the key yourself via
|
|
122
|
+
`TRACER_DB_KEY` (64-char hex, e.g. `openssl rand -hex 32`).
|
|
123
|
+
|
|
124
|
+
Two honest caveats. Encryption at rest defends the file, not your live
|
|
125
|
+
session: anyone running code as your OS user can read what the app can read —
|
|
126
|
+
that is the boundary of every local-first app. And the key lives only in your
|
|
127
|
+
keychain, so losing it (OS reinstall, keychain reset) makes the database
|
|
128
|
+
unrecoverable; back up the `tracer-sh` / `db-key` keychain value if you want
|
|
129
|
+
a safety net.
|
|
130
|
+
|
|
131
|
+
Verify it yourself:
|
|
130
132
|
|
|
131
133
|
```bash
|
|
132
|
-
|
|
134
|
+
sqlite3 ~/.tracer/data/tracer.db '.tables' # → "Error: file is not a database"
|
|
135
|
+
head -c 16 ~/.tracer/data/tracer.db | od -c # → random bytes, not "SQLite format 3"
|
|
133
136
|
```
|
|
134
137
|
|
|
135
|
-
|
|
138
|
+
## Uninstall
|
|
136
139
|
|
|
137
140
|
```bash
|
|
138
|
-
|
|
141
|
+
npm uninstall -g tracer-sh
|
|
142
|
+
rm -rf ~/.tracer # also removes settings, sessions, API keys
|
|
139
143
|
```
|
|
140
144
|
|
|
141
|
-
The database encryption key
|
|
142
|
-
account `db-key`).
|
|
145
|
+
The database encryption key lives in your OS keychain (service `tracer-sh`,
|
|
146
|
+
account `db-key`). On macOS:
|
|
143
147
|
|
|
144
148
|
```bash
|
|
145
149
|
security delete-generic-password -s tracer-sh -a db-key
|
|
@@ -156,19 +160,10 @@ security delete-generic-password -s tracer-sh -a db-key
|
|
|
156
160
|
|
|
157
161
|
## Contributing
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
**Report bugs or request features** — [open an issue](https://github.com/sholub-dev/tracer/issues). Include steps to reproduce for bugs, or a clear description for feature requests.
|
|
162
|
-
|
|
163
|
-
**Submit a code change:**
|
|
164
|
-
|
|
165
|
-
1. Fork this repo
|
|
166
|
-
2. Create a branch (`git checkout -b fix/my-fix`)
|
|
167
|
-
3. Make your changes and commit
|
|
168
|
-
4. Push to your fork (`git push origin fix/my-fix`)
|
|
169
|
-
5. Open a pull request against `master`
|
|
163
|
+
**Report bugs or request features** — [open an issue](https://github.com/sholub-dev/tracer/issues) with steps to reproduce or a clear description.
|
|
170
164
|
|
|
171
|
-
|
|
165
|
+
**Submit a code change** — fork, branch, and open a pull request against
|
|
166
|
+
`master`. All PRs require approval before merging.
|
|
172
167
|
|
|
173
168
|
## License
|
|
174
169
|
|
package/package.json
CHANGED
|
@@ -154,14 +154,14 @@ _Flag if:_ Count is more than 20% lower than the baseline.
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
156
|
### Reporting Format
|
|
157
|
-
After completing all applicable checks, report results as a table
|
|
157
|
+
After completing all applicable checks, report results as a table. (This verdict summary is the ONE permitted markdown table \u2014 each check's underlying data must still be shown via tool calls.)
|
|
158
158
|
|
|
159
159
|
| Check | Current | Baseline (1w ago) | Delta | Status |
|
|
160
160
|
|-------|---------|-------------------|-------|--------|
|
|
161
|
-
| Response Time P99 | ... | ... | +X% |
|
|
162
|
-
| Transaction Error Rate | ... | ... | +X% |
|
|
163
|
-
| External Services Errors | ... | ... | ... |
|
|
164
|
-
| Success Transactions | ... | ... | -X% |
|
|
161
|
+
| Response Time P99 | ... | ... | +X% | Pass / FLAGGED |
|
|
162
|
+
| Transaction Error Rate | ... | ... | +X% | Pass / FLAGGED |
|
|
163
|
+
| External Services Errors | ... | ... | ... | Pass / FLAGGED / N/A |
|
|
164
|
+
| Success Transactions | ... | ... | -X% | Pass / FLAGGED |
|
|
165
165
|
|
|
166
166
|
End with an overall verdict on its own line:
|
|
167
167
|
- \`Service appears healthy.\` \u2014 all checks pass
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
NR_AUTH_STOP_RULE,
|
|
14
14
|
NR_DOMAIN_KNOWLEDGE,
|
|
15
15
|
NR_INSIDE_OUT_DEBUGGING
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-LDYGR5S3.js";
|
|
17
17
|
import {
|
|
18
18
|
GCP_AUTH_STOP_RULE,
|
|
19
19
|
GCP_CROSS_SIGNAL,
|
|
@@ -28202,6 +28202,8 @@ var CONFIG = {
|
|
|
28202
28202
|
/** Buffer cap for `npm install -g` output; well above the 1MB default so a noisy
|
|
28203
28203
|
* but successful install (native rebuild logs) isn't misreported as a failure. */
|
|
28204
28204
|
npmInstallMaxBufferBytes: 16 * 1024 * 1024,
|
|
28205
|
+
/** Re-run the background version check when the cached result is older than this. */
|
|
28206
|
+
updateCheckTtlMs: 6 * 60 * 60 * 1e3,
|
|
28205
28207
|
// ── Dashboard defaults ──
|
|
28206
28208
|
widgetDefaultWidth: 6,
|
|
28207
28209
|
widgetDefaultHeight: 6,
|
|
@@ -28233,6 +28235,8 @@ import { join, dirname, sep } from "path";
|
|
|
28233
28235
|
import { fileURLToPath } from "url";
|
|
28234
28236
|
var RESTART_EXIT_CODE = CONFIG.restartExitCode;
|
|
28235
28237
|
var cachedStatus = null;
|
|
28238
|
+
var lastCheckAtMs = 0;
|
|
28239
|
+
var checkInFlight = false;
|
|
28236
28240
|
var cachedPackageInfo = null;
|
|
28237
28241
|
function resolvePackage() {
|
|
28238
28242
|
if (cachedPackageInfo) return cachedPackageInfo;
|
|
@@ -28271,7 +28275,7 @@ function getInstallMethod() {
|
|
|
28271
28275
|
return detectInstallMethod(resolvePackage().root);
|
|
28272
28276
|
}
|
|
28273
28277
|
function isNewerVersion(latest, current) {
|
|
28274
|
-
const parse4 = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
28278
|
+
const parse4 = (v) => v.replace(/^v/, "").split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
28275
28279
|
const [la, lb, lc] = parse4(latest);
|
|
28276
28280
|
const [ca, cb, cc] = parse4(current);
|
|
28277
28281
|
if (la !== ca) return la > ca;
|
|
@@ -28291,6 +28295,9 @@ function fetchLatestNpmVersion() {
|
|
|
28291
28295
|
});
|
|
28292
28296
|
}
|
|
28293
28297
|
function getUpdateStatus() {
|
|
28298
|
+
if (Date.now() - lastCheckAtMs > CONFIG.updateCheckTtlMs) {
|
|
28299
|
+
checkForUpdateBackground();
|
|
28300
|
+
}
|
|
28294
28301
|
if (cachedStatus) return cachedStatus;
|
|
28295
28302
|
return {
|
|
28296
28303
|
available: false,
|
|
@@ -28300,11 +28307,15 @@ function getUpdateStatus() {
|
|
|
28300
28307
|
};
|
|
28301
28308
|
}
|
|
28302
28309
|
function checkForUpdateBackground() {
|
|
28310
|
+
if (checkInFlight) return;
|
|
28311
|
+
checkInFlight = true;
|
|
28312
|
+
lastCheckAtMs = Date.now();
|
|
28303
28313
|
const current = readCurrentVersion();
|
|
28304
28314
|
const method = getInstallMethod();
|
|
28305
28315
|
const unavailable = () => ({ available: false, currentVersion: current, latestVersion: null, method });
|
|
28306
28316
|
if (current === "unknown") {
|
|
28307
28317
|
cachedStatus = unavailable();
|
|
28318
|
+
checkInFlight = false;
|
|
28308
28319
|
return;
|
|
28309
28320
|
}
|
|
28310
28321
|
fetchLatestNpmVersion().then((latest) => {
|
|
@@ -28315,28 +28326,35 @@ function checkForUpdateBackground() {
|
|
|
28315
28326
|
const available = isNewerVersion(latest, current);
|
|
28316
28327
|
cachedStatus = { available, currentVersion: current, latestVersion: latest, method };
|
|
28317
28328
|
if (available) {
|
|
28318
|
-
const hint = method === "
|
|
28329
|
+
const hint = method === "dev" ? "git pull, then restart tracer-sh \u2014 the launcher rebuilds automatically" : "click the version in the sidebar to update from the app";
|
|
28319
28330
|
console.log(`Update available: v${current} \u2192 v${latest} (${hint})`);
|
|
28320
28331
|
}
|
|
28321
28332
|
}).catch(() => {
|
|
28322
28333
|
cachedStatus = unavailable();
|
|
28334
|
+
}).finally(() => {
|
|
28335
|
+
checkInFlight = false;
|
|
28323
28336
|
});
|
|
28324
28337
|
}
|
|
28338
|
+
function npxPrefixDir(root) {
|
|
28339
|
+
return dirname(dirname(root));
|
|
28340
|
+
}
|
|
28325
28341
|
function performSelfUpdate() {
|
|
28326
|
-
const
|
|
28327
|
-
|
|
28342
|
+
const { root } = resolvePackage();
|
|
28343
|
+
const method = detectInstallMethod(root);
|
|
28344
|
+
if (method === "dev" || !root) {
|
|
28328
28345
|
return Promise.resolve({
|
|
28329
28346
|
ok: false,
|
|
28330
28347
|
method,
|
|
28331
|
-
error:
|
|
28348
|
+
error: "Running from a local source checkout, so in-app update is disabled."
|
|
28332
28349
|
});
|
|
28333
28350
|
}
|
|
28351
|
+
const install = method === "global" ? "npm install -g tracer-sh@latest" : `npm install --prefix "${npxPrefixDir(root)}" tracer-sh@latest`;
|
|
28334
28352
|
return new Promise((resolve5) => {
|
|
28335
28353
|
exec(
|
|
28336
28354
|
// Quiet flags keep output small (a verbose install can otherwise overflow
|
|
28337
28355
|
// the stdout buffer and look like a failure); maxBuffer adds headroom for
|
|
28338
28356
|
// native rebuild logs so a successful install is never misreported.
|
|
28339
|
-
|
|
28357
|
+
`${install} --no-fund --no-audit --loglevel=error`,
|
|
28340
28358
|
{ encoding: "utf-8", timeout: CONFIG.npmInstallTimeoutMs, maxBuffer: CONFIG.npmInstallMaxBufferBytes },
|
|
28341
28359
|
(err, _stdout, stderr) => {
|
|
28342
28360
|
if (err) {
|
|
@@ -63435,7 +63453,7 @@ function createDeleteMemoryTool(memoryExecute, opts) {
|
|
|
63435
63453
|
|
|
63436
63454
|
// src/agents/utility/memory-domain-knowledge.ts
|
|
63437
63455
|
var DOMAIN_LOADERS = {
|
|
63438
|
-
newrelic: async () => (await import("./domain-knowledge-
|
|
63456
|
+
newrelic: async () => (await import("./domain-knowledge-KF3XTKCK.js")).NR_DOMAIN_KNOWLEDGE,
|
|
63439
63457
|
gcp: async () => (await import("./domain-knowledge-HC2HKQB3.js")).GCP_DOMAIN_KNOWLEDGE,
|
|
63440
63458
|
posthog: async () => (await import("./domain-knowledge-RET7XGI5.js")).POSTHOG_DOMAIN_KNOWLEDGE
|
|
63441
63459
|
};
|
|
@@ -63449,7 +63467,7 @@ async function getDomainKnowledge(providerType) {
|
|
|
63449
63467
|
var SYSTEM_PROMPT = `You are a Memory Manager. Review completed sessions and extract lessons from FAILURES and STRUGGLE PATTERNS.
|
|
63450
63468
|
|
|
63451
63469
|
## MANDATORY RULE
|
|
63452
|
-
If the session contains ANY failed queries, you MUST address each failure \u2014 either create_memory if no similar memory exists, or update_memory if an existing memory covers the same topic but could be improved. Every failure MUST result in a tool call
|
|
63470
|
+
If the session contains ANY failed queries, you MUST address each failure \u2014 either create_memory if no similar memory exists, or update_memory if an existing memory covers the same topic but could be improved. Every failure MUST result in a tool call, with ONE exception: transient infrastructure failures (timeout, rate limit, 5xx/server error, network blip) teach nothing about the query language \u2014 for those, state you are skipping them and why instead of saving a memory. A false lesson is worse than none, because memories override future instructions.
|
|
63453
63471
|
|
|
63454
63472
|
## Purpose of memories
|
|
63455
63473
|
Memories capture corrections from real failures and discoveries in the user's specific environment. Each user's system has unique event types, field names, and naming conventions that differ from generic documentation. When the agent tries something and it fails or struggles, the correction is extremely valuable for future sessions.
|
|
@@ -63479,6 +63497,7 @@ If the agent failed then corrected itself, capture the MISTAKE \u2192 CORRECTION
|
|
|
63479
63497
|
If the agent failed but never found a correction, still save the mistake: "Don't use [wrong syntax/field] in NRQL"
|
|
63480
63498
|
|
|
63481
63499
|
## When NOT to save
|
|
63500
|
+
- Transient failures \u2014 timeouts, rate limits, 5xx/server errors, network blips. Not query-language lessons; saving them poisons future sessions.
|
|
63482
63501
|
- General best practices that aren't tied to a specific failure or struggle
|
|
63483
63502
|
- Successful patterns that didn't involve a prior failure or struggle
|
|
63484
63503
|
- User-specific data (IDs, account names, endpoints)
|
|
@@ -63545,18 +63564,18 @@ ${domainKnowledge}
|
|
|
63545
63564
|
` : "";
|
|
63546
63565
|
let tailInstruction;
|
|
63547
63566
|
if (failures.length > 0) {
|
|
63548
|
-
tailInstruction = `This session had ${failures.length} failed queries. You MUST address each failure with create_memory or update_memory \u2014
|
|
63567
|
+
tailInstruction = `This session had ${failures.length} failed queries. You MUST address each failure with create_memory or update_memory \u2014 the only exception is transient infrastructure failures, which you skip with a stated reason.`;
|
|
63549
63568
|
if (emptyCount > 0) tailInstruction += ` Additionally, ${emptyCount} queries returned empty results \u2014 review the session for struggle patterns.`;
|
|
63550
63569
|
} else if (emptyCount > 0) {
|
|
63551
63570
|
tailInstruction = `This session had no errors but ${emptyCount} queries returned empty results. Review the full session timeline for struggle patterns \u2014 repeated attempts, name variations, trial-and-error discovery. If there is a generalized learning, save it. If results were legitimately empty, respond with "No changes needed."`;
|
|
63552
63571
|
} else {
|
|
63553
63572
|
tailInstruction = `If the session was clean, respond with "No changes needed." and make no tool calls.`;
|
|
63554
63573
|
}
|
|
63555
|
-
const failuresSection = failures.length > 0 ? `##
|
|
63574
|
+
const failuresSection = failures.length > 0 ? `## FAILURES DETECTED (${failures.length}) \u2014 each MUST be addressed
|
|
63556
63575
|
${failures.map((f) => `- Query #${f.idx}: \`${f.query}\`
|
|
63557
63576
|
Error: ${f.error}`).join("\n")}
|
|
63558
63577
|
|
|
63559
|
-
For each failure above, call create_memory (or update_memory if a similar memory already exists).
|
|
63578
|
+
For each failure above, call create_memory (or update_memory if a similar memory already exists), unless it is a transient infrastructure failure \u2014 then say so and skip it.
|
|
63560
63579
|
|
|
63561
63580
|
` : "";
|
|
63562
63581
|
const prompt = `Review this completed ${providerType} session and decide what to remember.
|
|
@@ -63833,6 +63852,8 @@ ${DETECTIVE_MINDSET}
|
|
|
63833
63852
|
|
|
63834
63853
|
${EVIDENCE_GROUNDING}
|
|
63835
63854
|
|
|
63855
|
+
${ROOT_CAUSE_DISCIPLINE}
|
|
63856
|
+
|
|
63836
63857
|
${EXECUTION_DISCIPLINE}
|
|
63837
63858
|
|
|
63838
63859
|
${providerFragments.join("\n\n---\n\n")}
|
|
@@ -63842,7 +63863,7 @@ ${buildAnalysisSection(maxSteps)}`;
|
|
|
63842
63863
|
function buildRules(opts) {
|
|
63843
63864
|
const rules = [
|
|
63844
63865
|
`1. **ONE tool call per step.** After each tool result, write a brief summary, then make the next call.`,
|
|
63845
|
-
`2. **Empty results
|
|
63866
|
+
`2. **Empty results: suspect the query first, then prove absence.** Check field name, case, quoting, and time range; fix and retry differently. If a deliberately broadened probe (wider window, fewer filters) is also empty, the absence IS the finding \u2014 report it. Never keep reshaping the same query hoping data appears.`,
|
|
63846
63867
|
`3. **NEVER repeat a failed query.** Read the error, fix the cause. Same error twice \u2192 completely different approach.`,
|
|
63847
63868
|
`4. **Use discovered identifiers exactly.** If the actual name differs from the task, use the exact discovered value.`,
|
|
63848
63869
|
`5. You MUST write a non-empty text response when done \u2014 the user sees your text as the analysis.`,
|
|
@@ -63880,12 +63901,25 @@ Your only sources of truth are the literal text of tool results from this sessio
|
|
|
63880
63901
|
|
|
63881
63902
|
1. **Field names and values are opaque labels.** Never translate or assign meaning to a field name, enum value, code, or flag beyond its literal text \u2014 systems attach internal meanings you cannot know. Report the raw value; if its meaning matters and is undocumented, say so.
|
|
63882
63903
|
2. **Absence requires an empty probe.** Only claim something is missing, absent, or "not on file" if a query that would have returned it came back empty. Not having looked is not evidence of absence.
|
|
63883
|
-
3. **Separate facts, deductions, and gaps.** Facts restate query results. Deductions must follow from stated facts alone \u2014 present them as deductions and name the supporting results; correlation across results is not causation. Gaps are reported as "the data does not show X" \u2014 never filled with a plausible story
|
|
63904
|
+
3. **Separate facts, deductions, and gaps.** Facts restate query results. Deductions must follow from stated facts alone \u2014 present them as deductions and name the supporting results; correlation across results is not causation. Gaps are reported as "the data does not show X" \u2014 never filled with a plausible story.
|
|
63905
|
+
4. **Exact values only.** Every number, identifier, timestamp, and quoted error message in your response must appear literally in a tool result. Values you compute from results must be labeled as computed, with their inputs shown. A name you inferred (service, field, event) must be confirmed by a query before it appears in a finding.
|
|
63906
|
+
5. **Scope claims to what you queried.** "No errors" means "no errors matching my filter in my window" \u2014 state the window. Never generalize a claim beyond the time range, filter, or service actually queried.
|
|
63907
|
+
6. **Label confidence.** State each conclusion as confirmed (a result directly shows it), likely (converging evidence, no direct proof), or unverified (plausible, untested). Only a query result upgrades a claim \u2014 more prose does not. Never present likely or unverified as confirmed.`;
|
|
63908
|
+
var ROOT_CAUSE_DISCIPLINE = `## Root-Cause Discipline
|
|
63909
|
+
|
|
63910
|
+
For "why is X happening" investigations; skip for simple lookups. Steps 1-2 are usually a single time-bucketed query; steps 3-6 are reasoning applied to results you already have \u2014 they cost thought, not extra steps.
|
|
63911
|
+
|
|
63912
|
+
1. **Verify the symptom before explaining it.** The user's description is a claim, not a fact. Your first query confirms the problem actually appears in the data \u2014 right service, right window, roughly the reported magnitude. If it doesn't, report exactly that (with the probe you ran) instead of hunting for causes of something the data does not show.
|
|
63913
|
+
2. **Anchor the timeline.** Establish when the symptom started with a time-bucketed query. A cause must precede the onset \u2014 anything that began after it is a consequence or a coincidence. Ask what changed at onset: deployment, config, traffic shape, a dependency's errors.
|
|
63914
|
+
3. **Name the hypothesis each query tests.** Prefer queries that could DISPROVE it \u2014 a query that can only agree with you proves nothing. One matching correlation is never, by itself, a root cause.
|
|
63915
|
+
4. **Follow the chain to the earliest anomaly.** Timeouts, retries, and 5xx responses are usually symptoms. Keep asking "what made THAT happen" until you reach the earliest anomalous signal visible in the data. If the chain leaves the data you can query (application code, third-party internals), that boundary itself is the finding \u2014 never bridge it with a plausible story.
|
|
63916
|
+
5. **Rule out the strongest alternative.** Before declaring a root cause, name the best competing explanation and the evidence that eliminates it. If you cannot eliminate it, present both candidates and what distinguishes them.
|
|
63917
|
+
6. **Check magnitudes against a baseline.** Compare to the same window a day or week earlier before calling anything a spike or drop. Keep your own numbers consistent \u2014 if two of your results disagree (sampling, different windows), reconcile the disagreement before building on either.`;
|
|
63884
63918
|
var NO_FIXES_RULE = `**NEVER suggest fixes, remediation, next steps, or actions.** Forbidden phrasings include: "consider," "you should," "try," "might want to," "recommend," "could help," "suggests [action]," "would resolve," "to fix this." Any sentence about what to DO about the problem is forbidden, regardless of phrasing. Your job ends at "here is what happened and the evidence." The developer decides what to do.`;
|
|
63885
63919
|
var EXECUTION_DISCIPLINE = `## Execution Discipline
|
|
63886
63920
|
|
|
63887
63921
|
For multi-step investigations:
|
|
63888
|
-
1. **Step N: [Goal]** \u2014 state
|
|
63922
|
+
1. **Step N: [Goal]** \u2014 state the hypothesis this tests or the gap it fills
|
|
63889
63923
|
2. **Tool call** \u2192 ONE query
|
|
63890
63924
|
3. **\u2192 Found:** [data] **\u2192 So what:** [only what this data supports \u2014 if it needs an assumption, it's a gap, not a finding]
|
|
63891
63925
|
4. **\u2192 Can I answer now?** \u2014 If YES: respond. If NO: state what's missing.
|
|
@@ -63906,7 +63940,7 @@ function analysisBlock() {
|
|
|
63906
63940
|
- Do not start writing until you have a clear chain and a concrete list of visuals to run.
|
|
63907
63941
|
2. ${markerStep}
|
|
63908
63942
|
3. **Visual-first narrative.** Walk through what happened and back EVERY substantive finding with a tool call that displays the supporting data (chart or table in the UI). Weave tool calls between narrative paragraphs \u2014 do not cluster them all at the top or bottom. Short connecting text explains each visual; the visuals carry the evidence.
|
|
63909
|
-
4. **End with a concise conclusion** \u2014 the root cause, or the specific gap that prevents naming one, phrased as a deduction from the visuals above.
|
|
63943
|
+
4. **End with a concise conclusion** \u2014 the root cause with its confidence label (confirmed / likely / unverified), or the specific gap that prevents naming one, phrased as a deduction from the visuals above. If the conclusion is not confirmed, name the single piece of evidence that would settle it.
|
|
63910
63944
|
|
|
63911
63945
|
**Rules:**
|
|
63912
63946
|
- **Tool calls are mandatory, not optional.** Every substantive claim needs a tool call showing the data. Narrative without visuals is not acceptable. Cite investigation steps inline with \`[step N]\` only when it adds auditability \u2014 do not substitute citations for visuals.
|
|
@@ -63927,7 +63961,7 @@ You have a maximum of ${maxSteps} steps. Most investigations should finish in 3-
|
|
|
63927
63961
|
|
|
63928
63962
|
## Final Reminders
|
|
63929
63963
|
- **Tool calls are the evidence.** Every substantive claim in your response needs a visual \u2014 even if the same query already ran during investigation, re-run it here. The analysis section must be self-contained.
|
|
63930
|
-
- **Stay Grounded in Evidence:** every claim maps to a specific tool result; values mean only what their literal text says; absence claims need an empty probe; gaps are stated as "the data does not show". No fixes.`;
|
|
63964
|
+
- **Stay Grounded in Evidence:** every claim maps to a specific tool result; values mean only what their literal text says; absence claims need an empty probe; claims stay scoped to the window actually queried; conclusions carry confidence labels; gaps are stated as "the data does not show". No fixes.`;
|
|
63931
63965
|
}
|
|
63932
63966
|
|
|
63933
63967
|
// src/lib/prompt-builder.ts
|
|
@@ -63958,6 +63992,8 @@ ${DETECTIVE_MINDSET}
|
|
|
63958
63992
|
|
|
63959
63993
|
${EVIDENCE_GROUNDING}
|
|
63960
63994
|
|
|
63995
|
+
${ROOT_CAUSE_DISCIPLINE}
|
|
63996
|
+
|
|
63961
63997
|
${config2.insideOutDebugging}
|
|
63962
63998
|
|
|
63963
63999
|
${EXECUTION_DISCIPLINE}
|
|
@@ -73994,7 +74030,7 @@ Only after reviewing all memories, perform any needed updates or deletes.
|
|
|
73994
74030
|
- **Default to KEEP.** Only delete when you are 100% certain the memory is harmful or an exact duplicate.
|
|
73995
74031
|
- Merge duplicates: UPDATE the better one, DELETE the other.
|
|
73996
74032
|
- Rewrite vague notes to be specific and actionable (max 15 words).
|
|
73997
|
-
- Delete memories that teach
|
|
74033
|
+
- Delete memories that teach syntax invalid for THIS provider's query language \u2014 judge against the provider domain knowledge in the prompt, never against another provider's dialect (e.g. GROUP BY is invalid NRQL but valid HogQL).`;
|
|
73998
74034
|
async function runMemoryOptimizer(db2, toolName) {
|
|
73999
74035
|
const memories = db2.select().from(toolMemories).where(eq(toolMemories.toolName, toolName)).all();
|
|
74000
74036
|
const emptyStats = { kept: 0, updated: 0, deleted: 0 };
|
|
@@ -74606,8 +74642,8 @@ var updateRouter = router({
|
|
|
74606
74642
|
currentVersion: status.currentVersion,
|
|
74607
74643
|
latestVersion: status.latestVersion,
|
|
74608
74644
|
method: status.method,
|
|
74609
|
-
//
|
|
74610
|
-
canSelfUpdate: status.method
|
|
74645
|
+
// Global and npx installs upgrade in place; only source checkouts can't.
|
|
74646
|
+
canSelfUpdate: status.method !== "dev"
|
|
74611
74647
|
};
|
|
74612
74648
|
}),
|
|
74613
74649
|
// Upgrade a global install in place, then trigger a graceful restart so the
|
|
@@ -75190,6 +75226,13 @@ ${formatted}`;
|
|
|
75190
75226
|
}
|
|
75191
75227
|
|
|
75192
75228
|
// src/agents/base-agent.ts
|
|
75229
|
+
var IMAGE_ANALYSIS_GUIDANCE = `## Working with attached images and files
|
|
75230
|
+
The user has attached one or more images or files. Treat each as primary evidence, not decoration:
|
|
75231
|
+
- Analyze every attachment carefully and in full \u2014 do not skim. Inspect fine detail, not just the gist.
|
|
75232
|
+
- Extract ALL information that could matter: exact error messages and stack traces, status/error codes, log lines, timestamps, numeric values with their units, axis labels and series values on charts/graphs, configuration keys, IDs, and any text visible in screenshots.
|
|
75233
|
+
- Transcribe values precisely and double-check each reading before relying on it \u2014 re-read the image to confirm digits, spelling, and signs rather than approximating.
|
|
75234
|
+
- Quote what is actually shown rather than paraphrasing loosely, and tie every conclusion back to specific details in the attachment.
|
|
75235
|
+
- If any part is blurry, cropped, truncated, or ambiguous, say so explicitly and ask \u2014 never guess at an unreadable value.`;
|
|
75193
75236
|
function sanitizeMessages(messages) {
|
|
75194
75237
|
return messages.map((msg) => {
|
|
75195
75238
|
if (msg.role !== "assistant") return msg;
|
|
@@ -75274,11 +75317,16 @@ When the user's question spans multiple providers, query each relevant provider
|
|
|
75274
75317
|
const fragments = collected.promptFragments ?? [];
|
|
75275
75318
|
systemPrompt = fragments.length > 0 ? `${basePrompt}
|
|
75276
75319
|
|
|
75320
|
+
${EVIDENCE_GROUNDING}
|
|
75321
|
+
|
|
75277
75322
|
${fragments.join("\n\n")}` : `${basePrompt}
|
|
75278
75323
|
|
|
75279
75324
|
No observability providers are currently configured. If the user asks about observability data, let them know they can connect providers in the Settings page.`;
|
|
75280
75325
|
}
|
|
75281
75326
|
systemPrompt += "\n\n" + getCurrentDateBlock(context2.db);
|
|
75327
|
+
if (modelInput.some((m) => m.parts.some((p) => p.type === "file"))) {
|
|
75328
|
+
systemPrompt += "\n\n" + IMAGE_ANALYSIS_GUIDANCE;
|
|
75329
|
+
}
|
|
75282
75330
|
if (summaryForPrompt) {
|
|
75283
75331
|
systemPrompt += `
|
|
75284
75332
|
|
|
@@ -75725,10 +75773,10 @@ If a tool call fails, retry with a corrected approach. If you fail the same tool
|
|
|
75725
75773
|
|
|
75726
75774
|
## Chart Type Selection
|
|
75727
75775
|
- "auto" \u2014 let the frontend auto-detect based on query shape
|
|
75728
|
-
- "timeseries" \u2014 for
|
|
75729
|
-
- "table" \u2014 for
|
|
75776
|
+
- "timeseries" \u2014 for time-bucketed queries (NRQL TIMESERIES, HogQL GROUP BY time bucket) \u2014 line charts
|
|
75777
|
+
- "table" \u2014 for grouped/top-N results without a time axis (NRQL FACET, SQL GROUP BY)
|
|
75730
75778
|
- "scalar" \u2014 for single-value aggregations
|
|
75731
|
-
- "histogram" \u2014 for histogram()
|
|
75779
|
+
- "histogram" \u2014 for distribution queries (e.g. NRQL histogram())
|
|
75732
75780
|
|
|
75733
75781
|
## Widget Sizing
|
|
75734
75782
|
The dashboard uses a ${CONFIG.gridColumns}\xD7${CONFIG.gridColumns} grid (${CONFIG.gridColumns} columns, ${CONFIG.gridColumns} rows fill the viewport). Both axes are responsive to screen size.
|
|
@@ -76207,6 +76255,9 @@ The condition is a JS expression evaluated against the query \`result\` array. E
|
|
|
76207
76255
|
- \`result[0].average > 2\` \u2014 alert when average exceeds 2 seconds
|
|
76208
76256
|
- \`result[0].errorRate > 0.05\` \u2014 alert when error rate exceeds 5%
|
|
76209
76257
|
|
|
76258
|
+
## Threshold Grounding
|
|
76259
|
+
Never invent a threshold. Unless the user gave an explicit number, run the monitor's query over recent data FIRST and pick the threshold relative to the observed values. When proposing a condition, state the observed baseline it is based on (e.g. "normal is 5-10/min, alerting above 50"). A threshold chosen without looking at the data either never fires or fires constantly.
|
|
76260
|
+
|
|
76210
76261
|
## Frequency Recommendations
|
|
76211
76262
|
- 30s \u2014 critical real-time checks
|
|
76212
76263
|
- 60s \u2014 standard monitoring (default)
|
package/packages/web/dist/assets/{SearchableSelect-BfcFMllA.js → SearchableSelect-C0HvVPdQ.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as q,r as s,j as r}from"./index-
|
|
1
|
+
import{a as q,r as s,j as r}from"./index-DWtKdjfG.js";var P=q();function $(c){const n=c?`tracer:starred:${c}`:null,[i,S]=s.useState(()=>{if(!n)return new Set;try{const a=localStorage.getItem(n);return a?new Set(JSON.parse(a)):new Set}catch{return new Set}});return[i,a=>{S(u=>{const t=new Set(u);return t.has(a)?t.delete(a):t.add(a),n&&localStorage.setItem(n,JSON.stringify([...t])),t})}]}function I({options:c,value:n,onChange:i,placeholder:S="Select...",storageKey:v,fitContent:a,disabled:u}){const[t,l]=s.useState(!1),[w,y]=s.useState(""),f=s.useRef(null),j=s.useRef(null),N=s.useRef(null),g=s.useRef(null),[x,C]=$(v),[m,L]=s.useState({top:0,left:0,minWidth:0});s.useEffect(()=>{if(!t)return;const e=d=>{f.current?.contains(d.target)||j.current?.contains(d.target)||l(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[t]);const R=s.useCallback(()=>{if(!f.current)return;const e=f.current.getBoundingClientRect();L({top:e.bottom+4,left:e.left,minWidth:e.width})},[]);s.useEffect(()=>{t&&(R(),y(""),requestAnimationFrame(()=>N.current?.focus()))},[t,R]);const h=c.find(e=>e.value===n),p=s.useMemo(()=>{const e=w.toLowerCase();return[...e?c.filter(o=>o.label.toLowerCase().includes(e)||o.value.toLowerCase().includes(e)):c].sort((o,b)=>{const k=x.has(o.value)?0:1,E=x.has(b.value)?0:1;return k!==E?k-E:o.label.localeCompare(b.label)})},[c,w,x]);s.useEffect(()=>{if(t&&n&&g.current){const e=g.current.querySelector(`[data-value="${CSS.escape(n)}"]`);e&&e.scrollIntoView({block:"nearest"})}},[t,n]);const W=t?P.createPortal(r.jsxs("div",{ref:j,className:"fixed z-[100] bg-white border border-[#d4d2cd] rounded shadow-lg",style:{top:m.top,left:m.left,minWidth:m.minWidth,width:a?"max-content":m.minWidth},children:[r.jsx("div",{className:"p-1.5 border-b border-[#e8e6e1]",children:r.jsx("input",{ref:N,type:"text",value:w,onChange:e=>y(e.target.value),placeholder:"Search...",className:"w-full px-2 py-1.5 text-xs text-[#2c2c2c] font-sans bg-[#f5f4f0] border border-[#e8e6e1] rounded focus:outline-none focus:border-[#2b5ea7] placeholder:text-[#9c9890]",onKeyDown:e=>{e.key==="Escape"&&l(!1),e.key==="Enter"&&p.length>0&&(i(p[0].value),l(!1))}})}),r.jsx("div",{ref:g,className:"max-h-[280px] overflow-y-auto",children:p.length===0?r.jsx("div",{className:"px-3 py-3 text-xs text-[#9c9890] text-center",children:"No projects found"}):p.map(e=>{const d=e.value===n,o=x.has(e.value);return r.jsxs("div",{"data-value":e.value,className:`flex items-center gap-1.5 px-2 py-1.5 text-xs font-sans cursor-pointer transition-colors ${d?"bg-[#2b5ea7]/10 text-[#2b5ea7]":"text-[#2c2c2c] hover:bg-[#f5f4f0]"}`,onClick:()=>{i(e.value),l(!1)},children:[r.jsx("button",{type:"button",onClick:b=>{b.stopPropagation(),C(e.value)},className:`shrink-0 w-4 h-4 flex items-center justify-center text-[10px] transition-colors ${o?"text-[#d4a017]":"text-[#d4d2cd] hover:text-[#9c9890]"}`,title:o?"Unstar":"Star to pin to top",children:o?"★":"☆"}),r.jsx("span",{className:a?"whitespace-nowrap":"truncate flex-1",children:e.label})]},e.value)})})]}),document.body):null;return r.jsxs(r.Fragment,{children:[r.jsxs("button",{ref:f,type:"button",onClick:()=>!u&&l(!t),disabled:u,className:"w-full bg-white border border-[#d4d2cd] rounded px-3 py-2 text-xs text-[#2c2c2c] font-sans text-left flex items-center justify-between focus:outline-none focus:border-[#2b5ea7] hover:border-[#b0ada6] transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[r.jsx("span",{className:h?"text-[#2c2c2c] truncate":"text-[#9c9890]",children:h?h.displayLabel??h.label:S}),r.jsx("span",{className:"text-[#9c9890] text-[10px] ml-2 shrink-0 transition-transform duration-200",style:{transform:t?"rotate(180deg)":"rotate(0deg)"},children:"▾"})]}),W]})}export{I as S,P as r,$ as u};
|