@agimon-ai/po-ui 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/apiContracts.cjs +17 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +53 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +13 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/config.cjs +62 -0
  10. package/dist/config.d.mts +2 -0
  11. package/dist/config.mjs +2 -0
  12. package/dist/extensionName.cjs +59 -0
  13. package/dist/extensionName.cjs.map +1 -0
  14. package/dist/extensionName.d.mts +14 -0
  15. package/dist/extensionName.d.mts.map +1 -0
  16. package/dist/extensionName.mjs +54 -0
  17. package/dist/extensionName.mjs.map +1 -0
  18. package/dist/extensions/mcp-ui.mjs +46 -0
  19. package/dist/extensions/mcp-ui.mjs.map +1 -0
  20. package/dist/extensions/mcp.cjs +79 -0
  21. package/dist/extensions/mcp.cjs.map +1 -0
  22. package/dist/extensions/mcp.d.mts +5 -0
  23. package/dist/extensions/mcp.d.mts.map +1 -0
  24. package/dist/extensions/mcp.mjs +74 -0
  25. package/dist/extensions/mcp.mjs.map +1 -0
  26. package/dist/extensions/server.cjs +137 -0
  27. package/dist/extensions/server.cjs.map +1 -0
  28. package/dist/extensions/server.d.mts +5 -0
  29. package/dist/extensions/server.d.mts.map +1 -0
  30. package/dist/extensions/server.mjs +132 -0
  31. package/dist/extensions/server.mjs.map +1 -0
  32. package/dist/extensions/web.mjs +332 -0
  33. package/dist/extensions/web.mjs.map +1 -0
  34. package/dist/footer.cjs +26 -0
  35. package/dist/footer.d.mts +2 -0
  36. package/dist/footer.mjs +2 -0
  37. package/dist/harnessMetadata-CzsdGAxL.d.mts +21 -0
  38. package/dist/harnessMetadata-CzsdGAxL.d.mts.map +1 -0
  39. package/dist/harnessMetadata-DkMSJutN.mjs +25 -0
  40. package/dist/harnessMetadata-DkMSJutN.mjs.map +1 -0
  41. package/dist/harnessMetadata-Dw9Rb1nW.cjs +30 -0
  42. package/dist/harnessMetadata-Dw9Rb1nW.cjs.map +1 -0
  43. package/dist/harnessMetadata.cjs +3 -0
  44. package/dist/harnessMetadata.d.mts +2 -0
  45. package/dist/harnessMetadata.mjs +2 -0
  46. package/dist/index-Bx49wmE7.d.mts +30 -0
  47. package/dist/index-Bx49wmE7.d.mts.map +1 -0
  48. package/dist/index-Dpzvl-vg.d.mts +103 -0
  49. package/dist/index-Dpzvl-vg.d.mts.map +1 -0
  50. package/dist/index.cjs +12 -0
  51. package/dist/index.d.mts +8 -0
  52. package/dist/index.mjs +5 -0
  53. package/dist/leader.cjs +0 -0
  54. package/dist/leader.d.mts +2 -0
  55. package/dist/leader.mjs +1 -0
  56. package/dist/leaderRegistry.cjs +515 -0
  57. package/dist/leaderRegistry.cjs.map +1 -0
  58. package/dist/leaderRegistry.d.mts +59 -0
  59. package/dist/leaderRegistry.d.mts.map +1 -0
  60. package/dist/leaderRegistry.mjs +514 -0
  61. package/dist/leaderRegistry.mjs.map +1 -0
  62. package/dist/logSinkTelemetry.cjs +4 -0
  63. package/dist/logSinkTelemetry.d.mts +2 -0
  64. package/dist/logSinkTelemetry.mjs +2 -0
  65. package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
  66. package/dist/skills.cjs +38 -0
  67. package/dist/skills.d.mts +2 -0
  68. package/dist/skills.mjs +2 -0
  69. package/dist/telemetry--EgKB9ei.mjs +36 -0
  70. package/dist/telemetry--EgKB9ei.mjs.map +1 -0
  71. package/dist/telemetry-DMR6cIT1.cjs +47 -0
  72. package/dist/telemetry-DMR6cIT1.cjs.map +1 -0
  73. package/dist/theme-2wveit5V.cjs +166 -0
  74. package/dist/theme-2wveit5V.cjs.map +1 -0
  75. package/dist/theme-Byj_dBlT.mjs +134 -0
  76. package/dist/theme-Byj_dBlT.mjs.map +1 -0
  77. package/dist/theme.cjs +7 -0
  78. package/dist/theme.d.mts +2 -0
  79. package/dist/theme.mjs +2 -0
  80. package/dist/toolInventory.cjs +20 -0
  81. package/dist/toolInventory.d.mts +2 -0
  82. package/dist/toolInventory.mjs +2 -0
  83. package/dist/uiState-Binu4nUb.d.mts +40 -0
  84. package/dist/uiState-Binu4nUb.d.mts.map +1 -0
  85. package/dist/uiState-G_Pso-5i.mjs +57 -0
  86. package/dist/uiState-G_Pso-5i.mjs.map +1 -0
  87. package/dist/uiState-rUwgKslb.cjs +68 -0
  88. package/dist/uiState-rUwgKslb.cjs.map +1 -0
  89. package/dist/uiState.cjs +4 -0
  90. package/dist/uiState.d.mts +2 -0
  91. package/dist/uiState.mjs +2 -0
  92. package/package.json +196 -0
  93. package/themes/po-dark.json +88 -0
