@mandujs/core 0.53.2 → 0.54.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 +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan 18 P1-4 — Error grouping for the Kitchen Errors panel.
|
|
3
|
+
*
|
|
4
|
+
* The Kitchen error ring buffer (`kitchen-handler.ts:storedErrors`) is a
|
|
5
|
+
* flat list. When the same hydration failure or unhandled rejection
|
|
6
|
+
* fires repeatedly on every render, the buffer fills with near-identical
|
|
7
|
+
* rows and the operator has to scroll through them to spot the count.
|
|
8
|
+
*
|
|
9
|
+
* `groupErrors()` collapses runs of "the same error" into one row with
|
|
10
|
+
* count / first-seen / last-seen / affected-sources / a representative
|
|
11
|
+
* sample. The group key is intentionally conservative so that
|
|
12
|
+
* superficially different errors stay separate:
|
|
13
|
+
*
|
|
14
|
+
* key = sha1(`${type}|${source}|${normalize(message)}`)
|
|
15
|
+
*
|
|
16
|
+
* `normalize` collapses obvious noise (numbers, UUIDs, file:line refs)
|
|
17
|
+
* but leaves stack-shape and message word order intact. This is a
|
|
18
|
+
* smaller hammer than full stack-trace fingerprinting — that lives
|
|
19
|
+
* elsewhere if we ever need it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import crypto from "node:crypto";
|
|
23
|
+
|
|
24
|
+
export interface KitchenErrorLike {
|
|
25
|
+
id?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
severity?: string;
|
|
28
|
+
message: string;
|
|
29
|
+
stack?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
source?: string;
|
|
32
|
+
line?: number;
|
|
33
|
+
column?: number;
|
|
34
|
+
timestamp?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GroupedError {
|
|
38
|
+
/** Stable hash of (type, source, normalized message). */
|
|
39
|
+
key: string;
|
|
40
|
+
/** How many raw errors collapsed into this group. */
|
|
41
|
+
count: number;
|
|
42
|
+
/** Representative event — the most recent occurrence. */
|
|
43
|
+
sample: KitchenErrorLike;
|
|
44
|
+
/** Severity of the sample. Same across the group in practice. */
|
|
45
|
+
severity: string | undefined;
|
|
46
|
+
/** Earliest timestamp seen for this signature. */
|
|
47
|
+
firstSeen: number;
|
|
48
|
+
/** Latest timestamp seen for this signature. */
|
|
49
|
+
lastSeen: number;
|
|
50
|
+
/** Distinct source / file refs across the group, capped for transport. */
|
|
51
|
+
affectedSources: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const NUM_RE = /\d{1,}/g;
|
|
55
|
+
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
56
|
+
const HEX_RE = /\b0x[0-9a-f]+\b/gi;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Replace noise that varies run-to-run but doesn't change which bug is
|
|
60
|
+
* being reported. Kept intentionally narrow so user-visible message
|
|
61
|
+
* structure stays recognizable for the operator.
|
|
62
|
+
*/
|
|
63
|
+
export function normalizeMessage(message: string): string {
|
|
64
|
+
return message
|
|
65
|
+
.replace(UUID_RE, "<UUID>")
|
|
66
|
+
.replace(HEX_RE, "<HEX>")
|
|
67
|
+
.replace(NUM_RE, "<N>")
|
|
68
|
+
.trim()
|
|
69
|
+
.toLowerCase();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function errorKey(err: KitchenErrorLike): string {
|
|
73
|
+
const parts = [
|
|
74
|
+
err.type ?? "runtime",
|
|
75
|
+
err.source ?? "unknown",
|
|
76
|
+
normalizeMessage(err.message ?? ""),
|
|
77
|
+
].join("|");
|
|
78
|
+
return crypto.createHash("sha1").update(parts).digest("hex").slice(0, 16);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface GroupOptions {
|
|
82
|
+
/** Cap `affectedSources` length in the response. Default 5. */
|
|
83
|
+
maxSourcesPerGroup?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Collapse a flat error list into `GroupedError[]`, newest-group first.
|
|
88
|
+
* Stable: a single error becomes a group of size 1.
|
|
89
|
+
*/
|
|
90
|
+
export function groupErrors(errors: readonly KitchenErrorLike[], options: GroupOptions = {}): GroupedError[] {
|
|
91
|
+
const maxSources = options.maxSourcesPerGroup ?? 5;
|
|
92
|
+
const byKey = new Map<string, GroupedError>();
|
|
93
|
+
|
|
94
|
+
for (const err of errors) {
|
|
95
|
+
const key = errorKey(err);
|
|
96
|
+
const ts = err.timestamp ?? 0;
|
|
97
|
+
const existing = byKey.get(key);
|
|
98
|
+
if (!existing) {
|
|
99
|
+
const sources = err.source ? [err.source] : [];
|
|
100
|
+
byKey.set(key, {
|
|
101
|
+
key,
|
|
102
|
+
count: 1,
|
|
103
|
+
sample: err,
|
|
104
|
+
severity: err.severity,
|
|
105
|
+
firstSeen: ts,
|
|
106
|
+
lastSeen: ts,
|
|
107
|
+
affectedSources: sources,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
existing.count += 1;
|
|
112
|
+
if (ts < existing.firstSeen || existing.firstSeen === 0) existing.firstSeen = ts;
|
|
113
|
+
if (ts > existing.lastSeen) {
|
|
114
|
+
existing.lastSeen = ts;
|
|
115
|
+
existing.sample = err; // newest wins so the operator sees the freshest message
|
|
116
|
+
}
|
|
117
|
+
if (err.source && !existing.affectedSources.includes(err.source)) {
|
|
118
|
+
if (existing.affectedSources.length < maxSources) {
|
|
119
|
+
existing.affectedSources.push(err.source);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Newest groups first — the operator wants the most recent regression up top.
|
|
125
|
+
return Array.from(byKey.values()).sort((a, b) => b.lastSeen - a.lastSeen);
|
|
126
|
+
}
|
|
@@ -11,26 +11,30 @@ import { getGlobalCache, getCacheStoreStats } from "../runtime/cache";
|
|
|
11
11
|
import { ActivitySSEBroadcaster } from "./stream/activity-sse";
|
|
12
12
|
import { GuardAPI } from "./api/guard-api";
|
|
13
13
|
import { handleRoutesRequest } from "./api/routes-api";
|
|
14
|
-
import { FileAPI } from "./api/file-api";
|
|
15
|
-
import { GuardDecisionManager } from "./api/guard-decisions";
|
|
16
|
-
import { ContractPlaygroundAPI } from "./api/contract-api";
|
|
17
|
-
import { handleAgentContextRequest } from "./api/agent-devtools-api";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
import { FileAPI } from "./api/file-api";
|
|
15
|
+
import { GuardDecisionManager } from "./api/guard-decisions";
|
|
16
|
+
import { ContractPlaygroundAPI } from "./api/contract-api";
|
|
17
|
+
import { handleAgentContextRequest } from "./api/agent-devtools-api";
|
|
18
|
+
import { groupErrors } from "./api/errors-grouping";
|
|
19
|
+
import type { BundleManifest } from "../bundler/types";
|
|
20
|
+
import type { DiagnoseReport } from "../diagnose/types";
|
|
21
|
+
import { runExtendedDiagnose } from "../diagnose/run";
|
|
22
|
+
import { renderKitchenHTML } from "./kitchen-ui";
|
|
23
|
+
import { eventBus } from "../observability/event-bus";
|
|
24
|
+
import fs from "fs/promises";
|
|
25
|
+
import path from "path";
|
|
26
|
+
|
|
27
|
+
export const KITCHEN_PREFIX = "/__kitchen";
|
|
28
|
+
|
|
29
|
+
function reverseCopy<T>(items: readonly T[]): T[] {
|
|
30
|
+
const reversed: T[] = [];
|
|
31
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
32
|
+
reversed.push(items[index] as T);
|
|
33
|
+
}
|
|
34
|
+
return reversed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KitchenOptions {
|
|
34
38
|
rootDir: string;
|
|
35
39
|
manifest: RoutesManifest;
|
|
36
40
|
guardConfig: GuardConfig | null;
|
|
@@ -83,9 +87,9 @@ export function recordRequest(entry: RequestEntry): void {
|
|
|
83
87
|
if (recentRequests.length > MAX_REQUESTS) recentRequests.shift();
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
export function getRecentRequests(): RequestEntry[] {
|
|
87
|
-
return reverseCopy(recentRequests);
|
|
88
|
-
}
|
|
90
|
+
export function getRecentRequests(): RequestEntry[] {
|
|
91
|
+
return reverseCopy(recentRequests);
|
|
92
|
+
}
|
|
89
93
|
|
|
90
94
|
/** Parse a window string like "5m", "30s", "1h" into milliseconds. */
|
|
91
95
|
function parseWindow(input: string): number {
|
|
@@ -102,6 +106,64 @@ function parseWindow(input: string): number {
|
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
// ========== Agent Context Pack — signal collectors (plan 18 P0-2) ==========
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read the bundle manifest from `.mandu/manifest.json`. Returns `null`
|
|
113
|
+
* when the file is missing or malformed — the builder treats `null` as
|
|
114
|
+
* "not collected" rather than "empty", so missing-vs-empty stays
|
|
115
|
+
* distinguishable for the build-broken classifier.
|
|
116
|
+
*/
|
|
117
|
+
async function readBundleManifest(rootDir: string): Promise<BundleManifest | null> {
|
|
118
|
+
const manifestPath = path.join(rootDir, ".mandu", "manifest.json");
|
|
119
|
+
try {
|
|
120
|
+
const raw = await fs.readFile(manifestPath, "utf-8");
|
|
121
|
+
return JSON.parse(raw) as BundleManifest;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run the extended diagnose suite. Returns `null` on failure so the
|
|
129
|
+
* supervisor degrades gracefully — no diagnose summary, but the rest
|
|
130
|
+
* of the context pack still works.
|
|
131
|
+
*/
|
|
132
|
+
async function runDiagnoseSignal(rootDir: string): Promise<DiagnoseReport | null> {
|
|
133
|
+
try {
|
|
134
|
+
return await runExtendedDiagnose(rootDir);
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Collect changed file paths via `git diff --name-only <base>`. Base
|
|
142
|
+
* defaults to `HEAD` (uncommitted changes only), overridable with the
|
|
143
|
+
* `MANDU_DIFF_BASE` environment variable. Returns `undefined` (not
|
|
144
|
+
* `[]`) when git is unavailable so the builder can omit the summary
|
|
145
|
+
* block entirely.
|
|
146
|
+
*/
|
|
147
|
+
async function collectChangedFiles(rootDir: string): Promise<string[] | undefined> {
|
|
148
|
+
const base = process.env.MANDU_DIFF_BASE ?? "HEAD";
|
|
149
|
+
try {
|
|
150
|
+
const proc = Bun.spawnSync({
|
|
151
|
+
cmd: ["git", "diff", "--name-only", base],
|
|
152
|
+
cwd: rootDir,
|
|
153
|
+
stdout: "pipe",
|
|
154
|
+
stderr: "pipe",
|
|
155
|
+
});
|
|
156
|
+
if (proc.exitCode !== 0) return undefined;
|
|
157
|
+
const text = new TextDecoder().decode(proc.stdout);
|
|
158
|
+
return text
|
|
159
|
+
.split(/\r?\n/)
|
|
160
|
+
.map((line) => line.trim())
|
|
161
|
+
.filter((line) => line.length > 0);
|
|
162
|
+
} catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
105
167
|
// ========== Per-Agent Stats ==========
|
|
106
168
|
|
|
107
169
|
export interface AgentStats {
|
|
@@ -229,11 +291,11 @@ export class KitchenHandler {
|
|
|
229
291
|
this.contractAPI.updateManifest(manifest);
|
|
230
292
|
}
|
|
231
293
|
|
|
232
|
-
/** Update guard config when mandu.config.ts changes */
|
|
233
|
-
updateGuardConfig(config: GuardConfig | null): void {
|
|
234
|
-
this.options.guardConfig = config;
|
|
235
|
-
this.guardAPI.updateConfig(config);
|
|
236
|
-
}
|
|
294
|
+
/** Update guard config when mandu.config.ts changes */
|
|
295
|
+
updateGuardConfig(config: GuardConfig | null): void {
|
|
296
|
+
this.options.guardConfig = config;
|
|
297
|
+
this.guardAPI.updateConfig(config);
|
|
298
|
+
}
|
|
237
299
|
|
|
238
300
|
/** Get the SSE broadcaster for external event injection */
|
|
239
301
|
get broadcaster(): ActivitySSEBroadcaster {
|
|
@@ -332,11 +394,11 @@ export class KitchenHandler {
|
|
|
332
394
|
// Requests API — recent HTTP events from eventBus (fallback: ring buffer)
|
|
333
395
|
if (sub === "/api/requests" && req.method === "GET") {
|
|
334
396
|
const url = new URL(req.url);
|
|
335
|
-
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
336
|
-
const busEvents = eventBus.getRecent(limit, { type: "http" });
|
|
337
|
-
if (busEvents.length > 0) {
|
|
338
|
-
return Response.json({ requests: reverseCopy(busEvents) });
|
|
339
|
-
}
|
|
397
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
398
|
+
const busEvents = eventBus.getRecent(limit, { type: "http" });
|
|
399
|
+
if (busEvents.length > 0) {
|
|
400
|
+
return Response.json({ requests: reverseCopy(busEvents) });
|
|
401
|
+
}
|
|
340
402
|
return Response.json({ requests: getRecentRequests().slice(0, limit) });
|
|
341
403
|
}
|
|
342
404
|
|
|
@@ -353,36 +415,90 @@ export class KitchenHandler {
|
|
|
353
415
|
// Activity API — recent MCP events from eventBus (fallback: activity.jsonl)
|
|
354
416
|
if (sub === "/api/activity" && req.method === "GET") {
|
|
355
417
|
const url = new URL(req.url);
|
|
356
|
-
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
357
|
-
const busEvents = eventBus.getRecent(limit, { type: "mcp" });
|
|
358
|
-
if (busEvents.length > 0) {
|
|
359
|
-
return Response.json({ events: reverseCopy(busEvents) });
|
|
360
|
-
}
|
|
418
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
419
|
+
const busEvents = eventBus.getRecent(limit, { type: "mcp" });
|
|
420
|
+
if (busEvents.length > 0) {
|
|
421
|
+
return Response.json({ events: reverseCopy(busEvents) });
|
|
422
|
+
}
|
|
361
423
|
const events = await this.readRecentActivity();
|
|
362
424
|
return Response.json({ events });
|
|
363
425
|
}
|
|
364
426
|
|
|
365
427
|
// Agent Stats API — per-agent (sessionId) aggregation of MCP events
|
|
366
|
-
if (sub === "/api/agent-stats" && req.method === "GET") {
|
|
367
|
-
return Response.json(computeAgentStats());
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
428
|
+
if (sub === "/api/agent-stats" && req.method === "GET") {
|
|
429
|
+
return Response.json(computeAgentStats());
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Generic events API (plan 18 P1-3).
|
|
433
|
+
// Exposes the full eventBus surface — `build` / `cache` / `ws` / `ate`
|
|
434
|
+
// / `error` categories live on the bus but were previously invisible
|
|
435
|
+
// because /api/activity and /api/requests filter to mcp/http only.
|
|
436
|
+
// Query params:
|
|
437
|
+
// ?type=build|cache|ws|ate|http|mcp|guard|error (optional — omit for all)
|
|
438
|
+
// ?severity=info|warn|error (optional)
|
|
439
|
+
// ?limit=N (default 100, max 500)
|
|
440
|
+
// ?stats=1 (return per-type counts instead of events)
|
|
441
|
+
if (sub === "/api/events" && req.method === "GET") {
|
|
442
|
+
const url = new URL(req.url);
|
|
443
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "100", 10) || 100, 500);
|
|
444
|
+
|
|
445
|
+
if (url.searchParams.get("stats") === "1") {
|
|
446
|
+
return Response.json({ stats: eventBus.getStats() });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const typeParam = url.searchParams.get("type");
|
|
450
|
+
const sevParam = url.searchParams.get("severity");
|
|
451
|
+
const allowedTypes = new Set(["http", "mcp", "guard", "build", "error", "cache", "ws", "ate"]);
|
|
452
|
+
const allowedSev = new Set(["info", "warn", "error"]);
|
|
453
|
+
const filter: { type?: "http" | "mcp" | "guard" | "build" | "error" | "cache" | "ws" | "ate"; severity?: "info" | "warn" | "error" } = {};
|
|
454
|
+
if (typeParam && allowedTypes.has(typeParam)) filter.type = typeParam as typeof filter.type;
|
|
455
|
+
if (sevParam && allowedSev.has(sevParam)) filter.severity = sevParam as typeof filter.severity;
|
|
456
|
+
|
|
457
|
+
const events = eventBus.getRecent(limit, filter);
|
|
458
|
+
return Response.json({ events: reverseCopy(events) });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Agent DevTools API — read-only context pack for supervised coding sessions.
|
|
462
|
+
//
|
|
463
|
+
// Plan 18 P0-2 — collect three additional signals (bundle manifest,
|
|
464
|
+
// diagnose report, changed files) so the supervisor can distinguish
|
|
465
|
+
// build-broken / boot-breaking categories from runtime errors.
|
|
466
|
+
// Each collector is fail-safe: failure to read disk or run a
|
|
467
|
+
// sub-process yields `null` / `[]` rather than throwing. Query
|
|
468
|
+
// params allow callers to skip expensive signals:
|
|
469
|
+
// ?bundle=0 → skip reading .mandu/manifest.json
|
|
470
|
+
// ?diagnose=0 → skip runExtendedDiagnose (a11y_hints is the
|
|
471
|
+
// most expensive check; opt out when latency matters)
|
|
472
|
+
// ?diff=0 → skip `git diff --name-only`
|
|
473
|
+
if (sub === "/api/agent-context" && req.method === "GET") {
|
|
474
|
+
const urlObj = new URL(req.url);
|
|
475
|
+
const wantBundle = urlObj.searchParams.get("bundle") !== "0";
|
|
476
|
+
const wantDiagnose = urlObj.searchParams.get("diagnose") !== "0";
|
|
477
|
+
const wantDiff = urlObj.searchParams.get("diff") !== "0";
|
|
478
|
+
|
|
479
|
+
const [bundleManifest, diagnoseReport, changedFiles] = await Promise.all([
|
|
480
|
+
wantBundle ? readBundleManifest(this.options.rootDir) : Promise.resolve(null),
|
|
481
|
+
wantDiagnose ? runDiagnoseSignal(this.options.rootDir) : Promise.resolve(null),
|
|
482
|
+
wantDiff ? collectChangedFiles(this.options.rootDir) : Promise.resolve(undefined),
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
return handleAgentContextRequest({
|
|
486
|
+
rootDir: this.options.rootDir,
|
|
487
|
+
manifest: this.manifest,
|
|
488
|
+
guardEnabled: !!this.options.guardConfig,
|
|
489
|
+
errors: getKitchenErrors(),
|
|
490
|
+
requests: getRecentRequests().slice(0, 100),
|
|
491
|
+
httpEvents: eventBus.getRecent(100, { type: "http" }),
|
|
492
|
+
mcpEvents: eventBus.getRecent(100, { type: "mcp" }),
|
|
493
|
+
guardEvents: eventBus.getRecent(100, { type: "guard" }),
|
|
494
|
+
agentStats: computeAgentStats(),
|
|
495
|
+
bundleManifest,
|
|
496
|
+
diagnoseReport,
|
|
497
|
+
changedFiles,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Cache API — cache store stats
|
|
386
502
|
if ((sub === "/api/cache" || sub === "/api/cache-stats") && req.method === "GET") {
|
|
387
503
|
const store = getGlobalCache();
|
|
388
504
|
return Response.json({
|
|
@@ -459,6 +575,19 @@ export class KitchenHandler {
|
|
|
459
575
|
return Response.json({ errors: storedErrors, count: storedErrors.length });
|
|
460
576
|
}
|
|
461
577
|
|
|
578
|
+
// Plan 18 P1-4 — Errors panel grouped view. Collapses identical
|
|
579
|
+
// signatures into one row with count/firstSeen/lastSeen/affected
|
|
580
|
+
// sources. The operator no longer scrolls past five copies of the
|
|
581
|
+
// same hydration error to find the new one underneath.
|
|
582
|
+
if (sub === "/api/errors/grouped" && req.method === "GET") {
|
|
583
|
+
const groups = groupErrors(storedErrors);
|
|
584
|
+
return Response.json({
|
|
585
|
+
groups,
|
|
586
|
+
groupCount: groups.length,
|
|
587
|
+
totalCount: storedErrors.length,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
462
591
|
if (sub === "/api/errors" && req.method === "DELETE") {
|
|
463
592
|
const count = storedErrors.length;
|
|
464
593
|
clearKitchenErrors();
|
|
@@ -513,11 +642,11 @@ export class KitchenHandler {
|
|
|
513
642
|
private async readRecentActivity(): Promise<unknown[]> {
|
|
514
643
|
const logPath = path.join(this.options.rootDir, ".mandu", "activity.jsonl");
|
|
515
644
|
try {
|
|
516
|
-
const content = await fs.readFile(logPath, "utf-8");
|
|
517
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
518
|
-
return reverseCopy(lines.slice(-50)).map((line) => {
|
|
519
|
-
try { return JSON.parse(line); } catch { return null; }
|
|
520
|
-
}).filter(Boolean);
|
|
645
|
+
const content = await fs.readFile(logPath, "utf-8");
|
|
646
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
647
|
+
return reverseCopy(lines.slice(-50)).map((line) => {
|
|
648
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
649
|
+
}).filter(Boolean);
|
|
521
650
|
} catch { return []; }
|
|
522
651
|
}
|
|
523
652
|
}
|