源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s

This commit is contained in:
agent-radar
2026-09-01 20:17:27 +08:00
commit 2ed3415fb7
8018 changed files with 1936476 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# `@tutti-os/analytics-debug`
Host-agnostic storage and React UI for inspecting analytics events.
The package does not connect to a daemon, choose a feature flag, or own product
translations. Each host adapts its own event stream into
`AnalyticsDebugEventStore` and supplies localized labels to the React panel.
This keeps Tutti and TSH transport policy independent while sharing bounded
storage, redaction, rendering, and floating-panel behavior.
```ts
import { AnalyticsDebugEventStore } from "@tutti-os/analytics-debug";
import { AnalyticsDebugFloatingEntry } from "@tutti-os/analytics-debug/react";
const store = new AnalyticsDebugEventStore({
redact: (event) => event
});
eventSource.subscribe((events) => store.recordEvents(events));
```
Hosts must load `@tutti-os/ui-system/styles.css` and include the published debug
bundle in Tailwind v4 source discovery. Adjust the relative path for the host
stylesheet, for example:
```css
@source "../node_modules/@tutti-os/analytics-debug/dist";
```
Events are kept in memory only. The default limit is 200 events. Hosts that may
receive sensitive parameters should supply a `redact` function before exposing
the panel.
+59
View File
@@ -0,0 +1,59 @@
{
"name": "@tutti-os/analytics-debug",
"version": "0.1.0",
"private": false,
"type": "module",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./react": "./src/react/index.ts"
},
"files": [
"dist",
"README.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/tutti-os/tutti.git",
"directory": "packages/analytics/debug"
},
"scripts": {
"build": "tsup --config tsup.config.ts",
"test": "node --test --experimental-strip-types \"./src/**/*.test.ts\"",
"typecheck": "node ../../../tools/scripts/run-tsgo-typecheck.mjs"
},
"dependencies": {
"@tutti-os/analytics": "workspace:*",
"@tutti-os/ui-system": "workspace:*"
},
"devDependencies": {
"@tutti-os/config-tsconfig": "workspace:*",
"@types/node": "^24.0.1",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "^5.8.3"
},
"peerDependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"tutti": {
"tailwindSourceRoot": "src"
},
"publishConfig": {
"access": "public",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./react": {
"types": "./dist/react/index.d.ts",
"import": "./dist/react/index.js"
}
},
"types": "./dist/index.d.ts"
}
}
@@ -0,0 +1,101 @@
import assert from "node:assert/strict";
import test from "node:test";
import { AnalyticsDebugEventStore } from "./eventStore.ts";
test("debug event store keeps immutable bounded snapshots", () => {
const store = new AnalyticsDebugEventStore({ maxEvents: 2 });
const params = {
context: {
source: "dashboard",
tags: ["primary"]
}
};
let notifications = 0;
store.subscribe(() => {
notifications++;
});
store.recordEvents([
{ clientTS: 1, name: "discarded.event" },
{ clientTS: 2, name: "workspace.opened", params },
{ clientTS: 3, name: "workspace.closed" }
]);
params.context.source = "mutated";
params.context.tags.push("mutated");
assert.deepEqual(store.getSnapshot(), [
{
clientTS: 2,
name: "workspace.opened",
params: {
context: {
source: "dashboard",
tags: ["primary"]
}
}
},
{ clientTS: 3, name: "workspace.closed" }
]);
const snapshot = store.getSnapshot();
const storedContext = snapshot[0]?.params?.context;
assert.ok(storedContext);
assert.throws(() => {
(storedContext as { source: string }).source = "consumer mutation";
}, TypeError);
assert.deepEqual(store.getSnapshot(), snapshot);
assert.equal(notifications, 1);
});
test("debug event store redacts before retaining events", () => {
const store = new AnalyticsDebugEventStore({
redact(event) {
if (event.name === "private.event") {
return null;
}
return {
...event,
params: {
...event.params,
token: "[redacted]"
}
};
}
});
store.recordEvents([
{
clientTS: 1,
name: "public.event",
params: { token: "secret" }
},
{
clientTS: 2,
name: "private.event"
}
]);
assert.deepEqual(store.getSnapshot(), [
{
clientTS: 1,
name: "public.event",
params: { token: "[redacted]" }
}
]);
});
test("debug event store isolates listener failures", () => {
const store = new AnalyticsDebugEventStore();
let notifications = 0;
store.subscribe(() => {
throw new Error("broken debug consumer");
});
store.subscribe(() => {
notifications++;
});
assert.doesNotThrow(() => {
store.recordEvents([{ clientTS: 1, name: "workspace.opened" }]);
});
assert.equal(notifications, 1);
});
+150
View File
@@ -0,0 +1,150 @@
import type { AnalyticsTransportEvent } from "@tutti-os/analytics";
export type AnalyticsDebugEvent = AnalyticsTransportEvent;
export type AnalyticsDebugEventSnapshot = readonly AnalyticsDebugEvent[];
export interface AnalyticsDebugEventStoreContract {
clear(): void;
getSnapshot(): AnalyticsDebugEventSnapshot;
recordEvents(events: readonly AnalyticsDebugEvent[]): void;
subscribe(listener: () => void): () => void;
}
export type AnalyticsDebugEventRedactor = (
event: AnalyticsDebugEvent
) => AnalyticsDebugEvent | null;
export interface AnalyticsDebugEventStoreOptions {
maxEvents?: number;
redact?: AnalyticsDebugEventRedactor;
}
const DEFAULT_MAX_EVENTS = 200;
/**
* Bounded, in-memory analytics event store.
*
* Transport connection and availability policy intentionally stay in the
* product host. Redaction runs before an event enters the store.
*/
export class AnalyticsDebugEventStore implements AnalyticsDebugEventStoreContract {
private events: AnalyticsDebugEventSnapshot = Object.freeze([]);
private readonly listeners = new Set<() => void>();
private readonly maxEvents: number;
private readonly redact: AnalyticsDebugEventRedactor;
constructor(options: AnalyticsDebugEventStoreOptions = {}) {
this.maxEvents = normalizeMaxEvents(options.maxEvents);
this.redact = options.redact ?? cloneAnalyticsDebugEvent;
}
clear(): void {
if (this.events.length === 0) {
return;
}
this.events = Object.freeze([]);
this.emit();
}
dispose(): void {
this.listeners.clear();
}
getSnapshot(): AnalyticsDebugEventSnapshot {
return this.events;
}
recordEvents(events: readonly AnalyticsDebugEvent[]): void {
if (events.length === 0) {
return;
}
const accepted: AnalyticsDebugEvent[] = [];
for (const event of events) {
try {
const redacted = this.redact(cloneAnalyticsDebugEvent(event));
if (redacted) {
accepted.push(
freezeAnalyticsDebugEvent(cloneAnalyticsDebugEvent(redacted))
);
}
} catch {
// A host redactor must not interrupt event reporting or panel updates.
}
}
if (accepted.length === 0) {
return;
}
this.events = Object.freeze(
[...this.events, ...accepted].slice(-this.maxEvents)
);
this.emit();
}
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
private emit(): void {
for (const listener of this.listeners) {
try {
listener();
} catch {
// One debug consumer must not break reporting or other consumers.
}
}
}
}
function normalizeMaxEvents(value: number | undefined): number {
if (value === undefined) {
return DEFAULT_MAX_EVENTS;
}
if (!Number.isSafeInteger(value) || value <= 0) {
throw new RangeError(
"analytics debug maxEvents must be a positive integer"
);
}
return value;
}
function cloneAnalyticsDebugEvent(
event: AnalyticsDebugEvent
): AnalyticsDebugEvent {
return {
clientTS: event.clientTS,
name: event.name,
...(event.params !== undefined
? { params: structuredClone(event.params) }
: {})
};
}
function freezeAnalyticsDebugEvent(
event: AnalyticsDebugEvent
): AnalyticsDebugEvent {
deepFreeze(event);
return event;
}
function deepFreeze(value: unknown, seen = new WeakSet<object>()): void {
if (
(typeof value !== "object" && typeof value !== "function") ||
value === null ||
seen.has(value)
) {
return;
}
seen.add(value);
for (const propertyValue of Object.values(value)) {
deepFreeze(propertyValue, seen);
}
Object.freeze(value);
}
+16
View File
@@ -0,0 +1,16 @@
export {
AnalyticsDebugEventStore,
type AnalyticsDebugEvent,
type AnalyticsDebugEventRedactor,
type AnalyticsDebugEventSnapshot,
type AnalyticsDebugEventStoreContract,
type AnalyticsDebugEventStoreOptions
} from "./eventStore.ts";
export {
hasAnalyticsDebugFloatingDragMoved,
resolveAnalyticsDebugFloatingPosition,
type AnalyticsDebugPoint,
type AnalyticsDebugPosition,
type AnalyticsDebugSize,
type ResolveAnalyticsDebugFloatingPositionInput
} from "./react/floatingPosition.ts";
@@ -0,0 +1,293 @@
import {
useCallback,
useMemo,
useRef,
useState,
useSyncExternalStore,
type MouseEvent as ReactMouseEvent,
type PointerEvent as ReactPointerEvent
} from "react";
import {
Badge,
Button,
CloseIcon,
DeleteIcon,
HealthIcon,
Popover,
PopoverContent,
PopoverTrigger,
ScrollArea
} from "@tutti-os/ui-system";
import type { AnalyticsDebugEventStoreContract } from "../eventStore.ts";
import {
hasAnalyticsDebugFloatingDragMoved,
resolveAnalyticsDebugFloatingPosition,
type AnalyticsDebugPosition
} from "./floatingPosition.ts";
export interface AnalyticsDebugLabels {
clear: string;
clientTimestamp(value: number): string;
close: string;
count(value: number): string;
empty: string;
open: string;
title: string;
}
export interface AnalyticsDebugFloatingEntryProps {
labels: AnalyticsDebugLabels;
store: AnalyticsDebugEventStoreContract;
formatEventTime?: (timestamp: number) => string;
}
interface DragSession {
dragging: boolean;
floatingSize: {
height: number;
width: number;
};
pointerStart: {
x: number;
y: number;
};
startPosition: AnalyticsDebugPosition;
}
export function AnalyticsDebugFloatingEntry({
formatEventTime = defaultFormatEventTime,
labels,
store
}: AnalyticsDebugFloatingEntryProps) {
const [position, setPosition] = useState<AnalyticsDebugPosition | null>(null);
const [panelOpen, setPanelOpen] = useState(false);
const dragSessionRef = useRef<DragSession | null>(null);
const suppressNextClickRef = useRef(false);
const subscribe = useCallback(
(listener: () => void) => store.subscribe(listener),
[store]
);
const getSnapshot = useCallback(() => store.getSnapshot(), [store]);
const events = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const orderedEvents = useMemo(() => [...events].reverse(), [events]);
const handlePointerDown = useCallback(
(event: ReactPointerEvent<HTMLButtonElement>) => {
if (event.button !== 0) {
return;
}
const rect = event.currentTarget.getBoundingClientRect();
dragSessionRef.current = {
dragging: false,
floatingSize: {
height: rect.height,
width: rect.width
},
pointerStart: {
x: event.clientX,
y: event.clientY
},
startPosition: {
left: rect.left,
top: rect.top
}
};
event.currentTarget.setPointerCapture(event.pointerId);
},
[]
);
const handlePointerMove = useCallback(
(event: ReactPointerEvent<HTMLButtonElement>) => {
const dragSession = dragSessionRef.current;
if (!dragSession) {
return;
}
const pointerCurrent = {
x: event.clientX,
y: event.clientY
};
const dragging =
dragSession.dragging ||
hasAnalyticsDebugFloatingDragMoved({
pointerCurrent,
pointerStart: dragSession.pointerStart
});
if (!dragging) {
return;
}
dragSession.dragging = true;
suppressNextClickRef.current = true;
setPosition(
resolveAnalyticsDebugFloatingPosition({
floatingSize: dragSession.floatingSize,
pointerCurrent,
pointerStart: dragSession.pointerStart,
startPosition: dragSession.startPosition,
viewport: {
height: window.innerHeight,
width: window.innerWidth
}
})
);
},
[]
);
const handlePointerEnd = useCallback(
(event: ReactPointerEvent<HTMLButtonElement>) => {
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
event.currentTarget.releasePointerCapture(event.pointerId);
}
dragSessionRef.current = null;
},
[]
);
const handleClickCapture = useCallback(
(event: ReactMouseEvent<HTMLButtonElement>) => {
if (!suppressNextClickRef.current) {
return;
}
suppressNextClickRef.current = false;
event.preventDefault();
event.stopPropagation();
},
[]
);
return (
<div
className="fixed right-4 bottom-4 z-[var(--z-dialog-popover)]"
data-analytics-debug-floating-root="true"
style={
position
? {
bottom: "auto",
left: position.left,
right: "auto",
top: position.top
}
: undefined
}
>
<Popover open={panelOpen} onOpenChange={setPanelOpen}>
<PopoverTrigger asChild>
<Button
aria-label={labels.open}
className="relative size-11 touch-none rounded-full border border-[var(--border-1)] bg-[var(--background-fronted)] text-[var(--text-primary)] shadow-soft hover:bg-[var(--background-hover)]"
onClickCapture={handleClickCapture}
onPointerCancel={handlePointerEnd}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerEnd}
size="icon-lg"
variant="outline"
>
<HealthIcon size={18} />
{events.length > 0 ? (
<span className="absolute -top-1 -right-1 flex min-w-5 items-center justify-center rounded-full bg-[var(--accent-bg)] px-1 text-[10px] leading-5 text-[var(--accent)]">
{events.length}
</span>
) : null}
</Button>
</PopoverTrigger>
<PopoverContent
align="end"
className="w-[min(420px,calc(100vw-32px))] gap-0 p-0"
data-analytics-debug-floating-panel="true"
onInteractOutside={(event) => {
event.preventDefault();
}}
side="top"
sideOffset={10}
style={{ zIndex: "var(--z-dialog-popover)" }}
>
<div className="flex items-start justify-between gap-2 border-b border-[var(--border-1)] px-3 py-2">
<div className="min-w-0">
<div className="truncate text-[13px] font-medium text-[var(--text-primary)]">
{labels.title}
</div>
<div className="text-[11px] text-[var(--text-tertiary)]">
{labels.count(events.length)}
</div>
</div>
<div className="flex shrink-0 items-center gap-1">
<Button
disabled={events.length === 0}
onClick={() => store.clear()}
size="sm"
variant="ghost"
>
<DeleteIcon size={14} />
{labels.clear}
</Button>
<Button
aria-label={labels.close}
onClick={() => setPanelOpen(false)}
size="icon-sm"
title={labels.close}
type="button"
variant="ghost"
>
<CloseIcon className="size-4" />
</Button>
</div>
</div>
<ScrollArea className="h-[min(420px,calc(100vh-120px))]">
{orderedEvents.length === 0 ? (
<div className="px-3 py-8 text-center text-[13px] text-[var(--text-tertiary)]">
{labels.empty}
</div>
) : (
<div className="divide-y divide-[var(--border-1)]">
{orderedEvents.map((event, index) => (
<article
className="px-3 py-2.5"
key={`${event.name}-${event.clientTS}-${index}`}
>
<div className="mb-1 flex items-center justify-between gap-2">
<div className="min-w-0 truncate text-[13px] font-medium text-[var(--text-primary)]">
{event.name}
</div>
<Badge className="shrink-0" variant="secondary">
{formatEventTime(event.clientTS)}
</Badge>
</div>
<div className="mb-1 text-[11px] text-[var(--text-tertiary)]">
{labels.clientTimestamp(event.clientTS)}
</div>
<pre className="max-w-full whitespace-pre-wrap break-words rounded-md bg-[var(--transparency-block)] p-2 text-[11px] leading-4 text-[var(--text-secondary)] [overflow-wrap:anywhere]">
{formatParams(event.params)}
</pre>
</article>
))}
</div>
)}
</ScrollArea>
</PopoverContent>
</Popover>
</div>
);
}
function defaultFormatEventTime(timestamp: number): string {
if (!Number.isFinite(timestamp) || timestamp <= 0) {
return "--:--:--";
}
return new Date(timestamp).toLocaleTimeString();
}
function formatParams(params: unknown): string {
if (!params || typeof params !== "object") {
return "{}";
}
try {
return JSON.stringify(params, null, 2);
} catch {
return "{}";
}
}
@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import test from "node:test";
import {
hasAnalyticsDebugFloatingDragMoved,
resolveAnalyticsDebugFloatingPosition
} from "./floatingPosition.ts";
test("floating debug entry follows the pointer within viewport bounds", () => {
assert.deepEqual(
resolveAnalyticsDebugFloatingPosition({
floatingSize: { height: 44, width: 44 },
pointerCurrent: { x: 1000, y: 900 },
pointerStart: { x: 20, y: 20 },
startPosition: { left: 24, top: 24 },
viewport: { height: 600, width: 800 }
}),
{ left: 748, top: 548 }
);
assert.equal(
hasAnalyticsDebugFloatingDragMoved({
pointerCurrent: { x: 103, y: 104 },
pointerStart: { x: 100, y: 100 }
}),
false
);
});
@@ -0,0 +1,65 @@
const FLOATING_VIEWPORT_MARGIN = 8;
const FLOATING_DRAG_THRESHOLD = 5;
export interface AnalyticsDebugPoint {
x: number;
y: number;
}
export interface AnalyticsDebugPosition {
left: number;
top: number;
}
export interface AnalyticsDebugSize {
height: number;
width: number;
}
export interface ResolveAnalyticsDebugFloatingPositionInput {
floatingSize: AnalyticsDebugSize;
pointerCurrent: AnalyticsDebugPoint;
pointerStart: AnalyticsDebugPoint;
startPosition: AnalyticsDebugPosition;
viewport: AnalyticsDebugSize;
}
export function resolveAnalyticsDebugFloatingPosition({
floatingSize,
pointerCurrent,
pointerStart,
startPosition,
viewport
}: ResolveAnalyticsDebugFloatingPositionInput): AnalyticsDebugPosition {
return {
left: clamp(
startPosition.left + pointerCurrent.x - pointerStart.x,
FLOATING_VIEWPORT_MARGIN,
viewport.width - floatingSize.width - FLOATING_VIEWPORT_MARGIN
),
top: clamp(
startPosition.top + pointerCurrent.y - pointerStart.y,
FLOATING_VIEWPORT_MARGIN,
viewport.height - floatingSize.height - FLOATING_VIEWPORT_MARGIN
)
};
}
export function hasAnalyticsDebugFloatingDragMoved({
pointerCurrent,
pointerStart
}: Pick<
ResolveAnalyticsDebugFloatingPositionInput,
"pointerCurrent" | "pointerStart"
>): boolean {
return (
Math.hypot(
pointerCurrent.x - pointerStart.x,
pointerCurrent.y - pointerStart.y
) > FLOATING_DRAG_THRESHOLD
);
}
function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
@@ -0,0 +1,13 @@
export {
AnalyticsDebugFloatingEntry,
type AnalyticsDebugFloatingEntryProps,
type AnalyticsDebugLabels
} from "./AnalyticsDebugFloatingEntry.tsx";
export {
hasAnalyticsDebugFloatingDragMoved,
resolveAnalyticsDebugFloatingPosition,
type AnalyticsDebugPoint,
type AnalyticsDebugPosition,
type AnalyticsDebugSize,
type ResolveAnalyticsDebugFloatingPositionInput
} from "./floatingPosition.ts";
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../../../packages/configs/tsconfig/base.json",
"compilerOptions": {
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"types": ["node"]
},
"include": ["src/**/*"]
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from "tsup";
export default defineConfig({
clean: true,
dts: true,
entry: {
index: "src/index.ts",
"react/index": "src/react/index.ts"
},
external: ["react", "react-dom"],
format: ["esm"],
sourcemap: true
});