@xpufx/paseo-top 0.4.4 → 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 +94 -1
- package/client/pill.tsx +690 -614
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +1 -1
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +308 -276
- 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} +9 -3
- 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} +51 -18
- 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/{surface.tsx → lifecycle/surface.tsx} +6 -6
- 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} +50 -58
- 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 -234
- package/package.json +7 -4
- 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 +2 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- 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 +18 -67
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +1 -1
- package/shared/multi-host.ts +153 -159
- package/shared/resources.ts +145 -0
- 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 +1 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
- 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 -168
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
- 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 -88
- package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
- 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 -132
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
- 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 -26
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
- 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 -30
- 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 -368
- 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/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/tickets.tsx +0 -766
- /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,6 +1,11 @@
|
|
|
1
1
|
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
2
|
-
import {
|
|
3
|
-
|
|
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";
|
|
4
9
|
import {
|
|
5
10
|
getSystemResourcesRpc,
|
|
6
11
|
getCustomPillsRpc,
|
|
@@ -32,266 +37,283 @@ import {
|
|
|
32
37
|
} from "./server/resources";
|
|
33
38
|
import { resolveTimelineCadence, shouldAppendTimelineForTurn } from "./shared/resources";
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 });
|
|
50
58
|
},
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const now = Date.now();
|
|
57
|
-
if (now - lastWarnAt.timeout < WARN_COOLDOWN_MS) return;
|
|
58
|
-
lastWarnAt.timeout = now;
|
|
59
|
-
log.warn("system-resources handler timed out", { timeoutMs });
|
|
60
|
-
},
|
|
61
|
-
onSaturated: ({ maxInflight }) => {
|
|
62
|
-
const now = Date.now();
|
|
63
|
-
if (now - lastWarnAt.saturated < WARN_COOLDOWN_MS) return;
|
|
64
|
-
lastWarnAt.saturated = now;
|
|
65
|
-
log.warn("system-resources handler saturated, serving stale", { maxInflight });
|
|
66
|
-
},
|
|
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 });
|
|
67
64
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
server.handle(topSettingsContract.get, handleGetSettings);
|
|
71
|
-
server.handle(topSettingsContract.update, handleUpdateSettings);
|
|
72
|
-
server.handle(topSettingsContract.reset, handleResetSettings);
|
|
73
|
-
server.handle(getSystemResourcesRpc, guardedSystemResources);
|
|
74
|
-
server.handle(getCustomPillsRpc, handleGetCustomPills);
|
|
75
|
-
server.handle(listCustomPillsRpc, handleListCustomPills);
|
|
76
|
-
server.handle(runCustomPillModalCommandRpc, handleRunCustomPillModalCommand);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
+
});
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// to drop terminals that cannot belong to it (see isStaleTurnEnd).
|
|
87
|
-
const activeTurnIds = new Map<string, string | null>();
|
|
88
|
-
// Time and timeline user_message count of the last canceled terminal, used to
|
|
89
|
-
// drop the daemon's extra interrupt terminal (see isInterruptEcho).
|
|
90
|
-
const lastCanceledAt = new Map<string, number>();
|
|
91
|
-
const lastCanceledUserMessages = new Map<string, number>();
|
|
92
|
-
// Per-agent turn counter for the timeline cadence option (0 = never,
|
|
93
|
-
// 1 = every turn, N>1 = every Nth turn). Counts deduped turn_ended events.
|
|
94
|
-
const turnCounters = new Map<string, number>();
|
|
95
|
-
|
|
96
|
-
const unsubscribeTurnStarted = server.on("agent.turn_started", (event, context) => {
|
|
97
|
-
turnStartTimes.set(event.agent.id, Date.now());
|
|
98
|
-
activeTurnIds.set(event.agent.id, event.turnId ?? null);
|
|
99
|
-
if ((event.agent as any)?.lastUsage) {
|
|
100
|
-
setLastLiveUsage((event.agent as any).lastUsage);
|
|
101
|
-
}
|
|
102
|
-
void context.paseo.agents
|
|
103
|
-
.ref(event.agent.id)
|
|
104
|
-
.refresh()
|
|
105
|
-
.then((refetched) => {
|
|
106
|
-
if (refetched && (refetched.agent as any)?.lastUsage) {
|
|
107
|
-
setLastLiveUsage((refetched.agent as any).lastUsage);
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
.catch(() => {});
|
|
111
|
-
void collectGitDiffStat(event.agent.cwd).then(
|
|
112
|
-
(before) => {
|
|
113
|
-
if (before) {
|
|
114
|
-
turnGitBefore.set(event.agent.id, before);
|
|
115
|
-
} else {
|
|
116
|
-
turnGitBefore.delete(event.agent.id);
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
() => {
|
|
120
|
-
turnGitBefore.delete(event.agent.id);
|
|
121
|
-
},
|
|
122
|
-
);
|
|
123
|
-
});
|
|
83
|
+
const permissionAuditModule = createPermissionAuditFeature<PluginServerContext>({
|
|
84
|
+
logger: log,
|
|
85
|
+
});
|
|
124
86
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
128
99
|
}
|
|
129
|
-
|
|
100
|
+
const durationMs = startTime ? Date.now() - startTime : undefined;
|
|
130
101
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
startTime: number | undefined,
|
|
135
|
-
gitBefore: { insertions: number; deletions: number; filesChanged: number } | undefined,
|
|
136
|
-
turnIndex: number,
|
|
137
|
-
): 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;
|
|
138
105
|
try {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 =
|
|
159
127
|
liveUsage?.inputTokens == null &&
|
|
160
128
|
liveUsage?.outputTokens == null &&
|
|
161
129
|
(liveUsage as any)?.contextWindowUsedTokens == null;
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
await sleep(150);
|
|
130
|
+
if (stillLacks) {
|
|
131
|
+
await sleep(250);
|
|
165
132
|
try {
|
|
166
|
-
const
|
|
133
|
+
const retry2 = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
167
134
|
liveUsage =
|
|
168
|
-
(
|
|
135
|
+
(retry2?.agent?.lastUsage as LiveUsage | null | undefined) ?? liveUsage;
|
|
169
136
|
} catch {}
|
|
170
|
-
const stillLacks =
|
|
171
|
-
liveUsage?.inputTokens == null &&
|
|
172
|
-
liveUsage?.outputTokens == null &&
|
|
173
|
-
(liveUsage as any)?.contextWindowUsedTokens == null;
|
|
174
|
-
if (stillLacks) {
|
|
175
|
-
await sleep(250);
|
|
176
|
-
try {
|
|
177
|
-
const retry2 = await context.paseo.agents.ref(event.agent.id).refresh();
|
|
178
|
-
liveUsage =
|
|
179
|
-
(retry2?.agent?.lastUsage as LiveUsage | null | undefined) ?? liveUsage;
|
|
180
|
-
} catch {}
|
|
181
|
-
}
|
|
182
137
|
}
|
|
183
|
-
setLastLiveUsage(liveUsage);
|
|
184
|
-
} catch {
|
|
185
|
-
// Model, provider, and title stay at event snapshot values; the card renders placeholders
|
|
186
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
|
+
});
|
|
167
|
+
|
|
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>();
|
|
187
194
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
201
220
|
},
|
|
202
221
|
);
|
|
222
|
+
});
|
|
203
223
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
data: telemetry,
|
|
210
|
-
});
|
|
224
|
+
const unsubscribeAgentCreated = server.on("agent.created", (event) => {
|
|
225
|
+
if ((event.agent as any)?.lastUsage) {
|
|
226
|
+
setLastLiveUsage((event.agent as any).lastUsage);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
211
229
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
turnId: event.turnId,
|
|
224
|
-
error: err instanceof Error ? err.message : String(err),
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
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
|
+
}
|
|
228
241
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
}
|
|
235
261
|
activeTurnIds.delete(event.agent.id);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
269
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
lastCanceledUserMessages: lastCanceledUserMessages.get(event.agent.id) ?? null,
|
|
251
|
-
eventUserMessages,
|
|
252
|
-
now: Date.now(),
|
|
253
|
-
});
|
|
254
|
-
if (duplicate) {
|
|
255
|
-
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", {
|
|
256
279
|
agentId: event.agent.id,
|
|
257
280
|
turnId: event.turnId,
|
|
258
|
-
|
|
281
|
+
error: err instanceof Error ? err.message : String(err),
|
|
259
282
|
});
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
activeTurnIds.delete(event.agent.id);
|
|
263
|
-
if (event.outcome.kind === "canceled") {
|
|
264
|
-
lastCanceledAt.set(event.agent.id, Date.now());
|
|
265
|
-
lastCanceledUserMessages.set(event.agent.id, eventUserMessages);
|
|
266
|
-
} else {
|
|
267
|
-
lastCanceledAt.delete(event.agent.id);
|
|
268
|
-
lastCanceledUserMessages.delete(event.agent.id);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const startTime = turnStartTimes.get(event.agent.id);
|
|
272
|
-
turnStartTimes.delete(event.agent.id);
|
|
273
|
-
const gitBefore = turnGitBefore.get(event.agent.id);
|
|
274
|
-
turnGitBefore.delete(event.agent.id);
|
|
275
|
-
const turnIndex = (turnCounters.get(event.agent.id) ?? 0) + 1;
|
|
276
|
-
turnCounters.set(event.agent.id, turnIndex);
|
|
277
|
-
void appendTurnCard(event, context, startTime, gitBefore, turnIndex);
|
|
278
|
-
}).catch((err) => {
|
|
279
|
-
log.warn("Failed to record turn telemetry", {
|
|
280
|
-
agentId: event.agent.id,
|
|
281
|
-
turnId: event.turnId,
|
|
282
|
-
error: err instanceof Error ? err.message : String(err),
|
|
283
283
|
});
|
|
284
284
|
});
|
|
285
|
-
});
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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);
|
|
297
319
|
}
|