@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
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { PluginRpcContract } from "./rpc";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cleanup handle returned by a feature contribution.
|
|
5
|
+
*
|
|
6
|
+
* A disposer MUST be idempotent: calling it more than once must not throw and
|
|
7
|
+
* must not tear down anything twice. `composeFeatureModules` additionally
|
|
8
|
+
* wraps every disposer it receives so the *composed* teardown is idempotent
|
|
9
|
+
* even if an individual module forgets.
|
|
10
|
+
*/
|
|
11
|
+
export type FeatureDisposer = () => void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A self-contained, droppable unit of plugin functionality.
|
|
15
|
+
*
|
|
16
|
+
* A feature module owns one concern (custom pills, timeline telemetry,
|
|
17
|
+
* permission auditing, …) and exposes it through up to two contribution hooks:
|
|
18
|
+
* `contributeServer` for the daemon-side `PluginServerContext` and
|
|
19
|
+
* `contributeClient` for the client-side `PluginClientContext`. Both hooks are
|
|
20
|
+
* optional; a module may contribute to one side, both, or neither.
|
|
21
|
+
*
|
|
22
|
+
* Lifecycle contract
|
|
23
|
+
* ------------------
|
|
24
|
+
* - A contribution hook runs at most once per `contribute*` call on the
|
|
25
|
+
* composing plugin entry.
|
|
26
|
+
* - It may return a {@link FeatureDisposer} for cleanup, or `void` when it owns
|
|
27
|
+
* nothing to release.
|
|
28
|
+
* - Returned disposers MUST be idempotent (see {@link FeatureDisposer}).
|
|
29
|
+
* - The plugin entry composes modules with {@link composeFeatureModules} and
|
|
30
|
+
* returns the combined disposer to Paseo.
|
|
31
|
+
* - `contracts` is declarative metadata (name-keyed RPC contracts the module
|
|
32
|
+
* owns); it is not registered automatically. The composing entry still calls
|
|
33
|
+
* `server.handle(contract, handler)`.
|
|
34
|
+
*
|
|
35
|
+
* Context types are generic because `paseo-plugin-helper/shared` must not
|
|
36
|
+
* import the Paseo SDK. Type a module with the SDK contexts at the call site:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
40
|
+
* import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
41
|
+
* import type { FeatureModule } from "paseo-plugin-helper/shared";
|
|
42
|
+
*
|
|
43
|
+
* export const customPills: FeatureModule<PluginServerContext, PluginClientContext> = {
|
|
44
|
+
* id: "custom-pills",
|
|
45
|
+
* contracts: { list: listCustomPillsRpc },
|
|
46
|
+
* contributeServer(server) {
|
|
47
|
+
* const { unsubscribe } = registerCustomPillsServer(server);
|
|
48
|
+
* return unsubscribe;
|
|
49
|
+
* },
|
|
50
|
+
* contributeClient(client) {
|
|
51
|
+
* const remove = registerCustomPills(client);
|
|
52
|
+
* return remove;
|
|
53
|
+
* },
|
|
54
|
+
* };
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export interface FeatureModule<
|
|
58
|
+
TServerContext = unknown,
|
|
59
|
+
TClientContext = unknown,
|
|
60
|
+
> {
|
|
61
|
+
/** Stable, plugin-unique module id (e.g. `"custom-pills"`). */
|
|
62
|
+
readonly id: string;
|
|
63
|
+
/** Daemon-side contribution. Returns an idempotent disposer, or `void`. */
|
|
64
|
+
readonly contributeServer?: (server: TServerContext) => FeatureDisposer | void;
|
|
65
|
+
/** Client-side contribution. Returns an idempotent disposer, or `void`. */
|
|
66
|
+
readonly contributeClient?: (client: TClientContext) => FeatureDisposer | void;
|
|
67
|
+
/** RPC contracts this module owns, keyed by a local name. */
|
|
68
|
+
readonly contracts?: Record<string, PluginRpcContract>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A composed set of feature modules. `contributeServer` / `contributeClient`
|
|
73
|
+
* run every module's hook for that side and return a single combined,
|
|
74
|
+
* idempotent disposer that tears the modules down in reverse order.
|
|
75
|
+
*/
|
|
76
|
+
export interface FeatureComposition<
|
|
77
|
+
TServerContext = unknown,
|
|
78
|
+
TClientContext = unknown,
|
|
79
|
+
> {
|
|
80
|
+
/** Module ids in composition order. */
|
|
81
|
+
readonly ids: readonly string[];
|
|
82
|
+
/** Every declared contract, flattened in module order and de-duplicated. */
|
|
83
|
+
readonly contracts: readonly PluginRpcContract[];
|
|
84
|
+
/** Run all `contributeServer` hooks; returns the combined disposer. */
|
|
85
|
+
contributeServer(server: TServerContext): FeatureDisposer;
|
|
86
|
+
/** Run all `contributeClient` hooks; returns the combined disposer. */
|
|
87
|
+
contributeClient(client: TClientContext): FeatureDisposer;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toIdempotentDisposer(disposer: FeatureDisposer): FeatureDisposer {
|
|
91
|
+
let called = false;
|
|
92
|
+
return () => {
|
|
93
|
+
if (called) return;
|
|
94
|
+
called = true;
|
|
95
|
+
disposer();
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Run a set of disposers in reverse (LIFO) order. Every disposer runs even if
|
|
101
|
+
* an earlier one throws; the first error is re-thrown after teardown finishes.
|
|
102
|
+
*/
|
|
103
|
+
function disposeAll(disposers: readonly FeatureDisposer[]): void {
|
|
104
|
+
let hasError = false;
|
|
105
|
+
let firstError: unknown;
|
|
106
|
+
for (let i = disposers.length - 1; i >= 0; i -= 1) {
|
|
107
|
+
try {
|
|
108
|
+
disposers[i]!();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (!hasError) {
|
|
111
|
+
hasError = true;
|
|
112
|
+
firstError = error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (hasError) throw firstError;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Collect the disposers produced by a per-side contribution hook. If a module
|
|
121
|
+
* throws mid-composition, already-contributed modules are disposed before the
|
|
122
|
+
* error is re-thrown so a failed composition never leaks partial state.
|
|
123
|
+
*/
|
|
124
|
+
function runContributions<TServerContext, TClientContext>(
|
|
125
|
+
modules: readonly FeatureModule<TServerContext, TClientContext>[],
|
|
126
|
+
contribute: (
|
|
127
|
+
module: FeatureModule<TServerContext, TClientContext>,
|
|
128
|
+
) => FeatureDisposer | void,
|
|
129
|
+
): FeatureDisposer {
|
|
130
|
+
const disposers: FeatureDisposer[] = [];
|
|
131
|
+
let disposed = false;
|
|
132
|
+
const dispose = () => {
|
|
133
|
+
if (disposed) return;
|
|
134
|
+
disposed = true;
|
|
135
|
+
disposeAll(disposers);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
for (const module of modules) {
|
|
140
|
+
const result = contribute(module);
|
|
141
|
+
if (typeof result === "function") {
|
|
142
|
+
disposers.push(toIdempotentDisposer(result));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Roll back what already mounted, but never let a failing disposer mask
|
|
147
|
+
// the contribution error that triggered the rollback.
|
|
148
|
+
try {
|
|
149
|
+
dispose();
|
|
150
|
+
} catch {
|
|
151
|
+
// ignored
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return dispose;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function assertUniqueModules<TServerContext, TClientContext>(
|
|
160
|
+
modules: readonly FeatureModule<TServerContext, TClientContext>[],
|
|
161
|
+
): void {
|
|
162
|
+
const seen = new Set<string>();
|
|
163
|
+
for (const module of modules) {
|
|
164
|
+
const id = typeof module.id === "string" ? module.id.trim() : "";
|
|
165
|
+
if (!id) {
|
|
166
|
+
throw new Error("Feature module id must be a non-empty string");
|
|
167
|
+
}
|
|
168
|
+
if (seen.has(id)) {
|
|
169
|
+
throw new Error(`Duplicate feature module id "${id}"`);
|
|
170
|
+
}
|
|
171
|
+
seen.add(id);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function collectContracts<TServerContext, TClientContext>(
|
|
176
|
+
modules: readonly FeatureModule<TServerContext, TClientContext>[],
|
|
177
|
+
): readonly PluginRpcContract[] {
|
|
178
|
+
const contracts: PluginRpcContract[] = [];
|
|
179
|
+
const seen = new Set<string>();
|
|
180
|
+
for (const module of modules) {
|
|
181
|
+
for (const contract of Object.values(module.contracts ?? {})) {
|
|
182
|
+
if (seen.has(contract.name)) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Duplicate RPC contract "${contract.name}" across feature modules`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
seen.add(contract.name);
|
|
188
|
+
contracts.push(contract);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return Object.freeze(contracts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Compose {@link FeatureModule}s into one contribution unit for a plugin entry.
|
|
196
|
+
*
|
|
197
|
+
* Modules run in array order on contribution and are disposed in reverse (LIFO)
|
|
198
|
+
* order. The returned combined disposer is idempotent: calling it twice runs
|
|
199
|
+
* teardown once and never throws the first error a second time.
|
|
200
|
+
*
|
|
201
|
+
* ```ts
|
|
202
|
+
* import type { PluginClientContext } from "@getpaseo/plugin/client";
|
|
203
|
+
* import type { PluginServerContext } from "@getpaseo/plugin/server";
|
|
204
|
+
* import { composeFeatureModules } from "paseo-plugin-helper/shared";
|
|
205
|
+
* import { customPills } from "./features/custom-pills";
|
|
206
|
+
* import { timelineTelemetry } from "./features/timeline-telemetry";
|
|
207
|
+
*
|
|
208
|
+
* const features = composeFeatureModules<PluginServerContext, PluginClientContext>([
|
|
209
|
+
* customPills,
|
|
210
|
+
* timelineTelemetry,
|
|
211
|
+
* ]);
|
|
212
|
+
*
|
|
213
|
+
* export function contributeServer(server: PluginServerContext) {
|
|
214
|
+
* return features.contributeServer(server);
|
|
215
|
+
* }
|
|
216
|
+
*
|
|
217
|
+
* export function contributeClient(client: PluginClientContext) {
|
|
218
|
+
* return features.contributeClient(client);
|
|
219
|
+
* }
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @throws if two modules share an `id` or declare the same RPC contract `name`.
|
|
223
|
+
*/
|
|
224
|
+
export function composeFeatureModules<
|
|
225
|
+
TServerContext = unknown,
|
|
226
|
+
TClientContext = unknown,
|
|
227
|
+
>(
|
|
228
|
+
modules: readonly FeatureModule<TServerContext, TClientContext>[],
|
|
229
|
+
): FeatureComposition<TServerContext, TClientContext> {
|
|
230
|
+
const list = [...modules];
|
|
231
|
+
assertUniqueModules(list);
|
|
232
|
+
const contracts = collectContracts(list);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
ids: Object.freeze(list.map((module) => module.id)),
|
|
236
|
+
contracts,
|
|
237
|
+
contributeServer(server) {
|
|
238
|
+
return runContributions(list, (module) => module.contributeServer?.(server));
|
|
239
|
+
},
|
|
240
|
+
contributeClient(client) {
|
|
241
|
+
return runContributions(list, (module) => module.contributeClient?.(client));
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -108,3 +108,30 @@ export function resolveForgeMark(
|
|
|
108
108
|
const resolved = isForgeKind(explicit) ? explicit : forgeKindFromHost(host);
|
|
109
109
|
return FORGE_MARKS[resolved];
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Official monochrome forge marks (Simple Icons, CC0 / project trademarks),
|
|
114
|
+
* authored on a 24x24 viewBox. Rendered as inline SVG through a data URI so a
|
|
115
|
+
* consumer needs no SVG dependency: exact vector marks on web/Electron, where
|
|
116
|
+
* Paseo renders React Native Web, and a Lucide fallback on native.
|
|
117
|
+
*
|
|
118
|
+
* Pure and React-free so it lives in `shared/` next to the resolver.
|
|
119
|
+
*/
|
|
120
|
+
const FORGE_MARK_PATHS: Partial<Record<ForgeKind, string>> = {
|
|
121
|
+
codeberg:
|
|
122
|
+
"M11.999.747A11.974 11.974 0 0 0 0 12.75c0 2.254.635 4.465 1.833 6.376L11.837 6.19c.072-.092.251-.092.323 0l4.178 5.402h-2.992l.065.239h3.113l.882 1.138h-3.674l.103.374h3.86l.777 1.003h-4.358l.135.483h4.593l.695.894h-5.038l.165.589h5.326l.609.785h-5.717l.182.65h6.038l.562.727h-6.397l.183.65h6.717A12.003 12.003 0 0 0 24 12.75 11.977 11.977 0 0 0 11.999.747zm3.654 19.104.182.65h5.326c.173-.204.353-.433.513-.65zm.385 1.377.18.65h3.563c.233-.198.485-.428.712-.65zm.383 1.377.182.648h1.203c.356-.204.685-.412 1.042-.648zz",
|
|
123
|
+
forgejo:
|
|
124
|
+
"M16.7773 0c1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.1175a7.0759 7.0759 0 0 1 4.148-1.4205l.1176-.001 1.3385.0002c.4973-.8827 1.4434-1.4788 2.5288-1.4788 1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.319c.8827.4973 1.4788 1.4434 1.4788 2.5287 0 1.602-1.2986 2.9005-2.9005 2.9005-1.6018 0-2.9004-1.2986-2.9004-2.9005 0-1.0853.596-2.0314 1.4788-2.5287l-.0002-9.9831c0-3.887 3.1195-7.0453 6.9915-7.108l.1176-.001h1.3385C14.7458.5962 15.692 0 16.7773 0ZM7.2227 19.9052c-.6596 0-1.1943.5347-1.1943 1.1943s.5347 1.1943 1.1943 1.1943 1.1944-.5347 1.1944-1.1943-.5348-1.1943-1.1944-1.1943Zm9.5546-10.4644c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Zm0-7.7346c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Z",
|
|
125
|
+
gitea:
|
|
126
|
+
"M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Inline SVG data URI for a custom mark, tinted with the resolved color. */
|
|
130
|
+
export function forgeMarkSource(kind: ForgeKind, color: string): { uri: string } | null {
|
|
131
|
+
const path = FORGE_MARK_PATHS[kind];
|
|
132
|
+
if (!path) return null;
|
|
133
|
+
const svg =
|
|
134
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">` +
|
|
135
|
+
`<path fill="${color}" d="${path}"/></svg>`;
|
|
136
|
+
return { uri: `data:image/svg+xml,${encodeURIComponent(svg)}` };
|
|
137
|
+
}
|
|
@@ -5,64 +5,139 @@ import { defineContract } from "./rpc";
|
|
|
5
5
|
// Label taxonomy & definitions (issues #122, #189, #200)
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
|
|
8
|
+
// Canonical taxonomy is numberless (platform#247). Readers accept the legacy
|
|
9
|
+
// numeric spellings too (dual-read) while the board migrates; `LEGACY_*` arrays
|
|
10
|
+
// and `LABEL_ALIASES` keep the two spellings interchangeable.
|
|
8
11
|
export const STATE_ORDER = [
|
|
12
|
+
"state/triage",
|
|
13
|
+
"state/wip",
|
|
14
|
+
"state/review",
|
|
15
|
+
"state/verify",
|
|
16
|
+
"state/done",
|
|
17
|
+
] as const;
|
|
18
|
+
export type StateLabel = (typeof STATE_ORDER)[number];
|
|
19
|
+
|
|
20
|
+
export const LEGACY_STATE_ORDER = [
|
|
9
21
|
"state/0-triage",
|
|
10
22
|
"state/1-wip",
|
|
11
23
|
"state/2-review",
|
|
12
24
|
"state/3-verify",
|
|
13
25
|
"state/4-done",
|
|
14
26
|
] as const;
|
|
15
|
-
export type StateLabel = (typeof STATE_ORDER)[number];
|
|
16
27
|
|
|
17
28
|
export const PRIORITY_ORDER = [
|
|
29
|
+
"priority/sos",
|
|
30
|
+
"priority/high",
|
|
31
|
+
"priority/normal",
|
|
32
|
+
"priority/low",
|
|
33
|
+
"priority/backburner",
|
|
34
|
+
] as const;
|
|
35
|
+
export type PriorityLabel = (typeof PRIORITY_ORDER)[number];
|
|
36
|
+
|
|
37
|
+
export const LEGACY_PRIORITY_ORDER = [
|
|
18
38
|
"priority/0-SOS",
|
|
19
39
|
"priority/1-high",
|
|
20
40
|
"priority/2-normal",
|
|
21
41
|
"priority/3-low",
|
|
22
42
|
"priority/4-backburner",
|
|
23
43
|
] as const;
|
|
24
|
-
export type PriorityLabel = (typeof PRIORITY_ORDER)[number];
|
|
25
44
|
|
|
26
45
|
export const ATTENTION_LABELS = [
|
|
46
|
+
"attention/orchestrator",
|
|
47
|
+
"attention/agent",
|
|
48
|
+
"attention/user",
|
|
49
|
+
"attention/ignore",
|
|
50
|
+
] as const;
|
|
51
|
+
export type AttentionLabel = (typeof ATTENTION_LABELS)[number];
|
|
52
|
+
|
|
53
|
+
export const LEGACY_ATTENTION_LABELS = [
|
|
27
54
|
"attention/0-orchestrator",
|
|
28
55
|
"attention/1-agent",
|
|
29
56
|
"attention/2-user",
|
|
30
57
|
"attention/3-ignore",
|
|
31
58
|
] as const;
|
|
32
|
-
export type AttentionLabel = (typeof ATTENTION_LABELS)[number];
|
|
33
59
|
|
|
34
60
|
export const SPEC_LABELS = [
|
|
61
|
+
"spec/needed",
|
|
62
|
+
"spec/checklist",
|
|
63
|
+
"spec/approved",
|
|
64
|
+
] as const;
|
|
65
|
+
export type SpecLabel = (typeof SPEC_LABELS)[number];
|
|
66
|
+
|
|
67
|
+
export const LEGACY_SPEC_LABELS = [
|
|
35
68
|
"spec/0-needed",
|
|
36
69
|
"spec/1-checklist",
|
|
37
70
|
"spec/2-approved",
|
|
38
71
|
] as const;
|
|
39
|
-
export type SpecLabel = (typeof SPEC_LABELS)[number];
|
|
40
72
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
function aliasPairs(
|
|
74
|
+
canonical: readonly string[],
|
|
75
|
+
legacy: readonly string[],
|
|
76
|
+
): Record<string, string> {
|
|
77
|
+
const map: Record<string, string> = {};
|
|
78
|
+
canonical.forEach((name, i) => {
|
|
79
|
+
const legacyName = legacy[i];
|
|
80
|
+
if (legacyName) map[legacyName] = name;
|
|
81
|
+
});
|
|
82
|
+
return map;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Legacy numeric label -> canonical numberless label (all scopes). */
|
|
86
|
+
export const LABEL_ALIASES: Record<string, string> = {
|
|
87
|
+
...aliasPairs(STATE_ORDER, LEGACY_STATE_ORDER),
|
|
88
|
+
...aliasPairs(PRIORITY_ORDER, LEGACY_PRIORITY_ORDER),
|
|
89
|
+
...aliasPairs(ATTENTION_LABELS, LEGACY_ATTENTION_LABELS),
|
|
90
|
+
...aliasPairs(SPEC_LABELS, LEGACY_SPEC_LABELS),
|
|
91
|
+
"review/0-needed": "review/needed",
|
|
92
|
+
"review/1-changes-requested": "review/changes-requested",
|
|
93
|
+
"review/2-approved": "review/approved",
|
|
94
|
+
"format/0-needed": "format/needed",
|
|
95
|
+
"format/1-ok": "format/ok",
|
|
96
|
+
"size/0-cheap": "size/cheap",
|
|
97
|
+
"size/1-medium": "size/medium",
|
|
98
|
+
"size/2-expensive": "size/expensive",
|
|
99
|
+
"size/3-chunk": "size/chunk",
|
|
100
|
+
"linked/0-needs-split": "linked/needs-split",
|
|
101
|
+
"linked/1-peer": "linked/peer",
|
|
102
|
+
"linked/2-done": "linked/done",
|
|
103
|
+
"upstream/0-explore": "upstream/explore",
|
|
104
|
+
"upstream/1-blocked": "upstream/blocked",
|
|
105
|
+
"upstream/2-aligned": "upstream/aligned",
|
|
47
106
|
};
|
|
48
107
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
108
|
+
/** Canonical numberless spelling of a label; unknown labels pass through. */
|
|
109
|
+
export function canonicalLabel(label: string): string {
|
|
110
|
+
return LABEL_ALIASES[label] ?? label;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** True when two spellings name the same scoped label under the alias map. */
|
|
114
|
+
export function sameLabel(a: string, b: string): boolean {
|
|
115
|
+
return canonicalLabel(a) === canonicalLabel(b);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const CANONICAL_SHORT: Record<string, string> = {
|
|
119
|
+
"state/triage": "Triage",
|
|
120
|
+
"state/wip": "WIP",
|
|
121
|
+
"state/review": "Review",
|
|
122
|
+
"state/verify": "Verify",
|
|
123
|
+
"state/done": "Done",
|
|
124
|
+
"priority/sos": "SOS",
|
|
125
|
+
"priority/high": "High",
|
|
126
|
+
"priority/normal": "Normal",
|
|
127
|
+
"priority/low": "Low",
|
|
128
|
+
"priority/backburner": "Parked",
|
|
55
129
|
};
|
|
56
130
|
|
|
57
131
|
/** Compact display alias for a scoped label ("state/1-wip" -> "WIP"). */
|
|
58
132
|
export function shortLabelName(label: string): string {
|
|
59
|
-
return
|
|
133
|
+
return CANONICAL_SHORT[canonicalLabel(label)] ?? label;
|
|
60
134
|
}
|
|
61
135
|
|
|
62
|
-
/** The issue's current `state/*` label, or null when it carries none. */
|
|
136
|
+
/** The issue's current `state/*` label (canonical), or null when it carries none. */
|
|
63
137
|
export function currentStateLabel(labels: string[]): string | null {
|
|
64
138
|
for (const label of labels) {
|
|
65
|
-
|
|
139
|
+
const canonical = canonicalLabel(label);
|
|
140
|
+
if ((STATE_ORDER as readonly string[]).includes(canonical)) return canonical;
|
|
66
141
|
}
|
|
67
142
|
return null;
|
|
68
143
|
}
|
|
@@ -70,15 +145,16 @@ export function currentStateLabel(labels: string[]): string | null {
|
|
|
70
145
|
/** The issue's current `priority/*` label, defaulting to normal per spec §4.2. */
|
|
71
146
|
export function currentPriorityLabel(labels: string[]): string {
|
|
72
147
|
for (const label of labels) {
|
|
73
|
-
|
|
148
|
+
const canonical = canonicalLabel(label);
|
|
149
|
+
if ((PRIORITY_ORDER as readonly string[]).includes(canonical)) return canonical;
|
|
74
150
|
}
|
|
75
|
-
return "priority/
|
|
151
|
+
return "priority/normal";
|
|
76
152
|
}
|
|
77
153
|
|
|
78
154
|
/** Next `state/*` promotion step, or null when already done. */
|
|
79
155
|
export function nextStateLabel(labels: string[]): string | null {
|
|
80
156
|
const current = currentStateLabel(labels);
|
|
81
|
-
if (!current) return "state/
|
|
157
|
+
if (!current) return "state/wip";
|
|
82
158
|
const idx = (STATE_ORDER as readonly string[]).indexOf(current);
|
|
83
159
|
if (idx < 0 || idx + 1 >= STATE_ORDER.length) return null;
|
|
84
160
|
return STATE_ORDER[idx + 1];
|
|
@@ -660,12 +736,6 @@ export const forgeContextContract = defineContract({
|
|
|
660
736
|
output: ForgeContextOutputSchema,
|
|
661
737
|
});
|
|
662
738
|
|
|
663
|
-
export const forgeForgeContextContract = defineContract({
|
|
664
|
-
name: "forge.forge-context",
|
|
665
|
-
description: "Git-origin forge coordinates for a workspace, independent of issue queries",
|
|
666
|
-
input: ForgeContextInputSchema,
|
|
667
|
-
output: ForgeContextOutputSchema,
|
|
668
|
-
});
|
|
669
739
|
|
|
670
740
|
// ---------------------------------------------------------------------------
|
|
671
741
|
// Agent envelope parsing
|
|
@@ -953,24 +1023,48 @@ const LABEL_DEFS: LabelDefinition[] = [
|
|
|
953
1023
|
exclusive: true,
|
|
954
1024
|
description: `Workflow state ${i}`,
|
|
955
1025
|
})),
|
|
1026
|
+
...LEGACY_STATE_ORDER.map((name, i): LabelDefinition => ({
|
|
1027
|
+
name,
|
|
1028
|
+
color: STATE_COLORS[i] ?? "#59636e",
|
|
1029
|
+
exclusive: true,
|
|
1030
|
+
description: `Workflow state ${i} (legacy numeric)`,
|
|
1031
|
+
})),
|
|
956
1032
|
...PRIORITY_ORDER.map((name, i): LabelDefinition => ({
|
|
957
1033
|
name,
|
|
958
1034
|
color: PRIORITY_COLORS[i] ?? "#59636e",
|
|
959
1035
|
exclusive: true,
|
|
960
1036
|
description: `Priority ${i}`,
|
|
961
1037
|
})),
|
|
1038
|
+
...LEGACY_PRIORITY_ORDER.map((name, i): LabelDefinition => ({
|
|
1039
|
+
name,
|
|
1040
|
+
color: PRIORITY_COLORS[i] ?? "#59636e",
|
|
1041
|
+
exclusive: true,
|
|
1042
|
+
description: `Priority ${i} (legacy numeric)`,
|
|
1043
|
+
})),
|
|
962
1044
|
...ATTENTION_LABELS.map((name): LabelDefinition => ({
|
|
963
1045
|
name,
|
|
964
1046
|
color: "#8250df",
|
|
965
1047
|
exclusive: true,
|
|
966
1048
|
description: "Who acts next",
|
|
967
1049
|
})),
|
|
1050
|
+
...LEGACY_ATTENTION_LABELS.map((name): LabelDefinition => ({
|
|
1051
|
+
name,
|
|
1052
|
+
color: "#8250df",
|
|
1053
|
+
exclusive: true,
|
|
1054
|
+
description: "Who acts next (legacy numeric)",
|
|
1055
|
+
})),
|
|
968
1056
|
...SPEC_LABELS.map((name): LabelDefinition => ({
|
|
969
1057
|
name,
|
|
970
1058
|
color: "#0e7c6b",
|
|
971
1059
|
exclusive: true,
|
|
972
1060
|
description: "Spec readiness",
|
|
973
1061
|
})),
|
|
1062
|
+
...LEGACY_SPEC_LABELS.map((name): LabelDefinition => ({
|
|
1063
|
+
name,
|
|
1064
|
+
color: "#0e7c6b",
|
|
1065
|
+
exclusive: true,
|
|
1066
|
+
description: "Spec readiness (legacy numeric)",
|
|
1067
|
+
})),
|
|
974
1068
|
];
|
|
975
1069
|
|
|
976
1070
|
export function paseoLabelSet(): LabelDefinition[] {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isToolCallEntry, type AuditRecord, type PermissionDecision } from "./shared.js";
|
|
2
|
+
|
|
3
|
+
export type DecisionFilter = "all" | PermissionDecision;
|
|
4
|
+
export type AuditTypeFilter = "all" | "permission" | "tool_call";
|
|
5
|
+
|
|
6
|
+
export function formatAuditTime(iso: string): string {
|
|
7
|
+
const ms = Date.parse(iso);
|
|
8
|
+
if (Number.isNaN(ms)) return iso;
|
|
9
|
+
return new Date(ms).toLocaleString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Middle-ellipsis for long values, keeping the leading key and the trailing
|
|
14
|
+
* identifier. `maxLength` is the content budget; the returned string can be one
|
|
15
|
+
* character longer once the ellipsis is inserted.
|
|
16
|
+
*/
|
|
17
|
+
function truncateMiddle(text: string, maxLength = 120): string {
|
|
18
|
+
if (text.length <= maxLength) return text;
|
|
19
|
+
const head = Math.ceil(maxLength / 2);
|
|
20
|
+
const tail = Math.floor(maxLength / 2);
|
|
21
|
+
return `${text.slice(0, head)}…${text.slice(text.length - tail)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function summarizeAuditInput(input: unknown): string {
|
|
25
|
+
if (input === null || input === undefined) return "—";
|
|
26
|
+
if (typeof input === "string") return truncateMiddle(input);
|
|
27
|
+
try {
|
|
28
|
+
return truncateMiddle(JSON.stringify(input));
|
|
29
|
+
} catch {
|
|
30
|
+
return "—";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function filterAuditEntries(
|
|
35
|
+
records: AuditRecord[],
|
|
36
|
+
decision: DecisionFilter,
|
|
37
|
+
search: string,
|
|
38
|
+
type: AuditTypeFilter = "all",
|
|
39
|
+
): AuditRecord[] {
|
|
40
|
+
const needle = search.trim().toLowerCase();
|
|
41
|
+
return records.filter((record) => {
|
|
42
|
+
const toolCall = isToolCallEntry(record);
|
|
43
|
+
if (type === "permission" && toolCall) return false;
|
|
44
|
+
if (type === "tool_call" && !toolCall) return false;
|
|
45
|
+
// Decisions only exist on permission records; a decision filter excludes tool calls.
|
|
46
|
+
if (decision !== "all" && (toolCall || record.decision !== decision)) return false;
|
|
47
|
+
if (!needle) return true;
|
|
48
|
+
const haystack = [
|
|
49
|
+
record.name,
|
|
50
|
+
record.kind,
|
|
51
|
+
record.agentId,
|
|
52
|
+
record.agentModel ?? "",
|
|
53
|
+
summarizeAuditInput(record.input),
|
|
54
|
+
toolCall ? record.outcome : record.decision,
|
|
55
|
+
toolCall ? record.turnId ?? "" : "",
|
|
56
|
+
]
|
|
57
|
+
.join(" ")
|
|
58
|
+
.toLowerCase();
|
|
59
|
+
return haystack.includes(needle);
|
|
60
|
+
});
|
|
61
|
+
}
|