iosm-cli 0.2.12 → 0.2.13
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 +29 -0
- package/README.md +5 -3
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +80 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/background-processes.d.ts +11 -0
- package/dist/core/background-processes.d.ts.map +1 -1
- package/dist/core/background-processes.js +115 -9
- package/dist/core/background-processes.js.map +1 -1
- package/dist/core/openrouter-model-catalog.d.ts +9 -0
- package/dist/core/openrouter-model-catalog.d.ts.map +1 -0
- package/dist/core/openrouter-model-catalog.js +139 -0
- package/dist/core/openrouter-model-catalog.js.map +1 -0
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +6 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +9 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -0
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/usage-cost.d.ts +4 -0
- package/dist/core/usage-cost.d.ts.map +1 -0
- package/dist/core/usage-cost.js +28 -0
- package/dist/core/usage-cost.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +7 -5
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +19 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +646 -37
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/cli-reference.md +10 -2
- package/docs/configuration.md +6 -3
- package/docs/interactive-mode.md +15 -2
- package/docs/sessions-traces-export.md +2 -2
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ import { MAX_ORCHESTRATION_AGENTS, MAX_ORCHESTRATION_PARALLEL, MAX_SUBAGENT_DELE
|
|
|
19
19
|
import { loadModelsDevProviderCatalog, } from "../../core/models-dev-provider-catalog.js";
|
|
20
20
|
import { ModelRegistry } from "../../core/model-registry.js";
|
|
21
21
|
import { MODELS_DEV_PROVIDERS } from "../../core/models-dev-providers.js";
|
|
22
|
+
import { loadOpenRouterProviderConfig } from "../../core/openrouter-model-catalog.js";
|
|
22
23
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
23
24
|
import { getMcpCommandHelp, parseMcpAddCommand, parseMcpTargetCommand, } from "../../core/mcp/index.js";
|
|
24
25
|
import { addMemoryEntry, getMemoryFilePath, readMemoryEntries, removeMemoryEntry, updateMemoryEntry, } from "../../core/memory.js";
|
|
@@ -28,12 +29,13 @@ import { buildProjectIndex, collectChangedFilesSince, ensureProjectIndex, loadPr
|
|
|
28
29
|
import { SingularService, } from "../../core/singular.js";
|
|
29
30
|
import { getDefaultSemanticSearchConfig, getSemanticConfigPath, getSemanticIndexDir, isLikelyEmbeddingModelId, listOllamaLocalModels, listOpenRouterEmbeddingModels, loadMergedSemanticConfig, readScopedSemanticConfig, SemanticConfigMissingError, SemanticIndexRequiredError, SemanticRebuildRequiredError, SemanticSearchRuntime, upsertScopedSemanticSearchConfig, } from "../../core/semantic/index.js";
|
|
30
31
|
import { DefaultResourceLoader } from "../../core/resource-loader.js";
|
|
32
|
+
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
31
33
|
import { createAgentSession } from "../../core/sdk.js";
|
|
32
34
|
import { createTeamRun, getTeamRun, listTeamRuns } from "../../core/agent-teams.js";
|
|
33
35
|
import { buildSwarmPlanFromSingular, buildSwarmPlanFromTask, runSwarmScheduler, SwarmStateStore, } from "../../core/swarm/index.js";
|
|
34
36
|
import { loadCustomSubagents, resolveCustomSubagentReference, } from "../../core/subagents.js";
|
|
35
37
|
import { getSubagentRun, listSubagentRuns } from "../../core/subagent-runs.js";
|
|
36
|
-
import { getBackgroundProcess, listBackgroundProcesses, readBackgroundProcessLogTail, stopBackgroundProcess, } from "../../core/background-processes.js";
|
|
38
|
+
import { getBackgroundProcess, listBackgroundProcesses, pruneBackgroundProcesses, readBackgroundProcessLogTail, stopBackgroundProcess, } from "../../core/background-processes.js";
|
|
37
39
|
import { SessionManager } from "../../core/session-manager.js";
|
|
38
40
|
import { SettingsManager } from "../../core/settings-manager.js";
|
|
39
41
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -1005,6 +1007,7 @@ export class InteractiveMode {
|
|
|
1005
1007
|
},
|
|
1006
1008
|
]));
|
|
1007
1009
|
this.modelsDevProviderCatalogRefreshPromise = undefined;
|
|
1010
|
+
this.openRouterModelCatalogRefreshPromise = undefined;
|
|
1008
1011
|
// Custom header from extension (undefined = use built-in header)
|
|
1009
1012
|
this.customHeader = undefined;
|
|
1010
1013
|
// Active selector shown in editor container (used to restore UI after temporary dialogs)
|
|
@@ -3326,6 +3329,11 @@ export class InteractiveMode {
|
|
|
3326
3329
|
await this.handleBackgroundProcessesSlashCommand(text);
|
|
3327
3330
|
return;
|
|
3328
3331
|
}
|
|
3332
|
+
if (text === "/extensions" || text.startsWith("/extensions ") || text === "/ext" || text.startsWith("/ext ")) {
|
|
3333
|
+
this.editor.setText("");
|
|
3334
|
+
await this.handleExtensionsSlashCommand(text);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3329
3337
|
if (text === "/team-runs" || text.startsWith("/team-runs ")) {
|
|
3330
3338
|
this.editor.setText("");
|
|
3331
3339
|
this.handleTeamRunsSlashCommand(text);
|
|
@@ -6506,6 +6514,35 @@ export class InteractiveMode {
|
|
|
6506
6514
|
return false;
|
|
6507
6515
|
}
|
|
6508
6516
|
}
|
|
6517
|
+
async hydrateOpenRouterModelsFromApi(options) {
|
|
6518
|
+
const forceRefresh = options?.forceRefresh === true;
|
|
6519
|
+
if (!forceRefresh && this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID))
|
|
6520
|
+
return true;
|
|
6521
|
+
if (forceRefresh && this.isProviderConfiguredInModelsJson(OPENROUTER_PROVIDER_ID)) {
|
|
6522
|
+
return this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID);
|
|
6523
|
+
}
|
|
6524
|
+
if (this.openRouterModelCatalogRefreshPromise) {
|
|
6525
|
+
return this.openRouterModelCatalogRefreshPromise;
|
|
6526
|
+
}
|
|
6527
|
+
this.openRouterModelCatalogRefreshPromise = (async () => {
|
|
6528
|
+
const apiKey = await this.session.modelRegistry.authStorage
|
|
6529
|
+
.getApiKey(OPENROUTER_PROVIDER_ID)
|
|
6530
|
+
.catch(() => undefined);
|
|
6531
|
+
const config = await loadOpenRouterProviderConfig({ apiKey });
|
|
6532
|
+
if (!config || !config.models || config.models.length === 0)
|
|
6533
|
+
return false;
|
|
6534
|
+
try {
|
|
6535
|
+
this.session.modelRegistry.registerProvider(OPENROUTER_PROVIDER_ID, config);
|
|
6536
|
+
return this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID);
|
|
6537
|
+
}
|
|
6538
|
+
catch {
|
|
6539
|
+
return false;
|
|
6540
|
+
}
|
|
6541
|
+
})().finally(() => {
|
|
6542
|
+
this.openRouterModelCatalogRefreshPromise = undefined;
|
|
6543
|
+
});
|
|
6544
|
+
return this.openRouterModelCatalogRefreshPromise;
|
|
6545
|
+
}
|
|
6509
6546
|
async hydrateProviderModelsFromModelsDev(providerId, options) {
|
|
6510
6547
|
const forceRefresh = options?.forceRefresh === true;
|
|
6511
6548
|
if (!forceRefresh && this.hasRegisteredProviderModels(providerId))
|
|
@@ -6513,6 +6550,11 @@ export class InteractiveMode {
|
|
|
6513
6550
|
if (forceRefresh && this.isProviderConfiguredInModelsJson(providerId)) {
|
|
6514
6551
|
return this.hasRegisteredProviderModels(providerId);
|
|
6515
6552
|
}
|
|
6553
|
+
if (providerId === OPENROUTER_PROVIDER_ID) {
|
|
6554
|
+
const hydratedFromOpenRouter = await this.hydrateOpenRouterModelsFromApi({ forceRefresh });
|
|
6555
|
+
if (hydratedFromOpenRouter)
|
|
6556
|
+
return true;
|
|
6557
|
+
}
|
|
6516
6558
|
if (!options?.skipCatalogRefresh) {
|
|
6517
6559
|
await this.refreshModelsDevProviderCatalog();
|
|
6518
6560
|
}
|
|
@@ -6531,8 +6573,11 @@ export class InteractiveMode {
|
|
|
6531
6573
|
}
|
|
6532
6574
|
}
|
|
6533
6575
|
async hydrateMissingProviderModelsForSavedAuth(options) {
|
|
6534
|
-
const savedProviders = this.session.modelRegistry.authStorage.list();
|
|
6535
|
-
if (
|
|
6576
|
+
const savedProviders = new Set(this.session.modelRegistry.authStorage.list());
|
|
6577
|
+
if (this.session.modelRegistry.authStorage.hasAuth(OPENROUTER_PROVIDER_ID)) {
|
|
6578
|
+
savedProviders.add(OPENROUTER_PROVIDER_ID);
|
|
6579
|
+
}
|
|
6580
|
+
if (savedProviders.size === 0)
|
|
6536
6581
|
return;
|
|
6537
6582
|
await this.refreshModelsDevProviderCatalog();
|
|
6538
6583
|
const forceRefresh = options?.forceRefresh === true;
|
|
@@ -12461,54 +12506,273 @@ export class InteractiveMode {
|
|
|
12461
12506
|
source: "interactive",
|
|
12462
12507
|
});
|
|
12463
12508
|
}
|
|
12509
|
+
getBackgroundCommandUsage() {
|
|
12510
|
+
return "/bg [list|running|done|error|terminated|unknown] [limit: 1..200] | /bg status [id] | /bg logs [id] [lines: 1..1000] | /bg stop [id] | /bg stop-all | /bg prune [hours: 1..2160]";
|
|
12511
|
+
}
|
|
12512
|
+
canUseInteractiveSelectors() {
|
|
12513
|
+
return !!this.ui && !!this.editorContainer;
|
|
12514
|
+
}
|
|
12515
|
+
getBackgroundStatusWeight(status) {
|
|
12516
|
+
switch (status) {
|
|
12517
|
+
case "running":
|
|
12518
|
+
return 0;
|
|
12519
|
+
case "unknown":
|
|
12520
|
+
return 1;
|
|
12521
|
+
case "error":
|
|
12522
|
+
return 2;
|
|
12523
|
+
case "done":
|
|
12524
|
+
return 3;
|
|
12525
|
+
case "terminated":
|
|
12526
|
+
return 4;
|
|
12527
|
+
default:
|
|
12528
|
+
return 5;
|
|
12529
|
+
}
|
|
12530
|
+
}
|
|
12531
|
+
formatBackgroundStatusLabel(status) {
|
|
12532
|
+
switch (status) {
|
|
12533
|
+
case "running":
|
|
12534
|
+
return "RUNNING";
|
|
12535
|
+
case "done":
|
|
12536
|
+
return "DONE";
|
|
12537
|
+
case "error":
|
|
12538
|
+
return "ERROR";
|
|
12539
|
+
case "terminated":
|
|
12540
|
+
return "TERMINATED";
|
|
12541
|
+
case "unknown":
|
|
12542
|
+
return "UNKNOWN";
|
|
12543
|
+
default:
|
|
12544
|
+
return "UNKNOWN";
|
|
12545
|
+
}
|
|
12546
|
+
}
|
|
12547
|
+
formatRelativeTime(timestamp) {
|
|
12548
|
+
if (!timestamp)
|
|
12549
|
+
return "-";
|
|
12550
|
+
const parsed = Date.parse(timestamp);
|
|
12551
|
+
if (!Number.isFinite(parsed))
|
|
12552
|
+
return timestamp;
|
|
12553
|
+
const diffMs = Date.now() - parsed;
|
|
12554
|
+
if (diffMs < 0)
|
|
12555
|
+
return "just now";
|
|
12556
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
12557
|
+
if (diffSec < 60)
|
|
12558
|
+
return `${Math.max(1, diffSec)}s ago`;
|
|
12559
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
12560
|
+
if (diffMin < 60)
|
|
12561
|
+
return `${diffMin}m ago`;
|
|
12562
|
+
const diffHours = Math.floor(diffMin / 60);
|
|
12563
|
+
if (diffHours < 24)
|
|
12564
|
+
return `${diffHours}h ago`;
|
|
12565
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
12566
|
+
return `${diffDays}d ago`;
|
|
12567
|
+
}
|
|
12568
|
+
formatDurationMs(startedAt, finishedAt) {
|
|
12569
|
+
if (!startedAt)
|
|
12570
|
+
return "-";
|
|
12571
|
+
const started = Date.parse(startedAt);
|
|
12572
|
+
if (!Number.isFinite(started))
|
|
12573
|
+
return "-";
|
|
12574
|
+
const end = finishedAt ? Date.parse(finishedAt) : Date.now();
|
|
12575
|
+
if (!Number.isFinite(end) || end < started)
|
|
12576
|
+
return "-";
|
|
12577
|
+
const diffSec = Math.max(0, Math.floor((end - started) / 1000));
|
|
12578
|
+
const hours = Math.floor(diffSec / 3600);
|
|
12579
|
+
const minutes = Math.floor((diffSec % 3600) / 60);
|
|
12580
|
+
const seconds = diffSec % 60;
|
|
12581
|
+
if (hours > 0)
|
|
12582
|
+
return `${hours}h ${minutes}m`;
|
|
12583
|
+
if (minutes > 0)
|
|
12584
|
+
return `${minutes}m ${seconds}s`;
|
|
12585
|
+
return `${seconds}s`;
|
|
12586
|
+
}
|
|
12587
|
+
sortBackgroundRecords(records) {
|
|
12588
|
+
return [...records].sort((left, right) => {
|
|
12589
|
+
const byStatus = this.getBackgroundStatusWeight(left.status) - this.getBackgroundStatusWeight(right.status);
|
|
12590
|
+
if (byStatus !== 0)
|
|
12591
|
+
return byStatus;
|
|
12592
|
+
return right.createdAt.localeCompare(left.createdAt);
|
|
12593
|
+
});
|
|
12594
|
+
}
|
|
12464
12595
|
formatBackgroundProcessOptionLabel(record, index) {
|
|
12465
|
-
const
|
|
12466
|
-
const
|
|
12467
|
-
const
|
|
12468
|
-
const
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12596
|
+
const statusLabel = this.formatBackgroundStatusLabel(record.status);
|
|
12597
|
+
const age = this.formatRelativeTime(record.createdAt);
|
|
12598
|
+
const runtime = this.formatDurationMs(record.startedAt, record.finishedAt);
|
|
12599
|
+
const source = record.source ?? "-";
|
|
12600
|
+
const commandPreview = record.command.length > 72 ? `${record.command.slice(0, 69)}...` : record.command;
|
|
12601
|
+
const stopFlag = record.requestedStopAt ? " · stop requested" : "";
|
|
12602
|
+
return `${index + 1}. [${statusLabel}] ${record.id} · pid=${record.pid} · age=${age} · runtime=${runtime} · source=${source}${stopFlag}\n ${commandPreview}`;
|
|
12603
|
+
}
|
|
12604
|
+
buildBackgroundProcessesReport(records) {
|
|
12605
|
+
const counts = {
|
|
12606
|
+
running: 0,
|
|
12607
|
+
done: 0,
|
|
12608
|
+
error: 0,
|
|
12609
|
+
terminated: 0,
|
|
12610
|
+
unknown: 0,
|
|
12611
|
+
};
|
|
12612
|
+
for (const record of records) {
|
|
12613
|
+
counts[record.status] += 1;
|
|
12614
|
+
}
|
|
12615
|
+
const header = `Summary: total=${records.length} · running=${counts.running} · done=${counts.done} · error=${counts.error} · terminated=${counts.terminated} · unknown=${counts.unknown}`;
|
|
12616
|
+
const items = records.map((record, index) => this.formatBackgroundProcessOptionLabel(record, index));
|
|
12617
|
+
const hints = [
|
|
12618
|
+
"Quick actions:",
|
|
12619
|
+
"- /bg status <id>",
|
|
12620
|
+
"- /bg logs <id> [lines]",
|
|
12621
|
+
"- /bg stop <id>",
|
|
12622
|
+
"- /bg stop-all",
|
|
12623
|
+
"- /bg prune [hours]",
|
|
12624
|
+
];
|
|
12625
|
+
return [header, "", ...items, "", ...hints].join("\n");
|
|
12626
|
+
}
|
|
12627
|
+
async runBackgroundMenu(cwd) {
|
|
12628
|
+
if (!this.canUseInteractiveSelectors())
|
|
12629
|
+
return false;
|
|
12630
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, 60));
|
|
12631
|
+
const runningCount = records.filter((record) => record.status === "running").length;
|
|
12632
|
+
const selected = await this.showExtensionSelector(`/bg menu · running=${runningCount} · total=${records.length}`, [
|
|
12633
|
+
"List all processes",
|
|
12634
|
+
"List running only",
|
|
12635
|
+
"Show process status",
|
|
12636
|
+
"Show process logs",
|
|
12637
|
+
"Stop process",
|
|
12638
|
+
"Stop all running processes",
|
|
12639
|
+
"Prune old completed records",
|
|
12640
|
+
"Help",
|
|
12641
|
+
"Cancel",
|
|
12642
|
+
]);
|
|
12643
|
+
if (!selected || selected === "Cancel") {
|
|
12644
|
+
this.showStatus("/bg menu cancelled.");
|
|
12645
|
+
return true;
|
|
12646
|
+
}
|
|
12647
|
+
if (selected === "List all processes") {
|
|
12648
|
+
await this.handleBackgroundProcessesSlashCommand("/bg list");
|
|
12649
|
+
return true;
|
|
12650
|
+
}
|
|
12651
|
+
if (selected === "List running only") {
|
|
12652
|
+
await this.handleBackgroundProcessesSlashCommand("/bg running");
|
|
12653
|
+
return true;
|
|
12654
|
+
}
|
|
12655
|
+
if (selected === "Show process status") {
|
|
12656
|
+
await this.handleBackgroundProcessesSlashCommand("/bg status");
|
|
12657
|
+
return true;
|
|
12658
|
+
}
|
|
12659
|
+
if (selected === "Show process logs") {
|
|
12660
|
+
await this.handleBackgroundProcessesSlashCommand("/bg logs");
|
|
12661
|
+
return true;
|
|
12662
|
+
}
|
|
12663
|
+
if (selected === "Stop process") {
|
|
12664
|
+
await this.handleBackgroundProcessesSlashCommand("/bg stop");
|
|
12665
|
+
return true;
|
|
12666
|
+
}
|
|
12667
|
+
if (selected === "Stop all running processes") {
|
|
12668
|
+
await this.handleBackgroundProcessesSlashCommand("/bg stop-all");
|
|
12669
|
+
return true;
|
|
12670
|
+
}
|
|
12671
|
+
if (selected === "Prune old completed records") {
|
|
12672
|
+
await this.handleBackgroundProcessesSlashCommand("/bg prune");
|
|
12673
|
+
return true;
|
|
12674
|
+
}
|
|
12675
|
+
if (selected === "Help") {
|
|
12676
|
+
await this.handleBackgroundProcessesSlashCommand("/bg help");
|
|
12677
|
+
return true;
|
|
12678
|
+
}
|
|
12679
|
+
return true;
|
|
12680
|
+
}
|
|
12681
|
+
async pickBackgroundProcessId(cwd, actionLabel, options) {
|
|
12682
|
+
const limit = options?.limit ?? 50;
|
|
12683
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, limit));
|
|
12473
12684
|
if (records.length === 0) {
|
|
12474
12685
|
this.showStatus("No background processes found.");
|
|
12475
12686
|
return undefined;
|
|
12476
12687
|
}
|
|
12477
|
-
const
|
|
12478
|
-
|
|
12688
|
+
const preferredStatuses = options?.preferredStatuses;
|
|
12689
|
+
let filtered = records;
|
|
12690
|
+
if (preferredStatuses && preferredStatuses.length > 0) {
|
|
12691
|
+
const allowed = new Set(preferredStatuses);
|
|
12692
|
+
const preferred = records.filter((record) => allowed.has(record.status));
|
|
12693
|
+
if (preferred.length > 0)
|
|
12694
|
+
filtered = preferred;
|
|
12695
|
+
}
|
|
12696
|
+
const pickerOptions = filtered.map((record, index) => this.formatBackgroundProcessOptionLabel(record, index));
|
|
12697
|
+
const selected = await this.showExtensionSelector(`/bg ${actionLabel}: select process`, pickerOptions);
|
|
12479
12698
|
if (!selected) {
|
|
12480
12699
|
this.showStatus(`/bg ${actionLabel} cancelled.`);
|
|
12481
12700
|
return undefined;
|
|
12482
12701
|
}
|
|
12483
|
-
const selectedIndex =
|
|
12484
|
-
return selectedIndex >= 0 ?
|
|
12702
|
+
const selectedIndex = pickerOptions.indexOf(selected);
|
|
12703
|
+
return selectedIndex >= 0 ? filtered[selectedIndex]?.id : undefined;
|
|
12704
|
+
}
|
|
12705
|
+
async stopAllBackgroundProcesses(cwd) {
|
|
12706
|
+
const runningRecords = this.sortBackgroundRecords(listBackgroundProcesses(cwd, 200)).filter((record) => record.status === "running");
|
|
12707
|
+
if (runningRecords.length === 0) {
|
|
12708
|
+
this.showStatus("No running background processes found.");
|
|
12709
|
+
return;
|
|
12710
|
+
}
|
|
12711
|
+
if (this.canUseInteractiveSelectors()) {
|
|
12712
|
+
const confirmed = await this.showExtensionConfirm("Stop all background processes?", `Send stop signal to ${runningRecords.length} running process(es).`);
|
|
12713
|
+
if (!confirmed) {
|
|
12714
|
+
this.showStatus("/bg stop-all cancelled.");
|
|
12715
|
+
return;
|
|
12716
|
+
}
|
|
12717
|
+
}
|
|
12718
|
+
const updatedRecords = [];
|
|
12719
|
+
for (const record of runningRecords) {
|
|
12720
|
+
const updated = stopBackgroundProcess(cwd, record.id);
|
|
12721
|
+
if (updated)
|
|
12722
|
+
updatedRecords.push(updated);
|
|
12723
|
+
}
|
|
12724
|
+
const stillRunning = updatedRecords.filter((record) => record.status === "running");
|
|
12725
|
+
const lines = [
|
|
12726
|
+
`Stop-all requested for ${runningRecords.length} process(es).`,
|
|
12727
|
+
`Still running: ${stillRunning.length}`,
|
|
12728
|
+
"",
|
|
12729
|
+
...updatedRecords.map((record, index) => `${index + 1}. ${record.id} · status=${record.status} · requested_stop=${record.requestedStopAt ?? "-"}`),
|
|
12730
|
+
];
|
|
12731
|
+
this.showCommandTextBlock("Background Stop-All", lines.join("\n"));
|
|
12485
12732
|
}
|
|
12486
12733
|
async handleBackgroundProcessesSlashCommand(text) {
|
|
12487
12734
|
const args = this.parseSlashArgs(text).slice(1);
|
|
12488
12735
|
const cwd = this.sessionManager.getCwd();
|
|
12736
|
+
const usage = this.getBackgroundCommandUsage();
|
|
12737
|
+
if (args.length === 0) {
|
|
12738
|
+
const handledByMenu = await this.runBackgroundMenu(cwd);
|
|
12739
|
+
if (handledByMenu)
|
|
12740
|
+
return;
|
|
12741
|
+
}
|
|
12489
12742
|
const firstArg = (args[0] ?? "").toLowerCase();
|
|
12490
12743
|
const subcommand = firstArg || "list";
|
|
12491
|
-
|
|
12492
|
-
|
|
12744
|
+
const listFilters = {
|
|
12745
|
+
list: undefined,
|
|
12746
|
+
running: ["running"],
|
|
12747
|
+
done: ["done"],
|
|
12748
|
+
error: ["error"],
|
|
12749
|
+
failed: ["error"],
|
|
12750
|
+
terminated: ["terminated"],
|
|
12751
|
+
unknown: ["unknown"],
|
|
12752
|
+
};
|
|
12753
|
+
const hasListFilter = Object.prototype.hasOwnProperty.call(listFilters, subcommand);
|
|
12754
|
+
if (hasListFilter || /^\d+$/.test(subcommand)) {
|
|
12755
|
+
const limitRaw = subcommand === "list" ? args[1] : hasListFilter ? args[1] : subcommand;
|
|
12493
12756
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : 20;
|
|
12494
12757
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
12495
|
-
this.showWarning(
|
|
12758
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12496
12759
|
return;
|
|
12497
12760
|
}
|
|
12498
|
-
const records = listBackgroundProcesses(cwd, limit);
|
|
12499
|
-
|
|
12500
|
-
|
|
12761
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, limit));
|
|
12762
|
+
const statusFilter = hasListFilter ? listFilters[subcommand] : undefined;
|
|
12763
|
+
const filtered = statusFilter && statusFilter.length > 0
|
|
12764
|
+
? records.filter((record) => statusFilter.includes(record.status))
|
|
12765
|
+
: records;
|
|
12766
|
+
if (filtered.length === 0) {
|
|
12767
|
+
if (statusFilter && statusFilter.length > 0) {
|
|
12768
|
+
this.showStatus(`No background processes found for "${subcommand}" filter.`);
|
|
12769
|
+
}
|
|
12770
|
+
else {
|
|
12771
|
+
this.showStatus("No background processes found.");
|
|
12772
|
+
}
|
|
12501
12773
|
return;
|
|
12502
12774
|
}
|
|
12503
|
-
|
|
12504
|
-
const status = `status=${record.status}`;
|
|
12505
|
-
const pid = `pid=${record.pid}`;
|
|
12506
|
-
const created = record.createdAt ? ` · ${record.createdAt}` : "";
|
|
12507
|
-
const finished = record.finishedAt ? ` · finished=${record.finishedAt}` : "";
|
|
12508
|
-
const exitCode = typeof record.exitCode === "number" ? ` · exit=${record.exitCode}` : "";
|
|
12509
|
-
return `${index + 1}. ${record.id} · ${status} · ${pid}${created}${finished}${exitCode}\n ${record.command}`;
|
|
12510
|
-
});
|
|
12511
|
-
this.showCommandTextBlock("Background Processes", lines.join("\n"));
|
|
12775
|
+
this.showCommandTextBlock("Background Processes", this.buildBackgroundProcessesReport(filtered));
|
|
12512
12776
|
return;
|
|
12513
12777
|
}
|
|
12514
12778
|
if (subcommand === "status") {
|
|
@@ -12525,10 +12789,11 @@ export class InteractiveMode {
|
|
|
12525
12789
|
}
|
|
12526
12790
|
const lines = [
|
|
12527
12791
|
`ID: ${record.id}`,
|
|
12528
|
-
`Status: ${record.status}`,
|
|
12792
|
+
`Status: ${this.formatBackgroundStatusLabel(record.status)}${record.requestedStopAt ? " (stop requested)" : ""}`,
|
|
12529
12793
|
`PID: ${record.pid}`,
|
|
12530
|
-
`Created: ${record.createdAt}`,
|
|
12794
|
+
`Created: ${record.createdAt} (${this.formatRelativeTime(record.createdAt)})`,
|
|
12531
12795
|
`Started: ${record.startedAt}`,
|
|
12796
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)}`,
|
|
12532
12797
|
`Finished: ${record.finishedAt ?? "-"}`,
|
|
12533
12798
|
`Exit code: ${typeof record.exitCode === "number" ? record.exitCode : "-"}`,
|
|
12534
12799
|
`Cwd: ${record.cwd}`,
|
|
@@ -12537,6 +12802,10 @@ export class InteractiveMode {
|
|
|
12537
12802
|
`Status file: ${record.metaPath}`,
|
|
12538
12803
|
`Log file: ${record.logPath}`,
|
|
12539
12804
|
"",
|
|
12805
|
+
"Quick actions:",
|
|
12806
|
+
`- /bg logs ${record.id} 120`,
|
|
12807
|
+
`- /bg stop ${record.id}`,
|
|
12808
|
+
"",
|
|
12540
12809
|
"Command:",
|
|
12541
12810
|
record.command,
|
|
12542
12811
|
];
|
|
@@ -12546,7 +12815,9 @@ export class InteractiveMode {
|
|
|
12546
12815
|
if (subcommand === "logs") {
|
|
12547
12816
|
let id = args[1];
|
|
12548
12817
|
if (!id) {
|
|
12549
|
-
id = await this.pickBackgroundProcessId(cwd, "logs"
|
|
12818
|
+
id = await this.pickBackgroundProcessId(cwd, "logs", {
|
|
12819
|
+
preferredStatuses: ["running", "unknown", "error", "done", "terminated"],
|
|
12820
|
+
});
|
|
12550
12821
|
}
|
|
12551
12822
|
if (!id)
|
|
12552
12823
|
return;
|
|
@@ -12567,30 +12838,368 @@ export class InteractiveMode {
|
|
|
12567
12838
|
return;
|
|
12568
12839
|
}
|
|
12569
12840
|
const body = tail.trim().length > 0 ? tail : "(no output yet)";
|
|
12570
|
-
this.showCommandTextBlock("Background Logs", [
|
|
12841
|
+
this.showCommandTextBlock("Background Logs", [
|
|
12842
|
+
`ID: ${record.id} · status=${this.formatBackgroundStatusLabel(record.status)} · tail=${tailLines} lines`,
|
|
12843
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)} · stop_requested=${record.requestedStopAt ? "yes" : "no"}`,
|
|
12844
|
+
`Command: ${record.command.length > 120 ? `${record.command.slice(0, 117)}...` : record.command}`,
|
|
12845
|
+
"",
|
|
12846
|
+
body,
|
|
12847
|
+
].join("\n"));
|
|
12571
12848
|
return;
|
|
12572
12849
|
}
|
|
12573
12850
|
if (subcommand === "stop" || subcommand === "kill" || subcommand === "cancel") {
|
|
12574
12851
|
let id = args[1];
|
|
12575
12852
|
if (!id) {
|
|
12576
|
-
id = await this.pickBackgroundProcessId(cwd, "stop"
|
|
12853
|
+
id = await this.pickBackgroundProcessId(cwd, "stop", {
|
|
12854
|
+
preferredStatuses: ["running", "unknown", "error", "done", "terminated"],
|
|
12855
|
+
});
|
|
12577
12856
|
}
|
|
12578
12857
|
if (!id)
|
|
12579
12858
|
return;
|
|
12859
|
+
const current = getBackgroundProcess(cwd, id);
|
|
12860
|
+
if (!current) {
|
|
12861
|
+
this.showWarning(`Background process not found: ${id}`);
|
|
12862
|
+
return;
|
|
12863
|
+
}
|
|
12864
|
+
if (current.status !== "running") {
|
|
12865
|
+
this.showStatus(`Background process ${current.id} is already ${current.status}.`);
|
|
12866
|
+
return;
|
|
12867
|
+
}
|
|
12868
|
+
if (this.canUseInteractiveSelectors()) {
|
|
12869
|
+
const confirmed = await this.showExtensionConfirm("Stop background process?", `${current.id}\n${current.command.length > 140 ? `${current.command.slice(0, 137)}...` : current.command}`);
|
|
12870
|
+
if (!confirmed) {
|
|
12871
|
+
this.showStatus(`/bg stop cancelled for ${current.id}.`);
|
|
12872
|
+
return;
|
|
12873
|
+
}
|
|
12874
|
+
}
|
|
12580
12875
|
const record = stopBackgroundProcess(cwd, id);
|
|
12581
12876
|
if (!record) {
|
|
12582
12877
|
this.showWarning(`Background process not found: ${id}`);
|
|
12583
12878
|
return;
|
|
12584
12879
|
}
|
|
12585
12880
|
if (record.status === "running") {
|
|
12586
|
-
this.showWarning(`Stop
|
|
12881
|
+
this.showWarning(`Stop requested for ${record.id}, waiting for graceful shutdown. Use /bg status ${record.id} to monitor.`);
|
|
12587
12882
|
}
|
|
12588
12883
|
else {
|
|
12589
12884
|
this.showStatus(`Background process ${record.id} stopped (${record.status}).`);
|
|
12590
12885
|
}
|
|
12591
12886
|
return;
|
|
12592
12887
|
}
|
|
12593
|
-
|
|
12888
|
+
if (subcommand === "stop-all" || subcommand === "kill-all" || subcommand === "cancel-all") {
|
|
12889
|
+
await this.stopAllBackgroundProcesses(cwd);
|
|
12890
|
+
return;
|
|
12891
|
+
}
|
|
12892
|
+
if (subcommand === "prune" || subcommand === "cleanup") {
|
|
12893
|
+
const hoursRaw = args[1];
|
|
12894
|
+
const hours = hoursRaw ? Number.parseInt(hoursRaw, 10) : 168;
|
|
12895
|
+
if (!Number.isInteger(hours) || hours < 1 || hours > 2160) {
|
|
12896
|
+
this.showWarning("Usage: /bg prune [hours: 1..2160]");
|
|
12897
|
+
return;
|
|
12898
|
+
}
|
|
12899
|
+
const result = pruneBackgroundProcesses(cwd, { maxAgeHours: hours });
|
|
12900
|
+
const preview = result.removedIds.length > 0 ? `\n${result.removedIds.slice(0, 10).map((id) => `- ${id}`).join("\n")}` : "";
|
|
12901
|
+
const moreSuffix = result.removedIds.length > 10 ? `\n- ... (+${result.removedIds.length - 10} more)` : "";
|
|
12902
|
+
this.showCommandTextBlock("Background Prune", [
|
|
12903
|
+
`Threshold: older than ${result.thresholdHours}h`,
|
|
12904
|
+
`Removed: ${result.removed}`,
|
|
12905
|
+
`Skipped running: ${result.skippedRunning}`,
|
|
12906
|
+
`Skipped recent: ${result.skippedRecent}`,
|
|
12907
|
+
preview ? "" : undefined,
|
|
12908
|
+
preview || undefined,
|
|
12909
|
+
moreSuffix || undefined,
|
|
12910
|
+
]
|
|
12911
|
+
.filter((line) => typeof line === "string")
|
|
12912
|
+
.join("\n"));
|
|
12913
|
+
return;
|
|
12914
|
+
}
|
|
12915
|
+
if (subcommand === "help") {
|
|
12916
|
+
this.showCommandTextBlock("Background Help", [
|
|
12917
|
+
"Background command usage:",
|
|
12918
|
+
usage,
|
|
12919
|
+
"",
|
|
12920
|
+
"Examples:",
|
|
12921
|
+
"- ! npm run dev &",
|
|
12922
|
+
"- /bg",
|
|
12923
|
+
"- /bg running",
|
|
12924
|
+
"- /bg status bg_123",
|
|
12925
|
+
"- /bg logs bg_123 200",
|
|
12926
|
+
"- /bg stop bg_123",
|
|
12927
|
+
"- /bg stop-all",
|
|
12928
|
+
"- /bg prune 72",
|
|
12929
|
+
].join("\n"));
|
|
12930
|
+
return;
|
|
12931
|
+
}
|
|
12932
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12933
|
+
}
|
|
12934
|
+
getExtensionsCommandUsage() {
|
|
12935
|
+
return "/extensions (/ext) [list|install <source>|update [source]|remove <source>|enable <target>|disable <target>|help] [--global]";
|
|
12936
|
+
}
|
|
12937
|
+
createInteractivePackageManager(cwd) {
|
|
12938
|
+
return new DefaultPackageManager({
|
|
12939
|
+
cwd,
|
|
12940
|
+
agentDir: getAgentDir(),
|
|
12941
|
+
settingsManager: this.settingsManager,
|
|
12942
|
+
});
|
|
12943
|
+
}
|
|
12944
|
+
normalizeExtensionOverrideTarget(target) {
|
|
12945
|
+
const trimmed = target.trim();
|
|
12946
|
+
if (!trimmed)
|
|
12947
|
+
return "";
|
|
12948
|
+
if (path.isAbsolute(trimmed)) {
|
|
12949
|
+
return path.resolve(trimmed);
|
|
12950
|
+
}
|
|
12951
|
+
let normalized = trimmed.replace(/\\/g, "/");
|
|
12952
|
+
if (normalized.startsWith("./"))
|
|
12953
|
+
normalized = normalized.slice(2);
|
|
12954
|
+
if (!normalized.startsWith("extensions/")) {
|
|
12955
|
+
normalized = `extensions/${normalized}`;
|
|
12956
|
+
}
|
|
12957
|
+
return normalized;
|
|
12958
|
+
}
|
|
12959
|
+
setExtensionOverride(scope, target, enabled) {
|
|
12960
|
+
const normalizedTarget = this.normalizeExtensionOverrideTarget(target);
|
|
12961
|
+
if (!normalizedTarget)
|
|
12962
|
+
return { updated: false, token: undefined };
|
|
12963
|
+
const current = scope === "project"
|
|
12964
|
+
? [...(this.settingsManager.getProjectSettings().extensions ?? [])]
|
|
12965
|
+
: [...(this.settingsManager.getGlobalSettings().extensions ?? [])];
|
|
12966
|
+
const normalizeEntryTarget = (entry) => {
|
|
12967
|
+
const trimmed = entry.trim();
|
|
12968
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
12969
|
+
return this.normalizeExtensionOverrideTarget(trimmed.slice(1));
|
|
12970
|
+
}
|
|
12971
|
+
return "";
|
|
12972
|
+
};
|
|
12973
|
+
const filtered = current.filter((entry) => normalizeEntryTarget(entry) !== normalizedTarget);
|
|
12974
|
+
const token = `${enabled ? "+" : "-"}${normalizedTarget}`;
|
|
12975
|
+
const next = [...filtered, token];
|
|
12976
|
+
if (JSON.stringify(next) === JSON.stringify(current)) {
|
|
12977
|
+
return { updated: false, token };
|
|
12978
|
+
}
|
|
12979
|
+
if (scope === "project") {
|
|
12980
|
+
this.settingsManager.setProjectExtensionPaths(next);
|
|
12981
|
+
}
|
|
12982
|
+
else {
|
|
12983
|
+
this.settingsManager.setExtensionPaths(next);
|
|
12984
|
+
}
|
|
12985
|
+
return { updated: true, token };
|
|
12986
|
+
}
|
|
12987
|
+
togglePackageExtensionSource(scope, source, enabled) {
|
|
12988
|
+
const settings = scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
|
12989
|
+
const packages = [...(settings.packages ?? [])];
|
|
12990
|
+
const index = packages.findIndex((pkg) => (typeof pkg === "string" ? pkg : pkg.source) === source);
|
|
12991
|
+
if (index < 0) {
|
|
12992
|
+
return { matched: false, changed: false, message: "" };
|
|
12993
|
+
}
|
|
12994
|
+
const entry = packages[index];
|
|
12995
|
+
let nextEntry = entry;
|
|
12996
|
+
if (!enabled) {
|
|
12997
|
+
if (typeof entry === "string") {
|
|
12998
|
+
nextEntry = { source: entry, extensions: [] };
|
|
12999
|
+
}
|
|
13000
|
+
else if (Array.isArray(entry.extensions) && entry.extensions.length === 0) {
|
|
13001
|
+
return { matched: true, changed: false, message: `Extension source already disabled: ${source}` };
|
|
13002
|
+
}
|
|
13003
|
+
else {
|
|
13004
|
+
nextEntry = { ...entry, extensions: [] };
|
|
13005
|
+
}
|
|
13006
|
+
}
|
|
13007
|
+
else if (typeof entry === "string") {
|
|
13008
|
+
return { matched: true, changed: false, message: `Extension source already enabled: ${source}` };
|
|
13009
|
+
}
|
|
13010
|
+
else if (!Array.isArray(entry.extensions) || entry.extensions.length > 0) {
|
|
13011
|
+
return { matched: true, changed: false, message: `Extension source already enabled: ${source}` };
|
|
13012
|
+
}
|
|
13013
|
+
else {
|
|
13014
|
+
const { extensions: _extensions, ...rest } = entry;
|
|
13015
|
+
nextEntry = Object.keys(rest).length === 1 ? rest.source : rest;
|
|
13016
|
+
}
|
|
13017
|
+
packages[index] = nextEntry;
|
|
13018
|
+
if (scope === "project") {
|
|
13019
|
+
this.settingsManager.setProjectPackages(packages);
|
|
13020
|
+
}
|
|
13021
|
+
else {
|
|
13022
|
+
this.settingsManager.setPackages(packages);
|
|
13023
|
+
}
|
|
13024
|
+
return {
|
|
13025
|
+
matched: true,
|
|
13026
|
+
changed: true,
|
|
13027
|
+
message: `${enabled ? "Enabled" : "Disabled"} extension source (${scope}): ${source}`,
|
|
13028
|
+
};
|
|
13029
|
+
}
|
|
13030
|
+
async showExtensionsList(scopeFilter) {
|
|
13031
|
+
const cwd = this.sessionManager.getCwd();
|
|
13032
|
+
const packageManager = this.createInteractivePackageManager(cwd);
|
|
13033
|
+
const resolved = await packageManager.resolve();
|
|
13034
|
+
const packageLines = [];
|
|
13035
|
+
const projectPackages = this.settingsManager.getProjectSettings().packages ?? [];
|
|
13036
|
+
const globalPackages = this.settingsManager.getGlobalSettings().packages ?? [];
|
|
13037
|
+
const includeProject = !scopeFilter || scopeFilter === "project";
|
|
13038
|
+
const includeGlobal = !scopeFilter || scopeFilter === "user";
|
|
13039
|
+
if (includeProject) {
|
|
13040
|
+
packageLines.push("Project package sources:");
|
|
13041
|
+
if (projectPackages.length === 0) {
|
|
13042
|
+
packageLines.push(" (none)");
|
|
13043
|
+
}
|
|
13044
|
+
else {
|
|
13045
|
+
for (const pkg of projectPackages) {
|
|
13046
|
+
const source = typeof pkg === "string" ? pkg : pkg.source;
|
|
13047
|
+
const status = typeof pkg === "object" && Array.isArray(pkg.extensions) && pkg.extensions.length === 0
|
|
13048
|
+
? "disabled"
|
|
13049
|
+
: "enabled";
|
|
13050
|
+
packageLines.push(` - [${status}] ${source}`);
|
|
13051
|
+
}
|
|
13052
|
+
}
|
|
13053
|
+
}
|
|
13054
|
+
if (includeGlobal) {
|
|
13055
|
+
if (packageLines.length > 0)
|
|
13056
|
+
packageLines.push("");
|
|
13057
|
+
packageLines.push("User package sources:");
|
|
13058
|
+
if (globalPackages.length === 0) {
|
|
13059
|
+
packageLines.push(" (none)");
|
|
13060
|
+
}
|
|
13061
|
+
else {
|
|
13062
|
+
for (const pkg of globalPackages) {
|
|
13063
|
+
const source = typeof pkg === "string" ? pkg : pkg.source;
|
|
13064
|
+
const status = typeof pkg === "object" && Array.isArray(pkg.extensions) && pkg.extensions.length === 0
|
|
13065
|
+
? "disabled"
|
|
13066
|
+
: "enabled";
|
|
13067
|
+
packageLines.push(` - [${status}] ${source}`);
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13070
|
+
}
|
|
13071
|
+
const extensions = resolved.extensions
|
|
13072
|
+
.filter((resource) => !scopeFilter || resource.metadata.scope === scopeFilter)
|
|
13073
|
+
.sort((a, b) => {
|
|
13074
|
+
if (a.metadata.scope !== b.metadata.scope)
|
|
13075
|
+
return a.metadata.scope.localeCompare(b.metadata.scope, "en");
|
|
13076
|
+
return a.path.localeCompare(b.path, "en");
|
|
13077
|
+
});
|
|
13078
|
+
const enabledCount = extensions.filter((resource) => resource.enabled).length;
|
|
13079
|
+
const disabledCount = extensions.length - enabledCount;
|
|
13080
|
+
const extensionLines = extensions.length === 0
|
|
13081
|
+
? [" (none)"]
|
|
13082
|
+
: extensions.map((resource) => {
|
|
13083
|
+
const relative = path.relative(cwd, resource.path);
|
|
13084
|
+
const displayPath = !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : resource.path;
|
|
13085
|
+
const status = resource.enabled ? "enabled" : "disabled";
|
|
13086
|
+
return ` - [${status}] ${displayPath} · scope=${resource.metadata.scope} · source=${resource.metadata.source}`;
|
|
13087
|
+
});
|
|
13088
|
+
const lines = [
|
|
13089
|
+
`Extension resources: total=${extensions.length} · enabled=${enabledCount} · disabled=${disabledCount}`,
|
|
13090
|
+
"",
|
|
13091
|
+
...extensionLines,
|
|
13092
|
+
"",
|
|
13093
|
+
...packageLines,
|
|
13094
|
+
"",
|
|
13095
|
+
"Commands:",
|
|
13096
|
+
"- /extensions install <source> [--global]",
|
|
13097
|
+
"- /extensions update [source]",
|
|
13098
|
+
"- /extensions remove <source> [--global]",
|
|
13099
|
+
"- /extensions disable <source-or-path> [--global]",
|
|
13100
|
+
"- /extensions enable <source-or-path> [--global]",
|
|
13101
|
+
];
|
|
13102
|
+
this.showCommandTextBlock("Extensions", lines.join("\n"));
|
|
13103
|
+
}
|
|
13104
|
+
async handleExtensionsSlashCommand(text) {
|
|
13105
|
+
const args = this.parseSlashArgs(text).slice(1);
|
|
13106
|
+
const usage = this.getExtensionsCommandUsage();
|
|
13107
|
+
const filteredArgs = args.filter((arg) => arg !== "/extensions" && arg !== "/ext");
|
|
13108
|
+
let scope = "project";
|
|
13109
|
+
const normalizedArgs = [];
|
|
13110
|
+
for (const arg of filteredArgs) {
|
|
13111
|
+
if (arg === "--global" || arg === "-g") {
|
|
13112
|
+
scope = "user";
|
|
13113
|
+
continue;
|
|
13114
|
+
}
|
|
13115
|
+
if (arg === "--project") {
|
|
13116
|
+
scope = "project";
|
|
13117
|
+
continue;
|
|
13118
|
+
}
|
|
13119
|
+
normalizedArgs.push(arg);
|
|
13120
|
+
}
|
|
13121
|
+
const subcommand = (normalizedArgs[0] ?? "list").toLowerCase();
|
|
13122
|
+
const subArgs = normalizedArgs.slice(1);
|
|
13123
|
+
const cwd = this.sessionManager.getCwd();
|
|
13124
|
+
const packageManager = this.createInteractivePackageManager(cwd);
|
|
13125
|
+
if (subcommand === "help") {
|
|
13126
|
+
this.showCommandTextBlock("Extensions Help", [
|
|
13127
|
+
"Extension lifecycle usage:",
|
|
13128
|
+
usage,
|
|
13129
|
+
"",
|
|
13130
|
+
"Examples:",
|
|
13131
|
+
"- /extensions list",
|
|
13132
|
+
"- /extensions list --global",
|
|
13133
|
+
"- /extensions install npm:@org/iosm-extension",
|
|
13134
|
+
"- /extensions install ./local-extension --global",
|
|
13135
|
+
"- /extensions update",
|
|
13136
|
+
"- /extensions remove npm:@org/iosm-extension",
|
|
13137
|
+
"- /extensions disable extensions/my-tool.ts",
|
|
13138
|
+
"- /ext enable npm:@org/iosm-extension --global",
|
|
13139
|
+
].join("\n"));
|
|
13140
|
+
return;
|
|
13141
|
+
}
|
|
13142
|
+
if (subcommand === "list") {
|
|
13143
|
+
await this.showExtensionsList(scope);
|
|
13144
|
+
return;
|
|
13145
|
+
}
|
|
13146
|
+
if (subcommand === "install") {
|
|
13147
|
+
const source = subArgs[0];
|
|
13148
|
+
if (!source) {
|
|
13149
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13150
|
+
return;
|
|
13151
|
+
}
|
|
13152
|
+
await packageManager.install(source, { local: scope === "project" });
|
|
13153
|
+
packageManager.addSourceToSettings(source, { local: scope === "project" });
|
|
13154
|
+
await this.reloadAfterMemoryMutation(`Installed extension source (${scope}): ${source}`, "Extension source installed but reload failed");
|
|
13155
|
+
return;
|
|
13156
|
+
}
|
|
13157
|
+
if (subcommand === "update") {
|
|
13158
|
+
const source = subArgs[0];
|
|
13159
|
+
await packageManager.update(source);
|
|
13160
|
+
await this.reloadAfterMemoryMutation(source ? `Updated extension source: ${source}` : "Updated extension sources", "Extension sources updated but reload failed");
|
|
13161
|
+
return;
|
|
13162
|
+
}
|
|
13163
|
+
if (subcommand === "remove" || subcommand === "uninstall") {
|
|
13164
|
+
const source = subArgs[0];
|
|
13165
|
+
if (!source) {
|
|
13166
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13167
|
+
return;
|
|
13168
|
+
}
|
|
13169
|
+
await packageManager.remove(source, { local: scope === "project" });
|
|
13170
|
+
const removed = packageManager.removeSourceFromSettings(source, { local: scope === "project" });
|
|
13171
|
+
if (!removed) {
|
|
13172
|
+
this.showWarning(`No matching extension source found in ${scope} scope: ${source}`);
|
|
13173
|
+
return;
|
|
13174
|
+
}
|
|
13175
|
+
await this.reloadAfterMemoryMutation(`Removed extension source (${scope}): ${source}`, "Extension source removed but reload failed");
|
|
13176
|
+
return;
|
|
13177
|
+
}
|
|
13178
|
+
if (subcommand === "enable" || subcommand === "disable") {
|
|
13179
|
+
const target = subArgs[0];
|
|
13180
|
+
if (!target) {
|
|
13181
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13182
|
+
return;
|
|
13183
|
+
}
|
|
13184
|
+
const desiredEnabled = subcommand === "enable";
|
|
13185
|
+
const packageToggle = this.togglePackageExtensionSource(scope, target, desiredEnabled);
|
|
13186
|
+
if (packageToggle.matched) {
|
|
13187
|
+
if (!packageToggle.changed) {
|
|
13188
|
+
this.showStatus(packageToggle.message);
|
|
13189
|
+
return;
|
|
13190
|
+
}
|
|
13191
|
+
await this.reloadAfterMemoryMutation(packageToggle.message, "Extension source toggle applied but reload failed");
|
|
13192
|
+
return;
|
|
13193
|
+
}
|
|
13194
|
+
const override = this.setExtensionOverride(scope, target, desiredEnabled);
|
|
13195
|
+
if (!override.updated) {
|
|
13196
|
+
this.showStatus(`No extension override changes were needed for: ${target}`);
|
|
13197
|
+
return;
|
|
13198
|
+
}
|
|
13199
|
+
await this.reloadAfterMemoryMutation(`${desiredEnabled ? "Enabled" : "Disabled"} extension path (${scope}) via override: ${override.token}`, "Extension override applied but reload failed");
|
|
13200
|
+
return;
|
|
13201
|
+
}
|
|
13202
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12594
13203
|
}
|
|
12595
13204
|
handleTeamRunsSlashCommand(text) {
|
|
12596
13205
|
const args = this.parseSlashArgs(text).slice(1);
|