@xpufx/paseo-top 0.4.3 → 0.4.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/OVERVIEW.md +17 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +124 -7
- package/client/pill.tsx +724 -669
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +33 -0
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +310 -274
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -227
- package/package.json +14 -2
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +4 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
- package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +20 -0
- package/shared/multi-host.ts +156 -155
- package/shared/resources.ts +146 -1
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -29
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
package/index.client.tsx
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
TextInput as HostTextInput,
|
|
10
10
|
copyText,
|
|
11
11
|
} from "@getpaseo/plugin/client/react-native";
|
|
12
|
-
import { initClientHelpers } from "./client/vendor/paseo-plugin-helper/index";
|
|
13
|
-
import {
|
|
12
|
+
import { initClientHelpers } from "./client/vendor/paseo-plugin-helper/core/index";
|
|
13
|
+
import { composeFeatureModules } from "./shared/vendor/paseo-plugin-helper/index";
|
|
14
|
+
import { contributeClient as contributePillsClient } from "./client/pill";
|
|
14
15
|
import { TopTimelineTelemetryCard } from "./client/telemetry";
|
|
15
16
|
import { TurnCounterPanel } from "./client/turn-panel";
|
|
16
17
|
import {
|
|
@@ -30,42 +31,62 @@ initClientHelpers({
|
|
|
30
31
|
TextInput: HostTextInput,
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
const topTimelineTelemetryFeature = {
|
|
35
|
+
id: "top-timeline-telemetry",
|
|
36
|
+
contributeClient(client: PluginClientContext) {
|
|
37
|
+
return client.addTimelineRenderer({
|
|
38
|
+
kind: TOP_TIMELINE_KIND,
|
|
39
|
+
version: TOP_TIMELINE_VERSION,
|
|
40
|
+
schema: topTimelineTelemetrySchema,
|
|
41
|
+
Component: TopTimelineTelemetryCard,
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const topCustomPillsFeature = {
|
|
47
|
+
id: "top-custom-pills",
|
|
48
|
+
contributeClient(client: PluginClientContext) {
|
|
49
|
+
return contributePillsClient(client);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
const topTurnCounterFeature = {
|
|
54
|
+
id: "top-turn-counter",
|
|
55
|
+
contributeClient(client: PluginClientContext) {
|
|
56
|
+
// Agent-scoped panel: exposes the deterministic turn index for the active
|
|
57
|
+
// conversation (Turn #N + canonical seq) so a human and the agent resolve the
|
|
58
|
+
// same reference. See client/turn-counter.ts. Agent panels are opened through
|
|
59
|
+
// a command-center item, which forwards the active agent's openPanel.
|
|
60
|
+
const removeTurnPanel = client.addWorkspacePanel({
|
|
61
|
+
id: "top-turn-counter",
|
|
62
|
+
title: "Turn Counter",
|
|
63
|
+
icon: "Repeat",
|
|
64
|
+
context: "agent",
|
|
65
|
+
Component: TurnCounterPanel,
|
|
66
|
+
});
|
|
67
|
+
const removeTurnCommand = client.addCommandCenterItem({
|
|
68
|
+
id: "top-turn-counter",
|
|
69
|
+
title: "Turn Counter",
|
|
70
|
+
icon: "Repeat",
|
|
71
|
+
context: "agent",
|
|
72
|
+
keywords: ["turn", "seq", "counter", "conversation"],
|
|
73
|
+
onSelect(ctx) {
|
|
74
|
+
ctx.openPanel("top-turn-counter");
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
return () => {
|
|
78
|
+
removeTurnCommand();
|
|
79
|
+
removeTurnPanel();
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
id: "top-turn-counter",
|
|
49
|
-
title: "Turn Counter",
|
|
50
|
-
icon: "Repeat",
|
|
51
|
-
context: "agent",
|
|
52
|
-
Component: TurnCounterPanel,
|
|
53
|
-
});
|
|
54
|
-
const removeTurnCommand = client.addCommandCenterItem({
|
|
55
|
-
id: "top-turn-counter",
|
|
56
|
-
title: "Turn Counter",
|
|
57
|
-
icon: "Repeat",
|
|
58
|
-
keywords: ["turn", "seq", "counter", "conversation"],
|
|
59
|
-
context: "agent",
|
|
60
|
-
onSelect(ctx) {
|
|
61
|
-
ctx.openPanel("top-turn-counter");
|
|
62
|
-
},
|
|
63
|
-
});
|
|
84
|
+
const topClientFeatures = composeFeatureModules<any, PluginClientContext>([
|
|
85
|
+
topTimelineTelemetryFeature,
|
|
86
|
+
topCustomPillsFeature,
|
|
87
|
+
topTurnCounterFeature,
|
|
88
|
+
]);
|
|
64
89
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
removeTurnPanel();
|
|
68
|
-
removeTimelineRenderer();
|
|
69
|
-
cleanupClient();
|
|
70
|
-
};
|
|
90
|
+
export default function contribute(client: PluginClientContext) {
|
|
91
|
+
return topClientFeatures.contributeClient(client);
|
|
71
92
|
}
|
package/index.server.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
guardRpcHandler,
|
|
4
|
+
createCustomPillsFeature,
|
|
5
|
+
createTimelineTelemetryFeature,
|
|
6
|
+
} from "./server/vendor/paseo-plugin-helper/index";
|
|
7
|
+
import { composeFeatureModules } from "./shared/vendor/paseo-plugin-helper/index";
|
|
8
|
+
import { createPermissionAuditFeature } from "./server/vendor/permission-audit";
|
|
3
9
|
import {
|
|
4
10
|
getSystemResourcesRpc,
|
|
5
11
|
getCustomPillsRpc,
|
|
@@ -31,260 +37,283 @@ import {
|
|
|
31
37
|
} from "./server/resources";
|
|
32
38
|
import { resolveTimelineCadence, shouldAppendTimelineForTurn } from "./shared/resources";
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
let lastSystemResources: SystemResources | null = null;
|
|
41
|
+
const lastWarnAt = { timeout: 0, saturated: 0 };
|
|
42
|
+
const WARN_COOLDOWN_MS = 60_000;
|
|
43
|
+
const guardedSystemResources = guardRpcHandler(
|
|
44
|
+
async (input: Parameters<typeof handleGetSystemResources>[0]) => {
|
|
45
|
+
const resources = await handleGetSystemResources(input);
|
|
46
|
+
lastSystemResources = resources;
|
|
47
|
+
return resources;
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
timeoutMs: 5000,
|
|
51
|
+
maxInflight: 4,
|
|
52
|
+
getStale: () => lastSystemResources,
|
|
53
|
+
onTimeout: ({ timeoutMs }) => {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (now - lastWarnAt.timeout < WARN_COOLDOWN_MS) return;
|
|
56
|
+
lastWarnAt.timeout = now;
|
|
57
|
+
log.warn("system-resources handler timed out", { timeoutMs });
|
|
49
58
|
},
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const now = Date.now();
|
|
56
|
-
if (now - lastWarnAt.timeout < WARN_COOLDOWN_MS) return;
|
|
57
|
-
lastWarnAt.timeout = now;
|
|
58
|
-
log.warn("system-resources handler timed out", { timeoutMs });
|
|
59
|
-
},
|
|
60
|
-
onSaturated: ({ maxInflight }) => {
|
|
61
|
-
const now = Date.now();
|
|
62
|
-
if (now - lastWarnAt.saturated < WARN_COOLDOWN_MS) return;
|
|
63
|
-
lastWarnAt.saturated = now;
|
|
64
|
-
log.warn("system-resources handler saturated, serving stale", { maxInflight });
|
|
65
|
-
},
|
|
59
|
+
onSaturated: ({ maxInflight }) => {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (now - lastWarnAt.saturated < WARN_COOLDOWN_MS) return;
|
|
62
|
+
lastWarnAt.saturated = now;
|
|
63
|
+
log.warn("system-resources handler saturated, serving stale", { maxInflight });
|
|
66
64
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
server.handle(topSettingsContract.get, handleGetSettings);
|
|
70
|
-
server.handle(topSettingsContract.update, handleUpdateSettings);
|
|
71
|
-
server.handle(topSettingsContract.reset, handleResetSettings);
|
|
72
|
-
server.handle(getSystemResourcesRpc, guardedSystemResources);
|
|
73
|
-
server.handle(getCustomPillsRpc, handleGetCustomPills);
|
|
74
|
-
server.handle(listCustomPillsRpc, handleListCustomPills);
|
|
75
|
-
server.handle(runCustomPillModalCommandRpc, handleRunCustomPillModalCommand);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
const customPillsModule = createCustomPillsFeature<PluginServerContext>({
|
|
69
|
+
id: "top-custom-pills",
|
|
70
|
+
poller: customPillPoller,
|
|
71
|
+
contracts: {
|
|
72
|
+
get: getCustomPillsRpc,
|
|
73
|
+
list: listCustomPillsRpc,
|
|
74
|
+
run: runCustomPillModalCommandRpc,
|
|
75
|
+
},
|
|
76
|
+
handlers: {
|
|
77
|
+
get: handleGetCustomPills,
|
|
78
|
+
list: handleListCustomPills,
|
|
79
|
+
run: handleRunCustomPillModalCommand,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if ((event.agent as any)?.lastUsage) {
|
|
94
|
-
setLastLiveUsage((event.agent as any).lastUsage);
|
|
95
|
-
}
|
|
96
|
-
void context.paseo.agents
|
|
97
|
-
.ref(event.agent.id)
|
|
98
|
-
.refresh()
|
|
99
|
-
.then((refetched) => {
|
|
100
|
-
if (refetched && (refetched.agent as any)?.lastUsage) {
|
|
101
|
-
setLastLiveUsage((refetched.agent as any).lastUsage);
|
|
102
|
-
}
|
|
103
|
-
})
|
|
104
|
-
.catch(() => {});
|
|
105
|
-
void collectGitDiffStat(event.agent.cwd).then(
|
|
106
|
-
(before) => {
|
|
107
|
-
if (before) {
|
|
108
|
-
turnGitBefore.set(event.agent.id, before);
|
|
109
|
-
} else {
|
|
110
|
-
turnGitBefore.delete(event.agent.id);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
() => {
|
|
114
|
-
turnGitBefore.delete(event.agent.id);
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
});
|
|
83
|
+
const permissionAuditModule = createPermissionAuditFeature<PluginServerContext>({
|
|
84
|
+
logger: log,
|
|
85
|
+
});
|
|
118
86
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
87
|
+
async function appendTurnCard(
|
|
88
|
+
event: any,
|
|
89
|
+
context: any,
|
|
90
|
+
startTime: number | undefined,
|
|
91
|
+
gitBefore: { insertions: number; deletions: number; filesChanged: number } | undefined,
|
|
92
|
+
turnIndex: number,
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
try {
|
|
95
|
+
const settings = await handleGetSettings();
|
|
96
|
+
const cadence = resolveTimelineCadence(settings);
|
|
97
|
+
if (!shouldAppendTimelineForTurn(cadence, turnIndex)) {
|
|
98
|
+
return;
|
|
122
99
|
}
|
|
123
|
-
|
|
100
|
+
const durationMs = startTime ? Date.now() - startTime : undefined;
|
|
124
101
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
startTime: number | undefined,
|
|
129
|
-
gitBefore: { insertions: number; deletions: number; filesChanged: number } | undefined,
|
|
130
|
-
turnIndex: number,
|
|
131
|
-
): Promise<void> {
|
|
102
|
+
let agentModel: string | null = null;
|
|
103
|
+
let agentProvider: string | null = event.agent.provider ?? null;
|
|
104
|
+
let agentTitle: string | null = event.agent.title ?? null;
|
|
132
105
|
try {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
106
|
+
const refetched = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
107
|
+
agentModel = refetched?.agent?.model ?? agentModel;
|
|
108
|
+
agentProvider = refetched?.agent?.provider ?? agentProvider;
|
|
109
|
+
agentTitle = refetched?.agent?.title ?? agentTitle;
|
|
110
|
+
let liveUsage =
|
|
111
|
+
(refetched?.agent?.lastUsage as LiveUsage | null | undefined) ??
|
|
112
|
+
((event.agent as any)?.lastUsage as LiveUsage | null | undefined) ??
|
|
113
|
+
null;
|
|
114
|
+
const lacksTokens =
|
|
115
|
+
liveUsage?.inputTokens == null &&
|
|
116
|
+
liveUsage?.outputTokens == null &&
|
|
117
|
+
(liveUsage as any)?.contextWindowUsedTokens == null;
|
|
118
|
+
if (lacksTokens) {
|
|
119
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
120
|
+
await sleep(150);
|
|
121
|
+
try {
|
|
122
|
+
const retry1 = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
123
|
+
liveUsage =
|
|
124
|
+
(retry1?.agent?.lastUsage as LiveUsage | null | undefined) ?? liveUsage;
|
|
125
|
+
} catch {}
|
|
126
|
+
const stillLacks =
|
|
153
127
|
liveUsage?.inputTokens == null &&
|
|
154
128
|
liveUsage?.outputTokens == null &&
|
|
155
129
|
(liveUsage as any)?.contextWindowUsedTokens == null;
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
await sleep(150);
|
|
130
|
+
if (stillLacks) {
|
|
131
|
+
await sleep(250);
|
|
159
132
|
try {
|
|
160
|
-
const
|
|
133
|
+
const retry2 = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
161
134
|
liveUsage =
|
|
162
|
-
(
|
|
135
|
+
(retry2?.agent?.lastUsage as LiveUsage | null | undefined) ?? liveUsage;
|
|
163
136
|
} catch {}
|
|
164
|
-
const stillLacks =
|
|
165
|
-
liveUsage?.inputTokens == null &&
|
|
166
|
-
liveUsage?.outputTokens == null &&
|
|
167
|
-
(liveUsage as any)?.contextWindowUsedTokens == null;
|
|
168
|
-
if (stillLacks) {
|
|
169
|
-
await sleep(250);
|
|
170
|
-
try {
|
|
171
|
-
const retry2 = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
172
|
-
liveUsage =
|
|
173
|
-
(retry2?.agent?.lastUsage as LiveUsage | null | undefined) ?? liveUsage;
|
|
174
|
-
} catch {}
|
|
175
|
-
}
|
|
176
137
|
}
|
|
177
|
-
setLastLiveUsage(liveUsage);
|
|
178
|
-
} catch {
|
|
179
|
-
// Model, provider, and title stay at event snapshot values; the card renders placeholders
|
|
180
138
|
}
|
|
139
|
+
setLastLiveUsage(liveUsage);
|
|
140
|
+
} catch {
|
|
141
|
+
// Model, provider, and title stay at event snapshot values; the card renders placeholders
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const telemetry = await collectTurnTelemetry(
|
|
145
|
+
event.turnId,
|
|
146
|
+
event.agent.id,
|
|
147
|
+
event.outcome,
|
|
148
|
+
durationMs,
|
|
149
|
+
{
|
|
150
|
+
cwd: event.agent.cwd,
|
|
151
|
+
provider: agentProvider,
|
|
152
|
+
title: agentTitle,
|
|
153
|
+
model: agentModel,
|
|
154
|
+
timeline: event.timeline,
|
|
155
|
+
gitBefore: gitBefore ?? null,
|
|
156
|
+
liveUsage: getLastLiveUsage(),
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await context.paseo.agents.ref(event.agent.id).timeline.append({
|
|
161
|
+
type: "plugin",
|
|
162
|
+
id: `top-turn-${event.turnId ?? Date.now()}`,
|
|
163
|
+
kind: TOP_TIMELINE_KIND,
|
|
164
|
+
version: TOP_TIMELINE_VERSION,
|
|
165
|
+
data: telemetry,
|
|
166
|
+
});
|
|
181
167
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
168
|
+
log.info("Appended turn telemetry to timeline", {
|
|
169
|
+
agentId: event.agent.id,
|
|
170
|
+
turnId: event.turnId,
|
|
171
|
+
outcome: event.outcome.kind,
|
|
172
|
+
durationMs,
|
|
173
|
+
cpuPercent: telemetry.cpuPercent,
|
|
174
|
+
memPercent: telemetry.memPercent,
|
|
175
|
+
});
|
|
176
|
+
} catch (err) {
|
|
177
|
+
log.warn("Failed to record turn telemetry", {
|
|
178
|
+
agentId: event.agent.id,
|
|
179
|
+
turnId: event.turnId,
|
|
180
|
+
error: err instanceof Error ? err.message : String(err),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const timelineTelemetryModule = createTimelineTelemetryFeature<PluginServerContext>({
|
|
186
|
+
id: "top-timeline-telemetry",
|
|
187
|
+
serverTap: (server) => {
|
|
188
|
+
const turnStartTimes = new Map<string, number>();
|
|
189
|
+
const turnGitBefore = new Map<string, { insertions: number; deletions: number; filesChanged: number }>();
|
|
190
|
+
const activeTurnIds = new Map<string, string | null>();
|
|
191
|
+
const lastCanceledAt = new Map<string, number>();
|
|
192
|
+
const lastCanceledUserMessages = new Map<string, number>();
|
|
193
|
+
const turnCounters = new Map<string, number>();
|
|
194
|
+
|
|
195
|
+
const unsubscribeTurnStarted = server.on("agent.turn_started", (event, context) => {
|
|
196
|
+
turnStartTimes.set(event.agent.id, Date.now());
|
|
197
|
+
activeTurnIds.set(event.agent.id, event.turnId ?? null);
|
|
198
|
+
if ((event.agent as any)?.lastUsage) {
|
|
199
|
+
setLastLiveUsage((event.agent as any).lastUsage);
|
|
200
|
+
}
|
|
201
|
+
void context.paseo.agents
|
|
202
|
+
.ref(event.agent.id)
|
|
203
|
+
.refresh()
|
|
204
|
+
.then((refetched) => {
|
|
205
|
+
if (refetched && (refetched.agent as any)?.lastUsage) {
|
|
206
|
+
setLastLiveUsage((refetched.agent as any).lastUsage);
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
.catch(() => {});
|
|
210
|
+
void collectGitDiffStat(event.agent.cwd).then(
|
|
211
|
+
(before) => {
|
|
212
|
+
if (before) {
|
|
213
|
+
turnGitBefore.set(event.agent.id, before);
|
|
214
|
+
} else {
|
|
215
|
+
turnGitBefore.delete(event.agent.id);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
() => {
|
|
219
|
+
turnGitBefore.delete(event.agent.id);
|
|
195
220
|
},
|
|
196
221
|
);
|
|
222
|
+
});
|
|
197
223
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
data: telemetry,
|
|
204
|
-
});
|
|
224
|
+
const unsubscribeAgentCreated = server.on("agent.created", (event) => {
|
|
225
|
+
if ((event.agent as any)?.lastUsage) {
|
|
226
|
+
setLastLiveUsage((event.agent as any).lastUsage);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
205
229
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
turnId: event.turnId,
|
|
218
|
-
error: err instanceof Error ? err.message : String(err),
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
230
|
+
const unsubscribeTurnEnded = server.on("agent.turn_ended", (event, context) => {
|
|
231
|
+
const settingsPromise = handleGetSettings();
|
|
232
|
+
void settingsPromise.then((settings) => {
|
|
233
|
+
if (settings.recordTurnTelemetry === false) {
|
|
234
|
+
turnStartTimes.delete(event.agent.id);
|
|
235
|
+
turnGitBefore.delete(event.agent.id);
|
|
236
|
+
activeTurnIds.delete(event.agent.id);
|
|
237
|
+
lastCanceledAt.delete(event.agent.id);
|
|
238
|
+
lastCanceledUserMessages.delete(event.agent.id);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
222
241
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
const activeTurnId = activeTurnIds.get(event.agent.id);
|
|
243
|
+
const eventTurnId = event.turnId ?? null;
|
|
244
|
+
const eventUserMessages = countTurns(event.timeline) ?? 0;
|
|
245
|
+
const duplicate =
|
|
246
|
+
isStaleTurnEnd(activeTurnId, eventTurnId) ||
|
|
247
|
+
isInterruptEcho({
|
|
248
|
+
lastCanceledAt: lastCanceledAt.get(event.agent.id) ?? null,
|
|
249
|
+
lastCanceledUserMessages: lastCanceledUserMessages.get(event.agent.id) ?? null,
|
|
250
|
+
eventUserMessages,
|
|
251
|
+
now: Date.now(),
|
|
252
|
+
});
|
|
253
|
+
if (duplicate) {
|
|
254
|
+
log.info("Skipped duplicate turn-end telemetry", {
|
|
255
|
+
agentId: event.agent.id,
|
|
256
|
+
turnId: event.turnId,
|
|
257
|
+
activeTurnId: activeTurnId ?? null,
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
229
261
|
activeTurnIds.delete(event.agent.id);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
262
|
+
if (event.outcome.kind === "canceled") {
|
|
263
|
+
lastCanceledAt.set(event.agent.id, Date.now());
|
|
264
|
+
lastCanceledUserMessages.set(event.agent.id, eventUserMessages);
|
|
265
|
+
} else {
|
|
266
|
+
lastCanceledAt.delete(event.agent.id);
|
|
267
|
+
lastCanceledUserMessages.delete(event.agent.id);
|
|
268
|
+
}
|
|
234
269
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
lastCanceledUserMessages: lastCanceledUserMessages.get(event.agent.id) ?? null,
|
|
245
|
-
eventUserMessages,
|
|
246
|
-
now: Date.now(),
|
|
247
|
-
});
|
|
248
|
-
if (duplicate) {
|
|
249
|
-
log.info("Skipped duplicate turn-end telemetry", {
|
|
270
|
+
const startTime = turnStartTimes.get(event.agent.id);
|
|
271
|
+
turnStartTimes.delete(event.agent.id);
|
|
272
|
+
const gitBefore = turnGitBefore.get(event.agent.id);
|
|
273
|
+
turnGitBefore.delete(event.agent.id);
|
|
274
|
+
const turnIndex = (turnCounters.get(event.agent.id) ?? 0) + 1;
|
|
275
|
+
turnCounters.set(event.agent.id, turnIndex);
|
|
276
|
+
void appendTurnCard(event, context, startTime, gitBefore, turnIndex);
|
|
277
|
+
}).catch((err) => {
|
|
278
|
+
log.warn("Failed to record turn telemetry", {
|
|
250
279
|
agentId: event.agent.id,
|
|
251
280
|
turnId: event.turnId,
|
|
252
|
-
|
|
281
|
+
error: err instanceof Error ? err.message : String(err),
|
|
253
282
|
});
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
activeTurnIds.delete(event.agent.id);
|
|
257
|
-
if (event.outcome.kind === "canceled") {
|
|
258
|
-
lastCanceledAt.set(event.agent.id, Date.now());
|
|
259
|
-
lastCanceledUserMessages.set(event.agent.id, eventUserMessages);
|
|
260
|
-
} else {
|
|
261
|
-
lastCanceledAt.delete(event.agent.id);
|
|
262
|
-
lastCanceledUserMessages.delete(event.agent.id);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const startTime = turnStartTimes.get(event.agent.id);
|
|
266
|
-
turnStartTimes.delete(event.agent.id);
|
|
267
|
-
const gitBefore = turnGitBefore.get(event.agent.id);
|
|
268
|
-
turnGitBefore.delete(event.agent.id);
|
|
269
|
-
const turnIndex = (turnCounters.get(event.agent.id) ?? 0) + 1;
|
|
270
|
-
turnCounters.set(event.agent.id, turnIndex);
|
|
271
|
-
void appendTurnCard(event, context, startTime, gitBefore, turnIndex);
|
|
272
|
-
}).catch((err) => {
|
|
273
|
-
log.warn("Failed to record turn telemetry", {
|
|
274
|
-
agentId: event.agent.id,
|
|
275
|
-
turnId: event.turnId,
|
|
276
|
-
error: err instanceof Error ? err.message : String(err),
|
|
277
283
|
});
|
|
278
284
|
});
|
|
279
|
-
});
|
|
280
285
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
return () => {
|
|
287
|
+
activeTurnIds.clear();
|
|
288
|
+
lastCanceledAt.clear();
|
|
289
|
+
lastCanceledUserMessages.clear();
|
|
290
|
+
turnStartTimes.clear();
|
|
291
|
+
turnGitBefore.clear();
|
|
292
|
+
turnCounters.clear();
|
|
293
|
+
unsubscribeTurnStarted();
|
|
294
|
+
unsubscribeAgentCreated();
|
|
295
|
+
unsubscribeTurnEnded();
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const topCoreModule = {
|
|
301
|
+
id: "top-core",
|
|
302
|
+
contributeServer(server: PluginServerContext) {
|
|
303
|
+
server.handle(topSettingsContract.get, handleGetSettings);
|
|
304
|
+
server.handle(topSettingsContract.update, handleUpdateSettings);
|
|
305
|
+
server.handle(topSettingsContract.reset, handleResetSettings);
|
|
306
|
+
server.handle(getSystemResourcesRpc, guardedSystemResources);
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const topFeatures = composeFeatureModules<PluginServerContext>([
|
|
311
|
+
customPillsModule,
|
|
312
|
+
permissionAuditModule,
|
|
313
|
+
timelineTelemetryModule,
|
|
314
|
+
topCoreModule,
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
export default function contribute(server: PluginServerContext) {
|
|
318
|
+
return topFeatures.contributeServer(server);
|
|
290
319
|
}
|