open-multi-agent-kit 0.94.1 → 0.95.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.
- package/CHANGELOG.md +33 -1
- package/README.md +100 -647
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +11 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +27 -2
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +239 -56
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-invalidation.js +1 -1
- package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.d.ts +5 -1
- package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-provider.js +37 -7
- package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
- package/dist/core/context-budget-v2-cache-telemetry.d.ts +1 -1
- package/dist/core/context-budget-v2-cache-telemetry.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-telemetry.js +2 -1
- package/dist/core/context-budget-v2-cache-telemetry.js.map +1 -1
- package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache.js +3 -0
- package/dist/core/context-budget-v2-cache.js.map +1 -1
- package/dist/core/context-budget-v2-representation-cache.d.ts.map +1 -1
- package/dist/core/context-budget-v2-representation-cache.js +4 -3
- package/dist/core/context-budget-v2-representation-cache.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +4 -0
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +3 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/github-repository.d.ts +3 -0
- package/dist/core/github-repository.d.ts.map +1 -0
- package/dist/core/github-repository.js +3 -0
- package/dist/core/github-repository.js.map +1 -0
- package/dist/core/model-registry.d.ts +3 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +9 -8
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/provider-usage.d.ts +48 -0
- package/dist/core/provider-usage.d.ts.map +1 -0
- package/dist/core/provider-usage.js +780 -0
- package/dist/core/provider-usage.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +10 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +8 -1
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +15 -17
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills-catalog-cache.d.ts +1 -0
- package/dist/core/skills-catalog-cache.d.ts.map +1 -1
- package/dist/core/skills-catalog-cache.js +42 -11
- package/dist/core/skills-catalog-cache.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -4
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +5 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-account-selector.d.ts +16 -0
- package/dist/modes/interactive/components/oauth-account-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/oauth-account-selector.js +61 -0
- package/dist/modes/interactive/components/oauth-account-selector.js.map +1 -0
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +25 -4
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts +21 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.js +165 -6
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +89 -60
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +2 -2
- package/dist/utils/image-resize-core.js.map +1 -1
- package/docs/development.md +3 -2
- package/docs/providers.md +19 -4
- package/docs/settings.md +9 -20
- package/docs/usage.md +10 -10
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +24 -24
- package/package.json +6 -6
- package/dist/core/github-star-nudge.d.ts +0 -15
- package/dist/core/github-star-nudge.d.ts.map +0 -1
- package/dist/core/github-star-nudge.js +0 -21
- package/dist/core/github-star-nudge.js.map +0 -1
|
@@ -16,7 +16,7 @@ import { parseSkillBlock } from "../../core/agent-session.js";
|
|
|
16
16
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
17
17
|
import { parseBangInvocation } from "../../core/bang-skill-invocation.js";
|
|
18
18
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
19
|
-
import {
|
|
19
|
+
import { OMK_GITHUB_REPOSITORY_URL } from "../../core/github-repository.js";
|
|
20
20
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
21
21
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
22
22
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
@@ -59,6 +59,7 @@ import { FooterComponent } from "./components/footer.js";
|
|
|
59
59
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
60
60
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
61
61
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
62
|
+
import { OAuthAccountSelectorComponent } from "./components/oauth-account-selector.js";
|
|
62
63
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
63
64
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
64
65
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
@@ -507,28 +508,6 @@ export class InteractiveMode {
|
|
|
507
508
|
this.editor.setAutocompleteProvider?.(provider);
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
|
-
showGithubStarNudgeIfNeeded() {
|
|
511
|
-
if (!shouldShowGithubStarNudge({ githubStarred: this.settingsManager.getGithubStarred() })) {
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
const starUrl = OMK_GITHUB_STAR_URL;
|
|
515
|
-
const starLink = getCapabilities().hyperlinks
|
|
516
|
-
? hyperlink(theme.fg("accent", "open repo & star"), starUrl)
|
|
517
|
-
: theme.fg("accent", starUrl);
|
|
518
|
-
const actionLine = theme.fg("muted", "Star it, then run ") +
|
|
519
|
-
theme.bold(theme.fg("accent", "/star")) +
|
|
520
|
-
theme.fg("muted", " so this stops. Link: ") +
|
|
521
|
-
starLink;
|
|
522
|
-
if (this.chatContainer.children.length > 0) {
|
|
523
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
524
|
-
}
|
|
525
|
-
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
|
526
|
-
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("accent", githubStarNudgeTitle()))}\n${githubStarNudgeBody()}`, 1, 0));
|
|
527
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
528
|
-
this.chatContainer.addChild(new Text(actionLine, 1, 0));
|
|
529
|
-
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
|
530
|
-
this.ui.requestRender();
|
|
531
|
-
}
|
|
532
511
|
showStartupNoticesIfNeeded() {
|
|
533
512
|
if (this.startupNoticesShown) {
|
|
534
513
|
return;
|
|
@@ -666,7 +645,7 @@ export class InteractiveMode {
|
|
|
666
645
|
// rail is actually visible, so the same data is never rendered twice.
|
|
667
646
|
this.statusSidebarPinned = this.settingsManager.getPinStatusSidebar();
|
|
668
647
|
this.ui.addChild(this.footerSlot);
|
|
669
|
-
this.statusSidebar = new StatusSidebarComponent(() => this.session, this.footerDataProvider, () => this.session.autoCompactionEnabled, () => this.ui.terminal.rows);
|
|
648
|
+
this.statusSidebar = new StatusSidebarComponent(() => this.session, this.footerDataProvider, () => this.session.autoCompactionEnabled, () => this.ui.terminal.rows, { requestRender: () => this.ui.requestRender() });
|
|
670
649
|
this.ui.showOverlay(this.statusSidebar, {
|
|
671
650
|
anchor: "top-right",
|
|
672
651
|
// Responsive: ~26% of the terminal, clamped — grows on fullscreen.
|
|
@@ -1445,7 +1424,6 @@ export class InteractiveMode {
|
|
|
1445
1424
|
this.setupExtensionShortcuts(extensionRunner);
|
|
1446
1425
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1447
1426
|
this.showStartupNoticesIfNeeded();
|
|
1448
|
-
this.showGithubStarNudgeIfNeeded();
|
|
1449
1427
|
}
|
|
1450
1428
|
applyRuntimeSettings() {
|
|
1451
1429
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
@@ -2292,8 +2270,8 @@ export class InteractiveMode {
|
|
|
2292
2270
|
this.editor.setText("");
|
|
2293
2271
|
return;
|
|
2294
2272
|
}
|
|
2295
|
-
if (text === "/star"
|
|
2296
|
-
this.handleStarCommand(
|
|
2273
|
+
if (text === "/star") {
|
|
2274
|
+
this.handleStarCommand();
|
|
2297
2275
|
return true;
|
|
2298
2276
|
}
|
|
2299
2277
|
if (text === "/changelog") {
|
|
@@ -2651,12 +2629,7 @@ export class InteractiveMode {
|
|
|
2651
2629
|
this.statusContainer.clear();
|
|
2652
2630
|
}
|
|
2653
2631
|
if (event.aborted) {
|
|
2654
|
-
|
|
2655
|
-
this.showError("Compaction cancelled");
|
|
2656
|
-
}
|
|
2657
|
-
else {
|
|
2658
|
-
this.showStatus("Auto-compaction cancelled");
|
|
2659
|
-
}
|
|
2632
|
+
this.showStatus(event.reason === "manual" ? "Compaction cancelled" : "Auto-compaction cancelled");
|
|
2660
2633
|
}
|
|
2661
2634
|
else if (event.result) {
|
|
2662
2635
|
this.chatContainer.clear();
|
|
@@ -2769,6 +2742,9 @@ export class InteractiveMode {
|
|
|
2769
2742
|
if (termination.kind === "completed" || this.lastRenderedTermination === termination)
|
|
2770
2743
|
return;
|
|
2771
2744
|
this.lastRenderedTermination = termination;
|
|
2745
|
+
// compaction_end owns the concise cancellation status; rendering this too duplicates it as a scary error.
|
|
2746
|
+
if (termination.causeCode === "compaction.aborted")
|
|
2747
|
+
return;
|
|
2772
2748
|
this.showError(formatSessionTermination(termination));
|
|
2773
2749
|
}
|
|
2774
2750
|
showPromptError(error, previousTermination) {
|
|
@@ -3056,15 +3032,21 @@ export class InteractiveMode {
|
|
|
3056
3032
|
try {
|
|
3057
3033
|
this.unregisterSignalHandlers();
|
|
3058
3034
|
}
|
|
3059
|
-
catch {
|
|
3035
|
+
catch (cleanupError) {
|
|
3036
|
+
void cleanupError;
|
|
3037
|
+
}
|
|
3060
3038
|
try {
|
|
3061
3039
|
killTrackedDetachedChildren();
|
|
3062
3040
|
}
|
|
3063
|
-
catch {
|
|
3041
|
+
catch (cleanupError) {
|
|
3042
|
+
void cleanupError;
|
|
3043
|
+
}
|
|
3064
3044
|
try {
|
|
3065
3045
|
this.ui.stop();
|
|
3066
3046
|
}
|
|
3067
|
-
catch {
|
|
3047
|
+
catch (cleanupError) {
|
|
3048
|
+
void cleanupError;
|
|
3049
|
+
}
|
|
3068
3050
|
console.error("OMK exiting due to uncaughtException:");
|
|
3069
3051
|
console.error(error);
|
|
3070
3052
|
process.exit(1);
|
|
@@ -4215,7 +4197,13 @@ export class InteractiveMode {
|
|
|
4215
4197
|
return;
|
|
4216
4198
|
}
|
|
4217
4199
|
if (providerOption.authType === "oauth") {
|
|
4218
|
-
|
|
4200
|
+
const accounts = this.session.modelRegistry.authStorage.listOAuthAccounts(providerOption.id);
|
|
4201
|
+
if (accounts.length > 0) {
|
|
4202
|
+
this.showOAuthAccountSelector(providerOption);
|
|
4203
|
+
}
|
|
4204
|
+
else {
|
|
4205
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4206
|
+
}
|
|
4219
4207
|
}
|
|
4220
4208
|
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4221
4209
|
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
@@ -4230,6 +4218,39 @@ export class InteractiveMode {
|
|
|
4230
4218
|
return { component: selector, focus: selector };
|
|
4231
4219
|
});
|
|
4232
4220
|
}
|
|
4221
|
+
showOAuthAccountSelector(provider) {
|
|
4222
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
4223
|
+
const accounts = authStorage.listOAuthAccounts(provider.id);
|
|
4224
|
+
if (accounts.length === 0) {
|
|
4225
|
+
void this.showLoginDialog(provider.id, provider.name);
|
|
4226
|
+
return;
|
|
4227
|
+
}
|
|
4228
|
+
this.showSelector((done) => {
|
|
4229
|
+
const selector = new OAuthAccountSelectorComponent(provider.name, accounts, async (selection) => {
|
|
4230
|
+
done();
|
|
4231
|
+
if (selection.kind === "add") {
|
|
4232
|
+
await this.showLoginDialog(provider.id, provider.name);
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
try {
|
|
4236
|
+
authStorage.selectOAuthAccount(provider.id, selection.index);
|
|
4237
|
+
this.session.modelRegistry.refresh();
|
|
4238
|
+
await this.updateAvailableProviderCount();
|
|
4239
|
+
this.footer.invalidate();
|
|
4240
|
+
this.updateEditorBorderColor();
|
|
4241
|
+
const label = authStorage.getOAuthAccountLabel(provider.id) ?? `Account ${selection.index + 1}`;
|
|
4242
|
+
this.showStatus(`Active ${provider.name} account: ${label}.`);
|
|
4243
|
+
}
|
|
4244
|
+
catch (error) {
|
|
4245
|
+
this.showError(`Account selection failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4246
|
+
}
|
|
4247
|
+
}, () => {
|
|
4248
|
+
done();
|
|
4249
|
+
this.showLoginProviderSelector("oauth");
|
|
4250
|
+
});
|
|
4251
|
+
return { component: selector, focus: selector };
|
|
4252
|
+
});
|
|
4253
|
+
}
|
|
4233
4254
|
async showOAuthSelector(mode) {
|
|
4234
4255
|
if (mode === "login") {
|
|
4235
4256
|
this.showLoginAuthTypeSelector();
|
|
@@ -4248,12 +4269,20 @@ export class InteractiveMode {
|
|
|
4248
4269
|
return;
|
|
4249
4270
|
}
|
|
4250
4271
|
try {
|
|
4272
|
+
const oauthAccountCount = this.session.modelRegistry.authStorage.getOAuthAccountCount(providerOption.id);
|
|
4251
4273
|
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
4252
4274
|
this.session.modelRegistry.refresh();
|
|
4253
4275
|
await this.updateAvailableProviderCount();
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4276
|
+
let message;
|
|
4277
|
+
if (providerOption.authType === "oauth") {
|
|
4278
|
+
message =
|
|
4279
|
+
oauthAccountCount > 1
|
|
4280
|
+
? `Logged out of ${providerOption.name}. Removed ${oauthAccountCount} subscription accounts.`
|
|
4281
|
+
: `Logged out of ${providerOption.name}`;
|
|
4282
|
+
}
|
|
4283
|
+
else {
|
|
4284
|
+
message = `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
4285
|
+
}
|
|
4257
4286
|
this.showStatus(message);
|
|
4258
4287
|
}
|
|
4259
4288
|
catch (error) {
|
|
@@ -4268,7 +4297,18 @@ export class InteractiveMode {
|
|
|
4268
4297
|
}
|
|
4269
4298
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4270
4299
|
this.session.modelRegistry.refresh();
|
|
4271
|
-
const
|
|
4300
|
+
const oauthAccountCount = this.session.modelRegistry.authStorage.getOAuthAccountCount(providerId);
|
|
4301
|
+
const oauthAccountLabel = this.session.modelRegistry.authStorage.getOAuthAccountLabel(providerId);
|
|
4302
|
+
let actionLabel;
|
|
4303
|
+
if (authType === "oauth") {
|
|
4304
|
+
actionLabel = `Logged in to ${providerName}${oauthAccountLabel ? ` as ${oauthAccountLabel}` : ""}`;
|
|
4305
|
+
if (oauthAccountCount > 1) {
|
|
4306
|
+
actionLabel += ` (${oauthAccountCount} subscription accounts; use /login to switch)`;
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
else {
|
|
4310
|
+
actionLabel = `Saved API key for ${providerName}`;
|
|
4311
|
+
}
|
|
4272
4312
|
let selectedModel;
|
|
4273
4313
|
let selectionError;
|
|
4274
4314
|
if (isUnknownModel(previousModel)) {
|
|
@@ -4787,28 +4827,17 @@ export class InteractiveMode {
|
|
|
4787
4827
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4788
4828
|
this.ui.requestRender();
|
|
4789
4829
|
}
|
|
4790
|
-
handleStarCommand(
|
|
4791
|
-
|
|
4792
|
-
const
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4796
|
-
this.chatContainer.addChild(new Text(theme.fg("warning", "Star nudge reset. It will show again on the next interactive launch until you /star."), 1, 0));
|
|
4797
|
-
this.ui.requestRender();
|
|
4798
|
-
return;
|
|
4799
|
-
}
|
|
4800
|
-
openBrowser(OMK_GITHUB_STAR_URL);
|
|
4801
|
-
this.settingsManager.setGithubStarred(true);
|
|
4802
|
-
const starLink = getCapabilities().hyperlinks
|
|
4803
|
-
? hyperlink(theme.fg("accent", OMK_GITHUB_STAR_URL), OMK_GITHUB_STAR_URL)
|
|
4804
|
-
: theme.fg("accent", OMK_GITHUB_STAR_URL);
|
|
4830
|
+
handleStarCommand() {
|
|
4831
|
+
openBrowser(OMK_GITHUB_REPOSITORY_URL);
|
|
4832
|
+
const repositoryLink = getCapabilities().hyperlinks
|
|
4833
|
+
? hyperlink(theme.fg("accent", OMK_GITHUB_REPOSITORY_URL), OMK_GITHUB_REPOSITORY_URL)
|
|
4834
|
+
: theme.fg("accent", OMK_GITHUB_REPOSITORY_URL);
|
|
4805
4835
|
this.chatContainer.addChild(new Spacer(1));
|
|
4806
4836
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
|
4807
4837
|
this.chatContainer.addChild(new Text([
|
|
4808
|
-
theme.bold(theme.fg("accent", "
|
|
4809
|
-
theme.fg("muted", "Opened the
|
|
4810
|
-
`${theme.fg("muted", "
|
|
4811
|
-
theme.fg("dim", "Forgot already? /star reset brings the nag back."),
|
|
4838
|
+
theme.bold(theme.fg("accent", "OMK on GitHub")),
|
|
4839
|
+
theme.fg("muted", "Opened the OMK repository in your browser."),
|
|
4840
|
+
`${theme.fg("muted", "Repository: ")}${repositoryLink}`,
|
|
4812
4841
|
].join("\n"), 1, 0));
|
|
4813
4842
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("accent", text)));
|
|
4814
4843
|
this.ui.requestRender();
|