@@ -0,0 +1,103 @@
1
+ import { FooterTextColor } from "@agimon-ai/po-core/footer";
2
+ //#region src/services/theme/index.d.ts
3
+ declare const DEFAULT_THEME_NAME = "po-dark";
4
+ /** Stable semantic color shared by every UI surface that identifies an agent run. */
5
+ declare function agentIdentityColor(identity: string): FooterTextColor;
6
+ /**
7
+ * Prefer the identity's stable color, then walk the palette to avoid reusing a
8
+ * color already visible on the same surface. Once every color is occupied,
9
+ * reuse the stable preference rather than changing existing assignments.
10
+ */
11
+ declare function allocateAgentIdentityColor(identity: string, occupied: ReadonlySet<FooterTextColor>): FooterTextColor;
12
+ declare const DEFAULT_THEME: {
13
+ readonly $schema: 'https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json';
14
+ readonly name: "po-dark";
15
+ readonly vars: {
16
+ readonly bg: '#1c1c1c';
17
+ readonly bgAlt: '#171717';
18
+ readonly bgActive: '#2b2b2b';
19
+ readonly bgHover: '#2b2b2b';
20
+ readonly fg: '#dedede';
21
+ readonly fgAlt: '#b0b0b0';
22
+ readonly grey: '#343434';
23
+ readonly greyAlt: '#555555';
24
+ readonly blue: '#8cb5cc';
25
+ readonly darkBlue: '#284b64';
26
+ readonly cyan: '#8fc8c7';
27
+ readonly green: '#81cca3';
28
+ readonly lightGreen: '#a5d9b5';
29
+ readonly teal: '#8ec1ab';
30
+ readonly orange: '#d3ad83';
31
+ readonly red: '#e99185';
32
+ readonly violet: '#b0a0ce';
33
+ readonly magenta: '#c19aae';
34
+ readonly yellow: '#d6be82';
35
+ readonly lightYellow: '#e1cf9e';
36
+ readonly comment: '#a4a4a4';
37
+ readonly diffAddedBg: '#152d20';
38
+ readonly diffRemovedBg: '#35201b';
39
+ };
40
+ readonly colors: {
41
+ readonly accent: 'green';
42
+ readonly border: 'greyAlt';
43
+ readonly borderAccent: 'green';
44
+ readonly borderMuted: 'grey';
45
+ readonly success: 'green';
46
+ readonly error: 'red';
47
+ readonly warning: 'yellow';
48
+ readonly muted: 'fgAlt';
49
+ readonly dim: 'comment';
50
+ readonly text: 'fg';
51
+ readonly thinkingText: 'fgAlt';
52
+ readonly selectedBg: 'bgHover';
53
+ readonly userMessageBg: 'bgActive';
54
+ readonly userMessageText: 'fg';
55
+ readonly customMessageBg: '#292c2a';
56
+ readonly customMessageText: 'fg';
57
+ readonly customMessageLabel: 'green';
58
+ readonly toolPendingBg: 'bgAlt';
59
+ readonly toolSuccessBg: 'diffAddedBg';
60
+ readonly toolErrorBg: 'diffRemovedBg';
61
+ readonly toolTitle: 'green';
62
+ readonly toolOutput: 'fg';
63
+ readonly mdHeading: 'magenta';
64
+ readonly mdLink: 'blue';
65
+ readonly mdLinkUrl: 'comment';
66
+ readonly mdCode: 'cyan';
67
+ readonly mdCodeBlock: 'lightYellow';
68
+ readonly mdCodeBlockBorder: 'greyAlt';
69
+ readonly mdQuote: 'fgAlt';
70
+ readonly mdQuoteBorder: 'greyAlt';
71
+ readonly mdHr: 'greyAlt';
72
+ readonly mdListBullet: 'green';
73
+ readonly toolDiffAdded: 'lightGreen';
74
+ readonly toolDiffRemoved: 'red';
75
+ readonly toolDiffContext: 'fgAlt';
76
+ readonly syntaxComment: 'comment';
77
+ readonly syntaxKeyword: 'blue';
78
+ readonly syntaxFunction: 'magenta';
79
+ readonly syntaxVariable: 'violet';
80
+ readonly syntaxString: 'green';
81
+ readonly syntaxNumber: 'orange';
82
+ readonly syntaxType: 'yellow';
83
+ readonly syntaxOperator: 'orange';
84
+ readonly syntaxPunctuation: 'fgAlt';
85
+ readonly thinkingOff: 'grey';
86
+ readonly thinkingMinimal: 'fgAlt';
87
+ readonly thinkingLow: 'darkBlue';
88
+ readonly thinkingMedium: 'blue';
89
+ readonly thinkingHigh: 'teal';
90
+ readonly thinkingXhigh: 'magenta';
91
+ readonly thinkingMax: 'red';
92
+ readonly bashMode: 'lightGreen';
93
+ };
94
+ readonly export: {
95
+ readonly pageBg: 'bg';
96
+ readonly cardBg: 'bgAlt';
97
+ readonly infoBg: '#3b3526';
98
+ };
99
+ };
100
+ declare function writeDefaultTheme(temporaryDirectory: string): Promise<string>;
101
+ //#endregion
102
+ export { writeDefaultTheme as a, allocateAgentIdentityColor as i, DEFAULT_THEME_NAME as n, agentIdentityColor as r, DEFAULT_THEME as t };
103
+ //# sourceMappingURL=index-Dpzvl-vg.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-Dpzvl-vg.d.mts","names":[],"sources":["../src/services/theme/index.ts"],"mappings":";;cAKa;;iBAqBG,mBAAmB,mBAAmB;;;;;;iBAStC,2BAA2B,kBAAkB,UAAU,YAAY,mBAAmB;cASzF;WACX;WAEA;WAGA;aACE;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aAEA;aACA;;WAEF;aACE;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aAEA;aACA;aACA;aACA;aACA;aACA;aACA;aAGA;aACA;aACA;aACA;aAEA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aAEA;aACA;aACA;aAEA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aACA;aAGA;aACA;aACA;aACA;aACA;aACA;aACA;aAEA;;WAEF;aACE;aACA;aACA;;;iBAIkB,kBAAkB,6BAA6B"}
package/dist/index.cjs ADDED
@@ -0,0 +1,12 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_harnessMetadata = require("./harnessMetadata-Dw9Rb1nW.cjs");
3
+ const require_telemetry = require("./telemetry-DMR6cIT1.cjs");
4
+ const require_uiState = require("./uiState-rUwgKslb.cjs");
5
+ const require_theme = require("./theme-2wveit5V.cjs");
6
+ exports.DEFAULT_THEME = require_theme.DEFAULT_THEME;
7
+ exports.DEFAULT_THEME_NAME = require_theme.DEFAULT_THEME_NAME;
8
+ exports.DoomUiState = require_uiState.DoomUiState;
9
+ exports.UI_EVENT = require_telemetry.UI_EVENT;
10
+ exports.createUiTelemetry = require_telemetry.createUiTelemetry;
11
+ exports.readDoomHarnessMetadata = require_harnessMetadata.readDoomHarnessMetadata;
12
+ exports.writeDefaultTheme = require_theme.writeDefaultTheme;
@@ -0,0 +1,8 @@
1
+ import { n as readDoomHarnessMetadata, t as DoomHarnessMetadata } from "./harnessMetadata-CzsdGAxL.mjs";
2
+ import { a as UiTelemetryOptions, i as UiTelemetry, n as UiEventAttributes, o as createUiTelemetry, r as UiEventName, t as UI_EVENT } from "./index-Bx49wmE7.mjs";
3
+ import { n as LeaderOption, r as LeaderSnapshot, t as DoomUiState } from "./uiState-Binu4nUb.mjs";
4
+ import { a as writeDefaultTheme, n as DEFAULT_THEME_NAME, t as DEFAULT_THEME } from "./index-Dpzvl-vg.mjs";
5
+ import { ConfigChoice, ConfigField, ConfigSection, ConfigStep, DoomConfigActionInput, DoomConfigContributionHandle, DoomConfigContributionOptions } from "@agimon-ai/po-core/config";
6
+ import { DoomFooterContributionDefinition, DoomFooterContributionHandle, DoomFooterContributionValue } from "@agimon-ai/po-core/footer";
7
+ import { DoomLeaderContributionHandle, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment } from "@agimon-ai/po-core/leader";
8
+ export { type ConfigChoice, type ConfigField, type ConfigSection, type ConfigStep, DEFAULT_THEME, DEFAULT_THEME_NAME, type DoomConfigActionInput, type DoomConfigContributionHandle, type DoomConfigContributionOptions, type DoomFooterContributionDefinition, type DoomFooterContributionHandle, type DoomFooterContributionValue, type DoomHarnessMetadata, type DoomLeaderBinding, type DoomLeaderCommand, type DoomLeaderContribution, type DoomLeaderContributionHandle, type DoomLeaderSegment, DoomUiState, type LeaderOption, type LeaderSnapshot, UI_EVENT, type UiEventAttributes, type UiEventName, type UiTelemetry, type UiTelemetryOptions, createUiTelemetry, readDoomHarnessMetadata, writeDefaultTheme };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { t as readDoomHarnessMetadata } from "./harnessMetadata-DkMSJutN.mjs";
2
+ import { n as createUiTelemetry, t as UI_EVENT } from "./telemetry--EgKB9ei.mjs";
3
+ import { t as DoomUiState } from "./uiState-G_Pso-5i.mjs";
4
+ import { a as writeDefaultTheme, n as DEFAULT_THEME_NAME, t as DEFAULT_THEME } from "./theme-Byj_dBlT.mjs";
5
+ export { DEFAULT_THEME, DEFAULT_THEME_NAME, DoomUiState, UI_EVENT, createUiTelemetry, readDoomHarnessMetadata, writeDefaultTheme };
File without changes
@@ -0,0 +1,2 @@
1
+ import { DoomLeaderActionHandlerOptions, DoomLeaderContributionHandle, LeaderAction as DoomLeaderExtensionAction, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment } from "@agimon-ai/po-core/leader";
2
+ export type { DoomLeaderActionHandlerOptions, DoomLeaderBinding, DoomLeaderCommand, DoomLeaderContribution, DoomLeaderContributionHandle, DoomLeaderExtensionAction, DoomLeaderSegment };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,515 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/services/leaderRegistry/index.ts
3
+ const CORE_SOURCE = "@agimon-ai/po-ui";
4
+ const UNKNOWN_SOURCE = "unknown";
5
+ const ROOT_LABEL = "leader";
6
+ const DEFAULT_OPTION_ORDER = 500;
7
+ const MAX_OPTION_ORDER = 1e3;
8
+ const MAX_PATH_LENGTH = 4;
9
+ const MAX_SOURCE_LENGTH = 128;
10
+ const MAX_BINDING_ID_LENGTH = 128;
11
+ const MAX_LABEL_LENGTH = 32;
12
+ const MAX_DETAIL_LENGTH = 80;
13
+ const MAX_COMMAND_NAME_LENGTH = 64;
14
+ const MAX_COMMAND_ARGUMENT_LENGTH = 256;
15
+ const MAX_ACTION_NAME_LENGTH = 128;
16
+ const SAFE_KEY = /^[a-z0-9]$/;
17
+ const SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;
18
+ const SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;
19
+ const SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;
20
+ const C0_CONTROL_MAX = 31;
21
+ const DELETE_CHARACTER = 127;
22
+ const MODEL_GROUP = {
23
+ key: "m",
24
+ label: "models",
25
+ detail: "model and thinking level",
26
+ order: 10
27
+ };
28
+ const SESSION_GROUP = {
29
+ key: "s",
30
+ label: "sessions",
31
+ detail: "history, forks and branches",
32
+ order: 30
33
+ };
34
+ const EXTENSION_GROUP = {
35
+ key: "e",
36
+ label: "extension",
37
+ detail: "tools, skills and config",
38
+ order: 50
39
+ };
40
+ const HELP_GROUP = {
41
+ key: "h",
42
+ label: "help",
43
+ detail: "package docs and logs",
44
+ order: 70
45
+ };
46
+ const QUIT_GROUP = {
47
+ key: "q",
48
+ label: "quit",
49
+ detail: "leave the session",
50
+ order: 90
51
+ };
52
+ function coreBinding(id, path, action) {
53
+ return {
54
+ id,
55
+ source: CORE_SOURCE,
56
+ path,
57
+ action
58
+ };
59
+ }
60
+ const CORE_BINDINGS = [
61
+ coreBinding("model.select", [MODEL_GROUP, {
62
+ key: "m",
63
+ label: "select",
64
+ detail: "choose model",
65
+ order: 10
66
+ }], {
67
+ type: "app",
68
+ action: "app.model.select"
69
+ }),
70
+ coreBinding("model.next", [MODEL_GROUP, {
71
+ key: "n",
72
+ label: "next",
73
+ detail: "cycle model",
74
+ order: 20
75
+ }], {
76
+ type: "app",
77
+ action: "app.model.cycleForward"
78
+ }),
79
+ coreBinding("thinking.cycle", [MODEL_GROUP, {
80
+ key: "t",
81
+ label: "thinking",
82
+ detail: "cycle level",
83
+ order: 30
84
+ }], {
85
+ type: "app",
86
+ action: "app.thinking.cycle"
87
+ }),
88
+ coreBinding("tools.open", [EXTENSION_GROUP, {
89
+ key: "t",
90
+ label: "tools",
91
+ detail: "browse tools",
92
+ order: 20
93
+ }], {
94
+ type: "command",
95
+ command: { name: "tools" }
96
+ }),
97
+ coreBinding("config.open", [EXTENSION_GROUP, {
98
+ key: "c",
99
+ label: "config",
100
+ detail: "settings",
101
+ order: 30
102
+ }], {
103
+ type: "command",
104
+ command: { name: "config" }
105
+ }),
106
+ coreBinding("session.new", [SESSION_GROUP, {
107
+ key: "n",
108
+ label: "new",
109
+ detail: "fresh session",
110
+ order: 10
111
+ }], {
112
+ type: "app",
113
+ action: "app.session.new"
114
+ }),
115
+ coreBinding("session.resume", [SESSION_GROUP, {
116
+ key: "r",
117
+ label: "resume",
118
+ detail: "open history",
119
+ order: 20
120
+ }], {
121
+ type: "app",
122
+ action: "app.session.resume"
123
+ }),
124
+ coreBinding("session.tree", [SESSION_GROUP, {
125
+ key: "t",
126
+ label: "tree",
127
+ detail: "branch view",
128
+ order: 30
129
+ }], {
130
+ type: "app",
131
+ action: "app.session.tree"
132
+ }),
133
+ coreBinding("session.fork", [SESSION_GROUP, {
134
+ key: "f",
135
+ label: "fork",
136
+ detail: "from selection",
137
+ order: 40
138
+ }], {
139
+ type: "app",
140
+ action: "app.session.fork"
141
+ }),
142
+ coreBinding("help.hotkeys", [HELP_GROUP, {
143
+ key: "h",
144
+ label: "hotkeys",
145
+ detail: "show bindings"
146
+ }], {
147
+ type: "command",
148
+ command: { name: "hotkeys" }
149
+ }),
150
+ coreBinding("app.exit", [QUIT_GROUP, {
151
+ key: "q",
152
+ label: "quit",
153
+ detail: "exit Pi"
154
+ }], {
155
+ type: "app",
156
+ action: "app.exit"
157
+ })
158
+ ];
159
+ function isRecord(value) {
160
+ return typeof value === "object" && value !== null && !Array.isArray(value);
161
+ }
162
+ function diagnostic(source, message, bindingId) {
163
+ return {
164
+ source,
165
+ message,
166
+ ...bindingId ? { bindingId } : {}
167
+ };
168
+ }
169
+ function containsControlCharacter(value) {
170
+ for (let index = 0; index < value.length; index += 1) {
171
+ const codeUnit = value.charCodeAt(index);
172
+ if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;
173
+ }
174
+ return false;
175
+ }
176
+ function readRequiredText(value, field, maximumLength, source, diagnostics, bindingId) {
177
+ if (typeof value !== "string" || !value.trim()) {
178
+ diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));
179
+ return;
180
+ }
181
+ const text = value.trim();
182
+ if (text.length > maximumLength || containsControlCharacter(text)) {
183
+ diagnostics.push(diagnostic(source, `${field} must be at most ${maximumLength} characters and contain no control characters.`, bindingId));
184
+ return;
185
+ }
186
+ return text;
187
+ }
188
+ function readOptionalText(value, field, maximumLength, source, diagnostics, bindingId) {
189
+ if (value === void 0) return void 0;
190
+ return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);
191
+ }
192
+ function parseSegment(value, source, bindingId, index, diagnostics) {
193
+ if (!isRecord(value)) {
194
+ diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));
195
+ return;
196
+ }
197
+ const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);
198
+ const label = readRequiredText(value.label, `bindings[].path[${index}].label`, MAX_LABEL_LENGTH, source, diagnostics, bindingId);
199
+ const detail = readOptionalText(value.detail, `bindings[].path[${index}].detail`, MAX_DETAIL_LENGTH, source, diagnostics, bindingId);
200
+ const tone = value.tone === "default" || value.tone === "exit" ? value.tone : void 0;
201
+ let order;
202
+ if (value.order !== void 0) {
203
+ if (!Number.isInteger(value.order) || value.order < 0 || value.order > MAX_OPTION_ORDER) diagnostics.push(diagnostic(source, `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`, bindingId));
204
+ else order = value.order;
205
+ }
206
+ if (!key || !label || !SAFE_KEY.test(key)) {
207
+ if (key && !SAFE_KEY.test(key)) diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));
208
+ return;
209
+ }
210
+ return {
211
+ key,
212
+ label,
213
+ ...detail ? { detail } : {},
214
+ ...tone ? { tone } : {},
215
+ ...order === void 0 ? {} : { order }
216
+ };
217
+ }
218
+ function parseCommand(value, source, bindingId, diagnostics) {
219
+ if (!isRecord(value)) {
220
+ diagnostics.push(diagnostic(source, "bindings[].command must be an object.", bindingId));
221
+ return;
222
+ }
223
+ const name = readRequiredText(value.name, "bindings[].command.name", MAX_COMMAND_NAME_LENGTH, source, diagnostics, bindingId);
224
+ const args = readOptionalText(value.args, "bindings[].command.args", MAX_COMMAND_ARGUMENT_LENGTH, source, diagnostics, bindingId);
225
+ if (!name || !SAFE_COMMAND_NAME.test(name)) {
226
+ if (name && !SAFE_COMMAND_NAME.test(name)) diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));
227
+ return;
228
+ }
229
+ return {
230
+ name,
231
+ ...args ? { args } : {}
232
+ };
233
+ }
234
+ function parseExtensionAction(value, source, bindingId, diagnostics) {
235
+ if (!isRecord(value)) {
236
+ diagnostics.push(diagnostic(source, "bindings[].action must be an object.", bindingId));
237
+ return;
238
+ }
239
+ const name = readRequiredText(value.name, "bindings[].action.name", MAX_ACTION_NAME_LENGTH, source, diagnostics, bindingId);
240
+ if (!name || !SAFE_IDENTIFIER.test(name)) {
241
+ if (name && !SAFE_IDENTIFIER.test(name)) diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));
242
+ return;
243
+ }
244
+ return { name };
245
+ }
246
+ function parseBinding(value, source, diagnostics) {
247
+ if (!isRecord(value)) {
248
+ diagnostics.push(diagnostic(source, "bindings[] must be an object."));
249
+ return;
250
+ }
251
+ const bindingId = readRequiredText(value.id, "bindings[].id", MAX_BINDING_ID_LENGTH, source, diagnostics);
252
+ if (!bindingId) return void 0;
253
+ if (!SAFE_IDENTIFIER.test(bindingId)) {
254
+ diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));
255
+ return;
256
+ }
257
+ if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {
258
+ diagnostics.push(diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId));
259
+ return;
260
+ }
261
+ const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));
262
+ const hasCommand = Object.hasOwn(value, "command");
263
+ if (hasCommand === Object.hasOwn(value, "action")) {
264
+ diagnostics.push(diagnostic(source, "bindings[] must contain exactly one of command or action.", bindingId));
265
+ return;
266
+ }
267
+ const target = hasCommand ? { command: parseCommand(value.command, source, bindingId, diagnostics) } : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };
268
+ if (path.some((segment) => segment === void 0)) return void 0;
269
+ if ("command" in target) {
270
+ if (!target.command) return void 0;
271
+ return {
272
+ id: bindingId,
273
+ path,
274
+ command: target.command
275
+ };
276
+ }
277
+ if (!target.action) return void 0;
278
+ return {
279
+ id: bindingId,
280
+ path,
281
+ action: target.action
282
+ };
283
+ }
284
+ function parseContribution(value) {
285
+ const diagnostics = [];
286
+ if (!isRecord(value)) return { diagnostics: [diagnostic(UNKNOWN_SOURCE, "Leader contribution must be an object.")] };
287
+ const source = readRequiredText(value.source, "source", MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);
288
+ if (!source) return { diagnostics };
289
+ if (!SAFE_SOURCE.test(source)) {
290
+ diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));
291
+ return { diagnostics };
292
+ }
293
+ if (!Array.isArray(value.bindings)) return { diagnostics: [diagnostic(source, "bindings must be an array.")] };
294
+ const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));
295
+ const duplicateIds = bindings.flatMap((binding) => binding ? [binding.id] : []).filter((id, index, ids) => ids.indexOf(id) !== index);
296
+ for (const id of new Set(duplicateIds)) diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));
297
+ if (diagnostics.length > 0 || bindings.some((binding) => binding === void 0)) return { diagnostics };
298
+ return {
299
+ contribution: {
300
+ source,
301
+ bindings
302
+ },
303
+ diagnostics
304
+ };
305
+ }
306
+ function optionOrder(segment) {
307
+ return segment.order ?? DEFAULT_OPTION_ORDER;
308
+ }
309
+ function cloneAction(action) {
310
+ if (action.type === "command") return {
311
+ type: "command",
312
+ command: { ...action.command }
313
+ };
314
+ if (action.type === "app") return {
315
+ type: "app",
316
+ action: action.action
317
+ };
318
+ return {
319
+ type: "extension",
320
+ source: action.source,
321
+ action: { ...action.action }
322
+ };
323
+ }
324
+ function createRootNode() {
325
+ return {
326
+ key: "",
327
+ label: ROOT_LABEL,
328
+ order: 0,
329
+ owner: CORE_SOURCE,
330
+ children: /* @__PURE__ */ new Map()
331
+ };
332
+ }
333
+ function pathLabel(path) {
334
+ return `SPC ${path.map((segment) => segment.key).join(" ")}`;
335
+ }
336
+ /**
337
+ * Whether a shared group prefix is worded the same way by both contributors.
338
+ *
339
+ * `detail` is left out on purpose: it is a subtitle, and these packages ship on
340
+ * independent npm versions, so comparing it means one upgraded package disagrees
341
+ * with every package that has not caught up.
342
+ *
343
+ * A mismatch is REPORTED, never fatal. Losing `SPC e s` because two packages
344
+ * spell the `SPC e` group differently costs the user a key they can no longer
345
+ * press, which is worse than a menu row worded by whoever registered first.
346
+ */
347
+ function metadataMatches(node, segment) {
348
+ return node.label === segment.label && node.order === optionOrder(segment);
349
+ }
350
+ /** Every binding under a node, for a takeover message that says what it displaced. */
351
+ function leafCount(node) {
352
+ if (node.children.size === 0) return node.action ? 1 : 0;
353
+ let total = 0;
354
+ for (const child of node.children.values()) total += leafCount(child);
355
+ return total;
356
+ }
357
+ /**
358
+ * Inserts one binding, taking a key over from whoever holds it.
359
+ *
360
+ * LAST REGISTRATION WINS, and the diagnostics say who lost what. The rule this
361
+ * replaced rejected the newcomer, which read as "warning plus nothing happened":
362
+ * the key stayed bound to the first registrant, or - when the clash was only a
363
+ * group label - to nobody at all. A takeover is recoverable in a way a dropped
364
+ * binding is not: the tree is rebuilt from every stored contribution, so when
365
+ * the winner unregisters, the displaced binding comes back on the next rebuild.
366
+ *
367
+ * Returns diagnostics rather than one issue, because a single insert can both
368
+ * disagree about a group's wording and take a key from someone.
369
+ */
370
+ function insertBinding(root, binding) {
371
+ const diagnostics = [];
372
+ let current = root;
373
+ let firstMissing = -1;
374
+ for (const [index, segment] of binding.path.entries()) {
375
+ const existing = current.children.get(segment.key);
376
+ if (!existing) {
377
+ firstMissing = index;
378
+ break;
379
+ }
380
+ if (!metadataMatches(existing, segment)) diagnostics.push(diagnostic(binding.source, `${pathLabel(binding.path.slice(0, index + 1))} keeps the label from ${existing.owner}; the binding is registered.`, binding.id));
381
+ if (!existing.detail && segment.detail) existing.detail = segment.detail;
382
+ const isLeaf = index === binding.path.length - 1;
383
+ const displaced = leafCount(existing);
384
+ if (isLeaf && displaced > 0) {
385
+ diagnostics.push(diagnostic(binding.source, `${pathLabel(binding.path)} taken over from ${existing.owner}${displaced > 1 ? `, displacing ${displaced} bindings` : ""}.`, binding.id));
386
+ existing.children.clear();
387
+ delete existing.action;
388
+ delete existing.bindingId;
389
+ } else if (!isLeaf && existing.action) {
390
+ diagnostics.push(diagnostic(binding.source, `${pathLabel(binding.path.slice(0, index + 1))} taken over from ${existing.owner} to open a group.`, binding.id));
391
+ delete existing.action;
392
+ delete existing.bindingId;
393
+ }
394
+ if (isLeaf || existing.action === void 0) existing.owner = binding.source;
395
+ current = existing;
396
+ }
397
+ const start = firstMissing === -1 ? binding.path.length : firstMissing;
398
+ for (let index = start; index < binding.path.length; index += 1) {
399
+ const segment = binding.path[index];
400
+ const node = {
401
+ key: segment.key,
402
+ label: segment.label,
403
+ ...segment.detail ? { detail: segment.detail } : {},
404
+ ...segment.tone ? { tone: segment.tone } : {},
405
+ order: optionOrder(segment),
406
+ owner: binding.source,
407
+ children: /* @__PURE__ */ new Map()
408
+ };
409
+ current.children.set(segment.key, node);
410
+ current = node;
411
+ }
412
+ current.action = cloneAction(binding.action);
413
+ current.bindingId = binding.id;
414
+ current.owner = binding.source;
415
+ return diagnostics;
416
+ }
417
+ var DoomLeaderRegistry = class {
418
+ root = createRootNode();
419
+ contributions = /* @__PURE__ */ new Map();
420
+ listeners = /* @__PURE__ */ new Set();
421
+ diagnostics = [];
422
+ dirty = false;
423
+ constructor() {
424
+ this.rebuild();
425
+ }
426
+ register(value) {
427
+ const result = this.apply(value);
428
+ if (!result.accepted) return result;
429
+ this.rebuild();
430
+ return {
431
+ accepted: true,
432
+ diagnostics: this.getDiagnostics()
433
+ };
434
+ }
435
+ /** Records a contribution without rebuilding the whole tree immediately. */
436
+ registerDeferred(value) {
437
+ return this.apply(value);
438
+ }
439
+ apply(value) {
440
+ const parsed = parseContribution(value);
441
+ if (!parsed.contribution) return {
442
+ accepted: false,
443
+ diagnostics: parsed.diagnostics
444
+ };
445
+ this.contributions.delete(parsed.contribution.source);
446
+ if (parsed.contribution.bindings.length > 0) this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);
447
+ this.dirty = true;
448
+ return {
449
+ accepted: true,
450
+ diagnostics: parsed.diagnostics
451
+ };
452
+ }
453
+ /** Rebuilds one batch and returns its deterministic conflict diagnostics. */
454
+ flush() {
455
+ if (this.dirty) this.rebuild();
456
+ return this.diagnostics.map((item) => ({ ...item }));
457
+ }
458
+ getGroup(path) {
459
+ this.flush();
460
+ let current = this.root;
461
+ for (const key of path) {
462
+ const next = current.children.get(key);
463
+ if (!next || next.action) return void 0;
464
+ current = next;
465
+ }
466
+ const options = [...current.children.values()].sort((left, right) => left.key.localeCompare(right.key)).map((node) => ({
467
+ key: node.key,
468
+ label: node.label,
469
+ ...node.detail ? { detail: node.detail } : {},
470
+ ...node.tone ? { tone: node.tone } : {},
471
+ hasChildren: node.children.size > 0,
472
+ ...node.action ? { action: cloneAction(node.action) } : {}
473
+ }));
474
+ return {
475
+ label: current.label,
476
+ options
477
+ };
478
+ }
479
+ getDiagnostics() {
480
+ return this.flush();
481
+ }
482
+ subscribe(listener) {
483
+ this.listeners.add(listener);
484
+ return () => this.listeners.delete(listener);
485
+ }
486
+ rebuild() {
487
+ const nextRoot = createRootNode();
488
+ const diagnostics = [];
489
+ for (const binding of CORE_BINDINGS) {
490
+ const [issue] = insertBinding(nextRoot, binding);
491
+ if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);
492
+ }
493
+ for (const [source, bindings] of this.contributions.entries()) for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) diagnostics.push(...insertBinding(nextRoot, {
494
+ id: binding.id,
495
+ source,
496
+ path: binding.path,
497
+ action: "command" in binding ? {
498
+ type: "command",
499
+ command: binding.command
500
+ } : {
501
+ type: "extension",
502
+ source,
503
+ action: binding.action
504
+ }
505
+ }));
506
+ this.root = nextRoot;
507
+ this.diagnostics = diagnostics;
508
+ this.dirty = false;
509
+ for (const listener of this.listeners) listener();
510
+ }
511
+ };
512
+ //#endregion
513
+ exports.DoomLeaderRegistry = DoomLeaderRegistry;
514
+
515
+ //# sourceMappingURL=leaderRegistry.cjs.map