@agi-fans/dsh-tui 0.22.0 → 0.23.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/LICENSE +26 -0
- package/lib/CHANGELOG.md +199 -1
- package/lib/chrome/box.d.ts +1 -18
- package/lib/chrome/box.js +2 -45
- package/lib/chrome/box.js.map +1 -1
- package/lib/chrome/code-highlight.js +4 -4
- package/lib/chrome/code-highlight.js.map +1 -1
- package/lib/chrome/command-output.d.ts +0 -1
- package/lib/chrome/command-output.js +11 -5
- package/lib/chrome/command-output.js.map +1 -1
- package/lib/chrome/goal-bar.js +1 -1
- package/lib/chrome/goal-bar.js.map +1 -1
- package/lib/chrome/main-screen-renderer.js +4 -3
- package/lib/chrome/main-screen-renderer.js.map +1 -1
- package/lib/chrome/markdown.d.ts +3 -3
- package/lib/chrome/markdown.js +85 -36
- package/lib/chrome/markdown.js.map +1 -1
- package/lib/chrome/md-style.d.ts +5 -2
- package/lib/chrome/md-style.js +13 -4
- package/lib/chrome/md-style.js.map +1 -1
- package/lib/chrome/renderer.d.ts +37 -1
- package/lib/chrome/renderer.js.map +1 -1
- package/lib/chrome/status-line.d.ts +3 -3
- package/lib/chrome/status-line.js +5 -5
- package/lib/chrome/status-line.js.map +1 -1
- package/lib/chrome/theme.d.ts +8 -3
- package/lib/chrome/theme.js +51 -153
- package/lib/chrome/theme.js.map +1 -1
- package/lib/commands/auth-account.d.ts +9 -0
- package/lib/commands/auth-account.js +202 -0
- package/lib/commands/auth-account.js.map +1 -0
- package/lib/commands/auth.js +238 -414
- package/lib/commands/auth.js.map +1 -1
- package/lib/commands/diff.js +22 -16
- package/lib/commands/diff.js.map +1 -1
- package/lib/commands/export.d.ts +0 -1
- package/lib/commands/export.js +97 -32
- package/lib/commands/export.js.map +1 -1
- package/lib/commands/files.js +2 -8
- package/lib/commands/files.js.map +1 -1
- package/lib/commands/integrations.d.ts +12 -1
- package/lib/commands/integrations.js +120 -23
- package/lib/commands/integrations.js.map +1 -1
- package/lib/commands/jobs-browser.d.ts +3 -0
- package/lib/commands/jobs-browser.js +152 -0
- package/lib/commands/jobs-browser.js.map +1 -0
- package/lib/commands/jobs.d.ts +3 -14
- package/lib/commands/jobs.js +26 -18
- package/lib/commands/jobs.js.map +1 -1
- package/lib/commands/model.d.ts +3 -0
- package/lib/commands/model.js +197 -120
- package/lib/commands/model.js.map +1 -1
- package/lib/commands/open-external.d.ts +2 -0
- package/lib/commands/open-external.js +19 -0
- package/lib/commands/open-external.js.map +1 -0
- package/lib/commands/permission.js +65 -54
- package/lib/commands/permission.js.map +1 -1
- package/lib/commands/plugins.js +381 -139
- package/lib/commands/plugins.js.map +1 -1
- package/lib/commands/schedule-catalog.d.ts +12 -0
- package/lib/commands/schedule-catalog.js +41 -0
- package/lib/commands/schedule-catalog.js.map +1 -0
- package/lib/commands/schedule-history.d.ts +4 -0
- package/lib/commands/schedule-history.js +62 -0
- package/lib/commands/schedule-history.js.map +1 -0
- package/lib/commands/schedule.d.ts +12 -15
- package/lib/commands/schedule.js +220 -187
- package/lib/commands/schedule.js.map +1 -1
- package/lib/commands/session-configuration.d.ts +3 -0
- package/lib/commands/session-configuration.js +62 -28
- package/lib/commands/session-configuration.js.map +1 -1
- package/lib/commands/session.js +111 -66
- package/lib/commands/session.js.map +1 -1
- package/lib/commands/terminal.js +14 -8
- package/lib/commands/terminal.js.map +1 -1
- package/lib/definition.d.ts +112 -15
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/input/actions.d.ts +1 -1
- package/lib/input/actions.js +5 -2
- package/lib/input/actions.js.map +1 -1
- package/lib/input/composer-files.d.ts +2 -0
- package/lib/input/composer-files.js +13 -3
- package/lib/input/composer-files.js.map +1 -1
- package/lib/input/image-paste.d.ts +5 -12
- package/lib/input/image-paste.js +32 -28
- package/lib/input/image-paste.js.map +1 -1
- package/lib/input/key-test.d.ts +5 -0
- package/lib/input/key-test.js +19 -0
- package/lib/input/key-test.js.map +1 -0
- package/lib/input/keys.d.ts +2 -0
- package/lib/input/keys.js +4 -1
- package/lib/input/keys.js.map +1 -1
- package/lib/locale/index.d.ts +6 -0
- package/lib/locale/index.js +32 -0
- package/lib/locale/index.js.map +1 -0
- package/lib/locale/prompt.d.ts +4 -0
- package/lib/locale/prompt.js +24 -0
- package/lib/locale/prompt.js.map +1 -0
- package/lib/locale/zh.d.ts +1114 -0
- package/lib/locale/zh.js +1115 -0
- package/lib/locale/zh.js.map +1 -0
- package/lib/prompt-intents.d.ts +4 -0
- package/lib/prompt-intents.js +4 -0
- package/lib/prompt-intents.js.map +1 -1
- package/lib/runtime/account-settings.d.ts +18 -0
- package/lib/runtime/account-settings.js +241 -0
- package/lib/runtime/account-settings.js.map +1 -0
- package/lib/runtime/agent-profile.d.ts +3 -3
- package/lib/runtime/agent-profile.js +4 -4
- package/lib/runtime/agent-profile.js.map +1 -1
- package/lib/runtime/btw-policy.js +2 -1
- package/lib/runtime/btw-policy.js.map +1 -1
- package/lib/runtime/btw.js +1 -2
- package/lib/runtime/btw.js.map +1 -1
- package/lib/runtime/composer-images.d.ts +2 -2
- package/lib/runtime/composer-images.js +4 -4
- package/lib/runtime/composer-images.js.map +1 -1
- package/lib/runtime/composer-shell.js +27 -1
- package/lib/runtime/composer-shell.js.map +1 -1
- package/lib/runtime/file-browser.d.ts +19 -0
- package/lib/runtime/file-browser.js +291 -0
- package/lib/runtime/file-browser.js.map +1 -0
- package/lib/runtime/file-review.d.ts +6 -9
- package/lib/runtime/file-review.js +91 -7
- package/lib/runtime/file-review.js.map +1 -1
- package/lib/runtime/help-reader.d.ts +12 -0
- package/lib/runtime/help-reader.js +50 -0
- package/lib/runtime/help-reader.js.map +1 -0
- package/lib/runtime/human-interaction.js +1 -1
- package/lib/runtime/human-interaction.js.map +1 -1
- package/lib/runtime/model-catalog-editor.d.ts +12 -0
- package/lib/runtime/model-catalog-editor.js +215 -0
- package/lib/runtime/model-catalog-editor.js.map +1 -0
- package/lib/runtime/model-settings.d.ts +11 -0
- package/lib/runtime/model-settings.js +404 -0
- package/lib/runtime/model-settings.js.map +1 -0
- package/lib/runtime/plugin-settings.d.ts +4 -1
- package/lib/runtime/plugin-settings.js +164 -88
- package/lib/runtime/plugin-settings.js.map +1 -1
- package/lib/runtime/prompt-browser.d.ts +9 -0
- package/lib/runtime/prompt-browser.js +19 -0
- package/lib/runtime/prompt-browser.js.map +1 -0
- package/lib/runtime/provider-local.d.ts +27 -27
- package/lib/runtime/provider-local.js +1264 -308
- package/lib/runtime/provider-local.js.map +1 -1
- package/lib/runtime/reference-reader.d.ts +15 -0
- package/lib/runtime/reference-reader.js +78 -0
- package/lib/runtime/reference-reader.js.map +1 -0
- package/lib/runtime/runner.js +38 -29
- package/lib/runtime/runner.js.map +1 -1
- package/lib/runtime/session-runtime.js +1 -1
- package/lib/runtime/session-runtime.js.map +1 -1
- package/lib/runtime/settings-draft.d.ts +21 -0
- package/lib/runtime/settings-draft.js +166 -0
- package/lib/runtime/settings-draft.js.map +1 -0
- package/lib/runtime/settings-preferences.js +1 -1
- package/lib/runtime/settings-preferences.js.map +1 -1
- package/lib/runtime/startup-notices.d.ts +0 -1
- package/lib/runtime/startup-notices.js +35 -6
- package/lib/runtime/startup-notices.js.map +1 -1
- package/lib/runtime/subagent-model-selection.js +2 -1
- package/lib/runtime/subagent-model-selection.js.map +1 -1
- package/lib/runtime/subagent-settings.d.ts +14 -0
- package/lib/runtime/subagent-settings.js +288 -0
- package/lib/runtime/subagent-settings.js.map +1 -0
- package/lib/runtime/terminal-notifications.d.ts +3 -0
- package/lib/runtime/terminal-notifications.js +13 -8
- package/lib/runtime/terminal-notifications.js.map +1 -1
- package/lib/runtime/terminal-probe.d.ts +2 -0
- package/lib/runtime/terminal-probe.js +9 -0
- package/lib/runtime/terminal-probe.js.map +1 -1
- package/lib/runtime/tool-presentation.js.map +1 -1
- package/lib/runtime/tool-reader.d.ts +5 -0
- package/lib/runtime/tool-reader.js +27 -0
- package/lib/runtime/tool-reader.js.map +1 -0
- package/lib/runtime/web-settings.d.ts +14 -0
- package/lib/runtime/web-settings.js +339 -0
- package/lib/runtime/web-settings.js.map +1 -0
- package/lib/session/command-attachments.d.ts +17 -0
- package/lib/session/command-attachments.js +50 -0
- package/lib/session/command-attachments.js.map +1 -0
- package/lib/session/context-diagnostics.d.ts +16 -0
- package/lib/session/context-diagnostics.js +15 -0
- package/lib/session/context-diagnostics.js.map +1 -1
- package/lib/session/external-agents.d.ts +3 -1
- package/lib/session/external-agents.js +35 -10
- package/lib/session/external-agents.js.map +1 -1
- package/lib/session/generation-continuation.d.ts +11 -0
- package/lib/session/generation-continuation.js +34 -0
- package/lib/session/generation-continuation.js.map +1 -0
- package/lib/session/goal-controls.d.ts +5 -0
- package/lib/session/goal-controls.js +81 -0
- package/lib/session/goal-controls.js.map +1 -0
- package/lib/session/interaction-adapter.d.ts +7 -3
- package/lib/session/interaction-adapter.js +121 -49
- package/lib/session/interaction-adapter.js.map +1 -1
- package/lib/session/message-queue.d.ts +2 -0
- package/lib/session/message-queue.js +102 -17
- package/lib/session/message-queue.js.map +1 -1
- package/lib/session/pending-inputs.d.ts +30 -0
- package/lib/session/pending-inputs.js +183 -0
- package/lib/session/pending-inputs.js.map +1 -0
- package/lib/session/session-configuration.js +1 -1
- package/lib/session/session-configuration.js.map +1 -1
- package/lib/session/session-controller.d.ts +32 -13
- package/lib/session/session-controller.js +444 -98
- package/lib/session/session-controller.js.map +1 -1
- package/lib/session/subagent-roster.d.ts +1 -0
- package/lib/session/subagent-roster.js +16 -5
- package/lib/session/subagent-roster.js.map +1 -1
- package/lib/session/subagent-tracker.d.ts +2 -0
- package/lib/session/subagent-tracker.js +18 -0
- package/lib/session/subagent-tracker.js.map +1 -1
- package/lib/session/tui-settings.d.ts +9 -5
- package/lib/session/tui-settings.js +3 -1
- package/lib/session/tui-settings.js.map +1 -1
- package/lib/session/workflow-projection.d.ts +2 -1
- package/lib/session/workflow-projection.js +34 -4
- package/lib/session/workflow-projection.js.map +1 -1
- package/lib/views/autocomplete.d.ts +10 -7
- package/lib/views/autocomplete.js +44 -37
- package/lib/views/autocomplete.js.map +1 -1
- package/lib/views/context-overview.d.ts +4 -0
- package/lib/views/context-overview.js +43 -0
- package/lib/views/context-overview.js.map +1 -0
- package/lib/views/copy-targets.d.ts +6 -2
- package/lib/views/copy-targets.js +19 -14
- package/lib/views/copy-targets.js.map +1 -1
- package/lib/views/document-reader.d.ts +10 -1
- package/lib/views/document-reader.js +59 -5
- package/lib/views/document-reader.js.map +1 -1
- package/lib/views/event-views.js +29 -11
- package/lib/views/event-views.js.map +1 -1
- package/lib/views/file-actions.js +21 -0
- package/lib/views/file-actions.js.map +1 -1
- package/lib/views/file-browser.d.ts +98 -0
- package/lib/views/file-browser.js +120 -0
- package/lib/views/file-browser.js.map +1 -0
- package/lib/views/history-search.js +4 -4
- package/lib/views/history-search.js.map +1 -1
- package/lib/views/hotkey-format.d.ts +3 -2
- package/lib/views/hotkey-format.js +4 -3
- package/lib/views/hotkey-format.js.map +1 -1
- package/lib/views/hotkeys.js +10 -12
- package/lib/views/hotkeys.js.map +1 -1
- package/lib/views/list-view.js +16 -11
- package/lib/views/list-view.js.map +1 -1
- package/lib/views/prompt-selector.d.ts +23 -1
- package/lib/views/prompt-selector.js +123 -60
- package/lib/views/prompt-selector.js.map +1 -1
- package/lib/views/settings-list.d.ts +6 -1
- package/lib/views/settings-list.js +69 -46
- package/lib/views/settings-list.js.map +1 -1
- package/lib/views/tool-actions.d.ts +16 -0
- package/lib/views/tool-actions.js +29 -0
- package/lib/views/tool-actions.js.map +1 -0
- package/lib/views/trajectory-timeline.d.ts +5 -6
- package/lib/views/trajectory-timeline.js +25 -15
- package/lib/views/trajectory-timeline.js.map +1 -1
- package/lib/views/trajectory.d.ts +23 -2
- package/lib/views/trajectory.js +404 -186
- package/lib/views/trajectory.js.map +1 -1
- package/lib/views/transcript-render.d.ts +39 -15
- package/lib/views/transcript-render.js +406 -208
- package/lib/views/transcript-render.js.map +1 -1
- package/lib/views/transcript-types.d.ts +3 -2
- package/lib/views/transcript-types.js.map +1 -1
- package/lib/views/workflow.d.ts +16 -0
- package/lib/views/workflow.js +42 -0
- package/lib/views/workflow.js.map +1 -0
- package/package.json +61 -58
- package/lib/chrome/welcome-tips.d.ts +0 -23
- package/lib/chrome/welcome-tips.js +0 -24
- package/lib/chrome/welcome-tips.js.map +0 -1
- package/lib/session/steering.d.ts +0 -3
- package/lib/session/steering.js +0 -9
- package/lib/session/steering.js.map +0 -1
package/lib/commands/plugins.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/** Native profile management over the published Harness transaction service. */
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { startCreator } from "./session-configuration.js";
|
|
3
4
|
import { registerCommands } from "./registration.js";
|
|
4
5
|
import { askPrompt, confirmPrompt } from "../prompt-intents.js";
|
|
5
|
-
import { editSettingsEntry, nativeIntegrationId, showIntegrationStatus } from "../runtime/plugin-settings.js";
|
|
6
|
-
import { externalAgentGuide } from "../session/external-agents.js";
|
|
6
|
+
import { editSettingsEntry, nativeIntegrationId, pluginSettingsEntries, showIntegrationStatus } from "../runtime/plugin-settings.js";
|
|
7
|
+
import { externalAgentGuide, externalAgentSummary } from "../session/external-agents.js";
|
|
8
|
+
import { readReference } from "../runtime/reference-reader.js";
|
|
9
|
+
import { createPromptBrowser } from "../runtime/prompt-browser.js";
|
|
10
|
+
import { resolveUiLanguage, uiText } from "../locale/index.js";
|
|
7
11
|
export const name = 'omdsh-command-plugins';
|
|
8
12
|
export const inject = ['commands', 'pluginManager', 'tui'];
|
|
9
13
|
// Removing the command's own presentation or session owner during its awaited
|
|
@@ -19,19 +23,53 @@ export function pluginReadOnly(row) {
|
|
|
19
23
|
return 'terminal-required';
|
|
20
24
|
return row.readOnlyReason;
|
|
21
25
|
}
|
|
22
|
-
|
|
26
|
+
function changeSummary(result) {
|
|
23
27
|
const selection = result.application === 'cancelled' || result.application === 'failed' || result.enabled === undefined
|
|
24
28
|
? '' : result.enabled ? ' · enabled' : ' · disabled';
|
|
25
29
|
const restart = result.application === 'restart-required' && result.restartReason !== undefined
|
|
26
30
|
? ` · restart required (${result.restartReason})` : '';
|
|
31
|
+
return `${result.target}: ${result.application}${selection}${restart}`;
|
|
32
|
+
}
|
|
33
|
+
export function changeText(result) {
|
|
27
34
|
return [
|
|
28
|
-
|
|
35
|
+
changeSummary(result),
|
|
29
36
|
...(result.error === undefined ? [] : [result.error.diagnostic ?? result.error.code]),
|
|
30
37
|
...(result.error?.incompatible ?? []).map(item => `${item.name}@${item.version}: DSH ${item.runtimeVersion} rejects ${JSON.stringify(item.requirements)}`),
|
|
31
38
|
...(result.warnings ?? []),
|
|
32
39
|
...(result.packageResult === undefined ? [] : [result.packageResult.output, `Full installation log: ${result.packageResult.logPath}`]),
|
|
33
40
|
].filter(Boolean).join('\n');
|
|
34
41
|
}
|
|
42
|
+
/** Keep the observed outcome beside the operation, with full logs on demand. */
|
|
43
|
+
function changeFeedback(result) {
|
|
44
|
+
if (result === undefined)
|
|
45
|
+
return {};
|
|
46
|
+
return {
|
|
47
|
+
detail: changeSummary(result),
|
|
48
|
+
actions: [{ key: 'Alt+D', label: 'details', valuePrefix: 'result:', scope: 'list' }],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function readChangeDetails(ctx, result, signal) {
|
|
52
|
+
if (result === undefined || signal.aborted)
|
|
53
|
+
return;
|
|
54
|
+
const text = changeText(result);
|
|
55
|
+
if (ctx.tui.interactive === false) {
|
|
56
|
+
ctx.tui.commandOutput('/plugins', text);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let position;
|
|
60
|
+
while (!signal.aborted) {
|
|
61
|
+
const action = await ctx.tui.prompt({ localize: ['options'], title: result.target, question: '',
|
|
62
|
+
presentation: 'document', documentSource: { text }, allowCustom: false, notify: false, signal,
|
|
63
|
+
...(position === undefined ? {} : { documentPosition: position }),
|
|
64
|
+
onDocumentPosition: value => { position = value; },
|
|
65
|
+
options: [{ label: 'Back', value: 'back' },
|
|
66
|
+
...(ctx.tui.copyText === undefined ? [] : [{ label: 'Copy details', value: 'copy' }])],
|
|
67
|
+
});
|
|
68
|
+
if (signal.aborted || action !== 'copy')
|
|
69
|
+
return;
|
|
70
|
+
await ctx.tui.copyText?.(text, result.target);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
35
73
|
function bundleStatus(row) {
|
|
36
74
|
if (row.availability === 'missing')
|
|
37
75
|
return row.installed ? 'Package unavailable' : 'Not installed';
|
|
@@ -56,7 +94,7 @@ async function install(ctx, spec, signal, approvedBuilds, saveExact = false) {
|
|
|
56
94
|
const operation = ctx.pluginManager.installBundle(spec, { requestId, saveExact, ...(approvedBuilds === undefined ? {} : { approvedBuilds }) });
|
|
57
95
|
// The installer owns cancellation through restoration, even after the
|
|
58
96
|
// prompt closes. Never abandon its promise or report cancellation early.
|
|
59
|
-
const prompt = ctx.tui.prompt({
|
|
97
|
+
const prompt = ctx.tui.prompt({ localize: true,
|
|
60
98
|
title: 'Install plugin', question: spec, presentation: 'progress',
|
|
61
99
|
detail, refreshDocument: () => detail, notify: false,
|
|
62
100
|
allowCustom: false, signal: AbortSignal.any([signal, promptAbort.signal]),
|
|
@@ -76,10 +114,10 @@ async function install(ctx, spec, signal, approvedBuilds, saveExact = false) {
|
|
|
76
114
|
async function confirmInstall(ctx, spec, signal, saveExact = false) {
|
|
77
115
|
const inspected = await ctx.pluginManager.inspect(spec, undefined, signal);
|
|
78
116
|
if (inspected.status === 'refused') {
|
|
79
|
-
ctx.tui
|
|
117
|
+
await readReference(ctx.tui, spec, inspected.reason, signal);
|
|
80
118
|
return;
|
|
81
119
|
}
|
|
82
|
-
const consent = await confirmPrompt(request => ctx.tui.prompt(request), {
|
|
120
|
+
const consent = await confirmPrompt(request => ctx.tui.prompt(request), { localize: ['title', 'options', 'actions'],
|
|
83
121
|
title: 'Install bundle',
|
|
84
122
|
question: inspected.name ?? spec,
|
|
85
123
|
detail: `${inspected.version ?? ''} ${inspected.description ?? ''}\nInstalled Host code runs with your user permissions and affects all sessions in this profile.`,
|
|
@@ -92,11 +130,10 @@ async function confirmInstall(ctx, spec, signal, saveExact = false) {
|
|
|
92
130
|
return;
|
|
93
131
|
let result = await install(ctx, spec, signal, undefined, saveExact);
|
|
94
132
|
if (result.pendingBuilds?.length && !signal.aborted) {
|
|
95
|
-
ctx.tui.
|
|
96
|
-
const approved = await confirmPrompt(request => ctx.tui.prompt(request), {
|
|
133
|
+
const approved = await confirmPrompt(request => ctx.tui.prompt(request), { localize: ['title', 'options', 'actions'],
|
|
97
134
|
title: 'Dependency scripts',
|
|
98
135
|
question: 'Allow these scripts and retry?',
|
|
99
|
-
detail: `${result.pendingBuilds.join('\n')}\nPermission persists by package name in this profile. Scripts run with your user permissions.`,
|
|
136
|
+
detail: `${changeSummary(result)}\n\n${result.pendingBuilds.join('\n')}\nPermission persists by package name in this profile. Scripts run with your user permissions.`,
|
|
100
137
|
acceptLabel: 'Allow scripts and retry',
|
|
101
138
|
cancelLabel: null,
|
|
102
139
|
signal,
|
|
@@ -106,173 +143,291 @@ async function confirmInstall(ctx, spec, signal, saveExact = false) {
|
|
|
106
143
|
}
|
|
107
144
|
return result;
|
|
108
145
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Browse the plugin indexes the profile configures and install an entry.
|
|
111
|
-
*
|
|
112
|
-
* The Host service owns fetching, validating and annotating an index; this only
|
|
113
|
-
* presents it, which is the split the Harness client makes too. With no source
|
|
114
|
-
* configured the answer is that sentence rather than an empty list, because the
|
|
115
|
-
* next step is a setting rather than a choice.
|
|
116
|
-
*/
|
|
117
146
|
/** Index text in the reader's language when the index offers one. */
|
|
118
147
|
function marketText(text) {
|
|
119
148
|
return typeof text === 'string' ? text : text.en;
|
|
120
149
|
}
|
|
121
|
-
async function discover(ctx, signal) {
|
|
150
|
+
async function discover(ctx, signal, onSuperseded) {
|
|
151
|
+
const prompt = (request) => ctx.tui.prompt({ dismissLabel: 'Back', ...request, signal: request.signal ?? signal, onSuperseded });
|
|
122
152
|
const market = ctx.get('pluginMarket');
|
|
153
|
+
const t = (text) => uiText(resolveUiLanguage(ctx.tui.prefs?.().uiLanguage), text);
|
|
123
154
|
if (market === undefined) {
|
|
124
|
-
ctx.tui
|
|
155
|
+
await readReference(ctx.tui, t('Discover plugins'), t('The plugin market is not mounted in this profile.'), signal);
|
|
125
156
|
return;
|
|
126
157
|
}
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
const searchPrompt = (query) => prompt(askPrompt({ localize: ['title', 'question', 'actions'],
|
|
159
|
+
title: 'Discover plugins', question: 'Search the plugin indexes (empty lists everything)', initialInput: query, signal }));
|
|
160
|
+
const initialQuery = await searchPrompt('');
|
|
161
|
+
if (initialQuery === null || signal.aborted)
|
|
162
|
+
return;
|
|
163
|
+
let query = initialQuery;
|
|
164
|
+
let entries = [];
|
|
165
|
+
const pages = new Map();
|
|
166
|
+
const failures = new Map();
|
|
167
|
+
let diagnostic = '', result;
|
|
168
|
+
const load = async (mode = 'first') => {
|
|
129
169
|
const sources = market.listIndexes();
|
|
130
170
|
const usable = sources.filter(source => source.error === undefined);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
171
|
+
for (const url of pages.keys())
|
|
172
|
+
if (!usable.some(source => source.url === url))
|
|
173
|
+
pages.delete(url);
|
|
174
|
+
for (const url of failures.keys())
|
|
175
|
+
if (!usable.some(source => source.url === url))
|
|
176
|
+
failures.delete(url);
|
|
177
|
+
const requests = usable.flatMap(source => {
|
|
178
|
+
const previous = pages.get(source.url);
|
|
179
|
+
const failure = failures.get(source.url);
|
|
180
|
+
const offset = mode === 'more' ? previous?.nextOffset
|
|
181
|
+
: mode === 'retry' ? failure?.offset : 0;
|
|
182
|
+
return offset == null ? [] : [{ url: source.url, offset, previous }];
|
|
183
|
+
});
|
|
184
|
+
// The published search API has no per-request abort. Dismissal abandons
|
|
185
|
+
// this read, and only the winning result may update the browser state.
|
|
186
|
+
const dismissed = new AbortController();
|
|
187
|
+
const readSignal = AbortSignal.any([signal, dismissed.signal]);
|
|
188
|
+
const progressEnd = new AbortController();
|
|
189
|
+
let progress;
|
|
190
|
+
let stop;
|
|
191
|
+
const stopped = new Promise(resolve => { stop = () => resolve(null); });
|
|
192
|
+
readSignal.addEventListener('abort', stop, { once: true });
|
|
193
|
+
if (readSignal.aborted)
|
|
194
|
+
stop();
|
|
195
|
+
const timer = setTimeout(() => {
|
|
196
|
+
if (readSignal.aborted)
|
|
197
|
+
return;
|
|
198
|
+
progress = prompt({ localize: true, title: 'Discover plugins', question: 'Loading plugins…',
|
|
199
|
+
detail: t('Esc returns to the results.'), presentation: 'progress', allowCustom: false, notify: false,
|
|
200
|
+
signal: AbortSignal.any([readSignal, progressEnd.signal]),
|
|
201
|
+
}).then(() => { if (!progressEnd.signal.aborted)
|
|
202
|
+
dismissed.abort(); }, () => dismissed.abort());
|
|
203
|
+
}, 150);
|
|
140
204
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
205
|
+
const operation = Promise.allSettled(requests.map(async ({ url, offset, previous }) => {
|
|
206
|
+
const queryOptions = { ...(query.trim() === '' ? {} : { text: query.trim() }), sort: { kind: 'relevance' }, limit: 20 };
|
|
207
|
+
let page = await market.search(url, { ...queryOptions, offset,
|
|
208
|
+
...(mode === 'refresh' || (mode === 'retry' && offset === 0) ? { refresh: true } : {}),
|
|
209
|
+
...(offset > 0 && previous !== undefined ? { fetchedAt: previous.fetchedAt } : {}),
|
|
210
|
+
});
|
|
211
|
+
if (readSignal.aborted)
|
|
212
|
+
return page;
|
|
213
|
+
if (offset > 0 && previous !== undefined) {
|
|
214
|
+
if (page.fetchedAt === previous.fetchedAt)
|
|
215
|
+
page = { ...page, entries: [...previous.entries, ...page.entries] };
|
|
216
|
+
else
|
|
217
|
+
page = await market.search(url, { ...queryOptions, offset: 0, fetchedAt: page.fetchedAt });
|
|
218
|
+
}
|
|
219
|
+
return page;
|
|
220
|
+
}));
|
|
221
|
+
const settled = await Promise.race([operation, stopped]);
|
|
222
|
+
if (signal.aborted)
|
|
223
|
+
return;
|
|
224
|
+
if (settled === null || readSignal.aborted) {
|
|
225
|
+
diagnostic = t('Loading cancelled. Refresh to try again.');
|
|
150
226
|
return;
|
|
151
227
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
228
|
+
settled.forEach((response, index) => {
|
|
229
|
+
const request = requests[index];
|
|
230
|
+
if (response.status === 'fulfilled') {
|
|
231
|
+
pages.set(request.url, response.value);
|
|
232
|
+
failures.delete(request.url);
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
failures.set(request.url, { offset: request.offset,
|
|
236
|
+
message: response.reason instanceof Error ? response.reason.message : String(response.reason) });
|
|
237
|
+
});
|
|
238
|
+
entries = [...pages.values()].flatMap(page => page.entries.map(entry => ({ page, entry })));
|
|
239
|
+
diagnostic = sources.length === 0 ? t('No plugin index sources are configured. Add one in Settings to browse a market.')
|
|
240
|
+
: [...sources.filter(source => source.error !== undefined).map(source => `${source.url} · ${source.error}`),
|
|
241
|
+
...[...failures].map(([url, failure]) => `${url} · ${failure.message}`)].join('\n');
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
progressEnd.abort();
|
|
246
|
+
await progress;
|
|
247
|
+
readSignal.removeEventListener('abort', stop);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const browse = createPromptBrowser(prompt);
|
|
251
|
+
await load();
|
|
252
|
+
while (!signal.aborted) {
|
|
253
|
+
const feedback = changeFeedback(result);
|
|
254
|
+
const total = [...pages.values()].reduce((sum, page) => sum + page.total, 0);
|
|
255
|
+
const count = pages.size === 0 ? '' : t('Showing {loaded} of {total} plugins').replace('{loaded}', String(entries.length)).replace('{total}', String(total));
|
|
256
|
+
const chosen = await browse({ localize: ['title', 'actions'], ...list,
|
|
257
|
+
title: 'Discover plugins', question: query,
|
|
258
|
+
detail: [count, diagnostic, feedback.detail].filter(Boolean).join('\n'), emptyText: t('No matching plugins.'),
|
|
259
|
+
options: entries.map(({ page, entry }) => {
|
|
260
|
+
const version = entry.versions.find(item => item.version === entry.defaultVersion) ?? entry.versions[0];
|
|
261
|
+
return {
|
|
262
|
+
label: entry.name, value: JSON.stringify([page.url, entry.name]),
|
|
263
|
+
description: [entry.defaultVersion ?? '', marketText(page.title),
|
|
264
|
+
entry.installation.status === 'installed' ? t('Installed') : '',
|
|
265
|
+
version?.compatibility.compatible === false ? t('Incompatible') : '',
|
|
266
|
+
].filter(Boolean).join(' · '),
|
|
267
|
+
...(entry.description === undefined ? {} : { preview: marketText(entry.description) }),
|
|
268
|
+
};
|
|
269
|
+
}),
|
|
270
|
+
actions: [
|
|
271
|
+
{ key: 'Alt+S', label: 'search', valuePrefix: 'search:', scope: 'list' },
|
|
272
|
+
{ key: 'Alt+R', label: 'refresh', valuePrefix: 'refresh:', scope: 'list' },
|
|
273
|
+
...([...pages.values()].some(page => page.nextOffset !== null) ? [{ key: 'Alt+N', label: 'load more', valuePrefix: 'more:', scope: 'list' }] : []),
|
|
274
|
+
...(failures.size > 0 ? [{ key: 'Alt+T', label: 'retry', valuePrefix: 'retry:', scope: 'list' }] : []),
|
|
275
|
+
...(feedback.actions ?? []),
|
|
276
|
+
], signal, }, query);
|
|
277
|
+
if (chosen === null || signal.aborted)
|
|
278
|
+
return;
|
|
279
|
+
if (chosen === 'result:') {
|
|
280
|
+
await readChangeDetails(ctx, result, signal);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (chosen === 'search:') {
|
|
284
|
+
const next = await searchPrompt(query);
|
|
285
|
+
if (next === null || signal.aborted)
|
|
175
286
|
continue;
|
|
287
|
+
if (next !== query) {
|
|
288
|
+
entries = [];
|
|
289
|
+
pages.clear();
|
|
290
|
+
failures.clear();
|
|
291
|
+
result = undefined;
|
|
176
292
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
293
|
+
query = next;
|
|
294
|
+
await load();
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (chosen === 'refresh:') {
|
|
298
|
+
await load('refresh');
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (chosen === 'more:') {
|
|
302
|
+
await load('more');
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (chosen === 'retry:') {
|
|
306
|
+
await load('retry');
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const picked = entries.find(({ page, entry }) => JSON.stringify([page.url, entry.name]) === chosen);
|
|
310
|
+
if (picked === undefined)
|
|
311
|
+
continue;
|
|
312
|
+
const version = picked.entry.versions.find(item => item.version === picked.entry.defaultVersion) ?? picked.entry.versions[0];
|
|
313
|
+
if (version === undefined) {
|
|
314
|
+
diagnostic = `${picked.entry.name} lists no version.`;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (version.compatibility.compatible === false) {
|
|
318
|
+
diagnostic = `${picked.entry.name} ${version.version} requires ${Object.entries(version.compatibility.requirements).map(([key, value]) => `${key} ${value}`).join(', ')}.`;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const change = await confirmInstall(ctx, version.install.spec, signal, true);
|
|
323
|
+
if (change !== undefined) {
|
|
324
|
+
result = change;
|
|
325
|
+
await load();
|
|
180
326
|
}
|
|
181
|
-
const result = await confirmInstall(ctx, version.install.spec, signal, true);
|
|
182
|
-
if (result !== undefined)
|
|
183
|
-
ctx.tui.commandOutput('/plugins', changeText(result));
|
|
184
|
-
return;
|
|
185
327
|
}
|
|
186
328
|
catch (error) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
329
|
+
diagnostic = error instanceof Error ? error.message : String(error);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function inspectPluginEntry(ctx, row, signal) {
|
|
334
|
+
const language = resolveUiLanguage(ctx.tui.prefs?.().uiLanguage);
|
|
335
|
+
const t = (text) => uiText(language, text);
|
|
336
|
+
const description = row.meta?.description;
|
|
337
|
+
const introduction = typeof description === 'string' ? description : description?.[language] ?? description?.en;
|
|
338
|
+
const reason = pluginReadOnly(row);
|
|
339
|
+
const text = [
|
|
340
|
+
`${t('Module')} ${row.moduleName}`,
|
|
341
|
+
`${t('Plugin entry')} ${row.patchId ?? row.entryId}`,
|
|
342
|
+
`${t('Status')} ${t(row.enabled ? 'Enabled' : 'Disabled')}`,
|
|
343
|
+
`${t('Load state')} ${row.fiberPhase ?? t('Not mounted')}`,
|
|
344
|
+
...(introduction === undefined ? [] : ['', introduction]),
|
|
345
|
+
...(reason === undefined ? [] : ['', `${t('Read-only')} ${reason === 'terminal-required' ? t('Required by the terminal.') : reason}`]),
|
|
346
|
+
...(row.diagnostic === undefined ? [] : ['', t('Diagnostic'), row.diagnostic]),
|
|
347
|
+
...(row.meta?.error === undefined ? [] : ['', t('Diagnostic'), row.meta.error]),
|
|
348
|
+
].join('\n');
|
|
349
|
+
let position;
|
|
350
|
+
while (!signal.aborted) {
|
|
351
|
+
const action = await ctx.tui.prompt({ localize: ['options'], title: row.moduleName, question: '',
|
|
352
|
+
presentation: 'document', documentSource: { text }, allowCustom: false, notify: false, signal,
|
|
353
|
+
...(position === undefined ? {} : { documentPosition: position }),
|
|
354
|
+
onDocumentPosition: value => { position = value; },
|
|
355
|
+
options: [{ label: 'Back', value: 'back' },
|
|
356
|
+
...(ctx.tui.copyText === undefined ? [] : [{ label: 'Copy details', value: 'copy' }]),
|
|
357
|
+
...(reason === undefined ? [{ label: row.enabled ? 'Disable' : 'Enable', value: 'toggle' }] : [])] });
|
|
358
|
+
if (signal.aborted || action === null || action === 'back')
|
|
190
359
|
return;
|
|
360
|
+
if (action === 'copy')
|
|
361
|
+
await ctx.tui.copyText?.(text, t('Plugin entry'));
|
|
362
|
+
if (action === 'toggle' && reason === undefined) {
|
|
363
|
+
const confirmed = await confirmPrompt(request => ctx.tui.prompt(request), { localize: ['title', 'options'],
|
|
364
|
+
title: 'Plugin entry', question: row.moduleName, acceptLabel: row.enabled ? 'Disable' : 'Enable', cancelLabel: null, signal });
|
|
365
|
+
if (confirmed && !signal.aborted) {
|
|
366
|
+
const current = (await ctx.pluginManager.listPlugins()).find(item => item.entryId === row.entryId);
|
|
367
|
+
if (signal.aborted || current === undefined || current.enabled !== row.enabled || pluginReadOnly(current) !== undefined)
|
|
368
|
+
return;
|
|
369
|
+
return ctx.pluginManager.setPluginEnabled(row.entryId, !row.enabled);
|
|
370
|
+
}
|
|
191
371
|
}
|
|
192
372
|
}
|
|
193
373
|
}
|
|
194
|
-
async function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (invocation.agent.status === 'running')
|
|
198
|
-
return { kind: 'error', text: 'Wait for the current turn to finish before managing plugins.' };
|
|
374
|
+
async function browseBundles(ctx, signal, configurationCtx) {
|
|
375
|
+
const browse = createPromptBrowser(request => ctx.tui.prompt(request));
|
|
376
|
+
let result;
|
|
199
377
|
while (!signal.aborted) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
options:
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (action === null)
|
|
209
|
-
break;
|
|
210
|
-
if (action === 'discover') {
|
|
211
|
-
await discover(ctx, signal);
|
|
378
|
+
const rows = await ctx.pluginManager.listBundles();
|
|
379
|
+
const selected = await browse({ localize: true, ...list, ...changeFeedback(result), title: 'Bundles', question: 'Choose a bundle',
|
|
380
|
+
options: rows.map(row => ({ label: row.name, value: row.name,
|
|
381
|
+
description: `${row.version ?? row.installTarget?.version ?? ''} · ${bundleStatus(row)}${row.error === undefined ? '' : ` · ${row.error.diagnostic ?? row.error.code}`}` })), signal });
|
|
382
|
+
if (selected === null || signal.aborted)
|
|
383
|
+
return;
|
|
384
|
+
if (selected === 'result:') {
|
|
385
|
+
await readChangeDetails(ctx, result, signal);
|
|
212
386
|
continue;
|
|
213
387
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (row === undefined)
|
|
222
|
-
continue;
|
|
223
|
-
const reason = pluginReadOnly(row);
|
|
224
|
-
if (reason !== undefined) {
|
|
225
|
-
ctx.tui.notice(`${row.moduleName}: read-only (${reason}).`);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const choice = await confirmPrompt(request => ctx.tui.prompt(request), {
|
|
229
|
-
title: 'Plugin entry',
|
|
230
|
-
question: row.moduleName,
|
|
231
|
-
acceptLabel: row.enabled ? 'Disable' : 'Enable',
|
|
232
|
-
cancelLabel: null,
|
|
233
|
-
signal,
|
|
234
|
-
});
|
|
235
|
-
if (choice && !signal.aborted)
|
|
236
|
-
result = await ctx.pluginManager.setPluginEnabled(row.entryId, !row.enabled);
|
|
237
|
-
}
|
|
238
|
-
else if (action === 'bundles') {
|
|
239
|
-
const rows = await ctx.pluginManager.listBundles();
|
|
240
|
-
const selected = await ctx.tui.prompt({ ...list, title: 'Bundles', question: 'Choose a bundle',
|
|
241
|
-
options: rows.map(row => ({ label: row.name, value: row.name,
|
|
242
|
-
description: `${row.version ?? row.installTarget?.version ?? ''} · ${bundleStatus(row)}${row.error === undefined ? '' : ` · ${row.error.diagnostic ?? row.error.code}`}` })), signal });
|
|
243
|
-
const row = rows.find(row => row.name === selected);
|
|
244
|
-
if (row === undefined)
|
|
245
|
-
continue;
|
|
246
|
-
if (row.readOnlyReason !== undefined) {
|
|
247
|
-
ctx.tui.notice(`${row.name}: read-only (${row.readOnlyReason}).`);
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
388
|
+
if (!rows.some(row => row.name === selected))
|
|
389
|
+
continue;
|
|
390
|
+
let choice = null;
|
|
391
|
+
while (!signal.aborted) {
|
|
392
|
+
const row = (await ctx.pluginManager.listBundles()).find(row => row.name === selected);
|
|
393
|
+
if (row === undefined || signal.aborted)
|
|
394
|
+
break;
|
|
250
395
|
const guide = externalAgentGuide(row.name);
|
|
251
396
|
const integration = nativeIntegrationId(row.name);
|
|
252
397
|
const ownership = row.availability === 'installation'
|
|
253
398
|
? `Included with omdsh. ${integration === undefined ? 'Use Enable or Disable to manage this bundle.' : 'Open Integration settings to enable or disable it.'} It updates with omdsh and cannot be uninstalled separately.`
|
|
254
399
|
: row.installed ? 'Installed in this profile. Uninstall unloads the bundle and removes its package from this profile.' : undefined;
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
400
|
+
const language = resolveUiLanguage(ctx.tui.prefs?.().uiLanguage);
|
|
401
|
+
const detail = [ownership, externalAgentSummary(row.name, language) ?? row.description].filter(Boolean).join('\n\n');
|
|
402
|
+
choice = await ctx.tui.prompt({ localize: true, title: row.name, question: 'Manage bundle', ...changeFeedback(result),
|
|
403
|
+
detail: [detail, row.readOnlyReason, changeFeedback(result).detail].filter(Boolean).join('\n\n'),
|
|
404
|
+
...(choice === null ? {} : { initialValue: choice }),
|
|
405
|
+
options: row.readOnlyReason === undefined ? [
|
|
258
406
|
...(guide === undefined || row.availability !== 'missing' && integration !== undefined ? [] : [{ label: 'Check profile status', value: 'check' }]),
|
|
259
407
|
...(integration === undefined || row.availability === 'missing' ? [] : [{ label: 'Integration settings', value: 'settings' }]),
|
|
260
408
|
...(row.installTarget === undefined || row.availability === 'installation' ? [] : [{ label: row.availability === 'missing' ? 'Install' : 'Reinstall', value: 'install' }]),
|
|
261
409
|
...(integration !== undefined || row.availability === 'missing' || row.error !== undefined && !row.enabled ? [] : [{ label: row.enabled ? 'Disable' : 'Enable', value: 'toggle' }]),
|
|
262
410
|
...(row.removable ? [{ label: row.installed ? 'Uninstall' : 'Remove from profile', value: 'remove' }] : []),
|
|
263
|
-
|
|
264
|
-
|
|
411
|
+
...(guide === undefined || row.availability !== 'missing' ? [] : [{ label: 'Usage notes', value: 'guide' }]),
|
|
412
|
+
] : [], allowCustom: false, notify: false, signal });
|
|
413
|
+
if (signal.aborted || choice === null)
|
|
265
414
|
break;
|
|
415
|
+
if (choice === 'result:') {
|
|
416
|
+
await readChangeDetails(ctx, result, signal);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
266
419
|
if (choice === 'check' && guide !== undefined)
|
|
267
420
|
await showIntegrationStatus(ctx, row.name, signal);
|
|
421
|
+
if (choice === 'guide' && guide !== undefined)
|
|
422
|
+
await readReference(ctx.tui, `${row.name} · ${uiText(language, 'Usage notes')}`, guide, signal);
|
|
268
423
|
if (choice === 'settings' && integration !== undefined)
|
|
269
|
-
await editSettingsEntry(ctx, integration, signal);
|
|
424
|
+
await editSettingsEntry(configurationCtx ?? ctx, integration, signal);
|
|
270
425
|
if (choice === 'install' && row.installTarget !== undefined)
|
|
271
|
-
result = await confirmInstall(ctx, row.installTarget.spec, signal, true);
|
|
426
|
+
result = await confirmInstall(ctx, row.installTarget.spec, signal, true) ?? result;
|
|
272
427
|
if (choice === 'toggle')
|
|
273
428
|
result = await ctx.pluginManager.setBundleEnabled(row.name, !row.enabled);
|
|
274
429
|
if (choice === 'remove' && row.removable) {
|
|
275
|
-
const consent = await confirmPrompt(request => ctx.tui.prompt(request), {
|
|
430
|
+
const consent = await confirmPrompt(request => ctx.tui.prompt(request), { localize: ['title', 'options', 'actions'],
|
|
276
431
|
title: row.installed ? 'Uninstall bundle' : 'Remove bundle from profile',
|
|
277
432
|
question: row.name,
|
|
278
433
|
detail: row.installed
|
|
@@ -286,22 +441,109 @@ async function manage(ctx, invocation, signal) {
|
|
|
286
441
|
result = await ctx.pluginManager.removeBundle(row.name);
|
|
287
442
|
}
|
|
288
443
|
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function manage(ctx, invocation, signal, configurationCtx) {
|
|
447
|
+
if (invocation.rawInput.trim() !== '')
|
|
448
|
+
return { kind: 'error', text: 'Usage: /plugins' };
|
|
449
|
+
if (invocation.agent.status === 'running')
|
|
450
|
+
return { kind: 'error', text: 'Wait for the current turn to finish before managing plugins.' };
|
|
451
|
+
const navigation = new AbortController();
|
|
452
|
+
signal = AbortSignal.any([signal, navigation.signal]);
|
|
453
|
+
const onSuperseded = () => navigation.abort();
|
|
454
|
+
const browse = createPromptBrowser(request => ctx.tui.prompt({ ...request, onSuperseded }));
|
|
455
|
+
let result;
|
|
456
|
+
while (!signal.aborted) {
|
|
457
|
+
const runtime = ctx.get('omdshSession');
|
|
458
|
+
const creator = runtime === undefined ? undefined : (await runtime.agentPresets()).find(preset => preset.id === 'cordis');
|
|
459
|
+
if (signal.aborted)
|
|
460
|
+
break;
|
|
461
|
+
const action = await browse({ localize: true, ...list, ...changeFeedback(result), title: 'Plugins', question: 'Manage this profile',
|
|
462
|
+
detail: [uiText(resolveUiLanguage(ctx.tui.prefs?.().uiLanguage), 'Changes affect every session using this profile. Package replacements may require a restart.'), changeFeedback(result).detail].filter(Boolean).join('\n\n'),
|
|
463
|
+
options: [
|
|
464
|
+
...(configurationCtx === undefined ? [] : [{ label: 'Configure plugins', value: 'configure', description: 'Shell, subagents, web providers and installed plugin settings.' }]),
|
|
465
|
+
...(runtime === undefined ? [] : [{ label: 'Let the agent create a plugin', value: 'creator', disabled: creator === undefined || creator.broken !== undefined, description: creator === undefined ? 'Creator is unavailable in this profile.' : creator.broken ?? 'Start Creator without changing your default Agent preset.' }]),
|
|
466
|
+
{ label: 'Plugin entries', value: 'entries' },
|
|
467
|
+
{ label: 'Bundles', value: 'bundles' },
|
|
468
|
+
{ label: 'Discover', value: 'discover', description: 'Browse the plugin indexes this profile configures and install an entry.' },
|
|
469
|
+
{ label: 'Install bundle', value: 'install' },
|
|
470
|
+
], signal });
|
|
471
|
+
if (action === null)
|
|
472
|
+
break;
|
|
473
|
+
if (action === 'result:') {
|
|
474
|
+
await readChangeDetails(ctx, result, signal);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (action === 'creator')
|
|
478
|
+
return startCreator(ctx, { ...invocation, signal });
|
|
479
|
+
if (action === 'configure') {
|
|
480
|
+
if (configurationCtx === undefined)
|
|
481
|
+
continue;
|
|
482
|
+
while (!signal.aborted) {
|
|
483
|
+
const entries = pluginSettingsEntries(configurationCtx).filter(entry => ['tools', 'agent', 'integrations', 'advanced'].includes(entry.section ?? 'advanced'));
|
|
484
|
+
const id = await browse({ localize: true, ...list, title: 'Plugin settings', question: 'Choose a plugin to configure',
|
|
485
|
+
options: entries.map(entry => ({ label: entry.label, value: entry.id, description: entry.summary ?? entry.description })), signal });
|
|
486
|
+
if (id === null || signal.aborted)
|
|
487
|
+
break;
|
|
488
|
+
if (entries.some(entry => entry.id === id))
|
|
489
|
+
await editSettingsEntry(configurationCtx, id, signal);
|
|
490
|
+
}
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (action === 'discover') {
|
|
494
|
+
await discover(ctx, signal, onSuperseded);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (action === 'entries') {
|
|
498
|
+
let entryResult;
|
|
499
|
+
while (!signal.aborted) {
|
|
500
|
+
const rows = await ctx.pluginManager.listPlugins();
|
|
501
|
+
if (signal.aborted)
|
|
502
|
+
break;
|
|
503
|
+
const id = await browse({ localize: true, ...list, ...changeFeedback(entryResult), title: 'Plugin entries', question: 'Choose an entry to inspect',
|
|
504
|
+
options: rows.map(row => ({ label: row.patchId ?? String(row.entryId), value: String(row.entryId),
|
|
505
|
+
description: `${row.enabled ? 'Enabled' : 'Disabled'} · ${row.fiberPhase ?? 'not mounted'} · ${row.moduleName}${pluginReadOnly(row) === undefined ? '' : ` · ${pluginReadOnly(row)}`}` })), signal });
|
|
506
|
+
if (id === 'result:') {
|
|
507
|
+
await readChangeDetails(ctx, entryResult, signal);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const row = rows.find(row => row.entryId === id);
|
|
511
|
+
if (row === undefined || signal.aborted)
|
|
512
|
+
break;
|
|
513
|
+
const change = await inspectPluginEntry(ctx, row, signal);
|
|
514
|
+
if (change !== undefined)
|
|
515
|
+
entryResult = change;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else if (action === 'bundles') {
|
|
519
|
+
await browseBundles(ctx, signal, configurationCtx);
|
|
520
|
+
}
|
|
289
521
|
else if (action === 'install') {
|
|
290
|
-
const spec = await ctx.tui.prompt(askPrompt({ title: 'Install bundle', question: 'Package name, version, Git URL, or absolute path', signal }));
|
|
522
|
+
const spec = await ctx.tui.prompt(askPrompt({ localize: ['title', 'question', 'actions'], title: 'Install bundle', question: 'Package name, version, Git URL, or absolute path', signal }));
|
|
291
523
|
if (spec === null || spec.trim() === '' || signal.aborted)
|
|
292
524
|
continue;
|
|
293
|
-
result = await confirmInstall(ctx, spec.trim(), signal);
|
|
525
|
+
result = await confirmInstall(ctx, spec.trim(), signal) ?? result;
|
|
294
526
|
}
|
|
295
|
-
if (result !== undefined)
|
|
296
|
-
ctx.tui.commandOutput('/plugins', changeText(result));
|
|
297
527
|
}
|
|
298
528
|
return { kind: 'success' };
|
|
299
529
|
}
|
|
300
530
|
export function apply(ctx) {
|
|
301
531
|
const closing = new AbortController();
|
|
532
|
+
const configurations = new Map();
|
|
533
|
+
// Configuration is optional. Each editor receives the services declared by its scope.
|
|
534
|
+
for (const services of [['settings'], ['settings', 'credentials']]) {
|
|
535
|
+
const key = services.join(',');
|
|
536
|
+
ctx.inject(services, configurationCtx => {
|
|
537
|
+
const lifetime = new AbortController();
|
|
538
|
+
configurations.set(key, { ctx: configurationCtx, signal: lifetime.signal });
|
|
539
|
+
configurationCtx.effect(() => () => { configurations.delete(key); lifetime.abort(); });
|
|
540
|
+
});
|
|
541
|
+
}
|
|
302
542
|
registerCommands(ctx, [{ name: 'plugins', description: 'Manage profile plugins and bundles', handler: async (invocation) => {
|
|
543
|
+
const configuration = configurations.get('settings,credentials') ?? configurations.get('settings');
|
|
544
|
+
const signal = AbortSignal.any([invocation.signal, closing.signal, ...(configuration === undefined ? [] : [configuration.signal])]);
|
|
303
545
|
try {
|
|
304
|
-
return await manage(ctx, invocation,
|
|
546
|
+
return await manage(ctx, invocation, signal, configuration?.ctx);
|
|
305
547
|
}
|
|
306
548
|
catch (error) {
|
|
307
549
|
return { kind: 'error', text: error instanceof Error ? error.message : String(error) };
|