iosm-cli 0.2.12 → 0.2.14
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/.npmignore +2 -0
- package/CHANGELOG.md +60 -0
- package/README.md +16 -4
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -2
- package/dist/cli/args.js.map +1 -1
- 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/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +17 -2
- package/dist/core/auth-storage.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/command-dispatcher.d.ts +16 -0
- package/dist/core/command-dispatcher.d.ts.map +1 -0
- package/dist/core/command-dispatcher.js +678 -0
- package/dist/core/command-dispatcher.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +13 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -2
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -2
- package/dist/core/model-resolver.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/provider-policy.d.ts +7 -0
- package/dist/core/provider-policy.d.ts.map +1 -0
- package/dist/core/provider-policy.js +19 -0
- package/dist/core/provider-policy.js.map +1 -0
- package/dist/core/settings-manager.d.ts +27 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +38 -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 +13 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-background-runs.d.ts +56 -0
- package/dist/core/subagent-background-runs.d.ts.map +1 -0
- package/dist/core/subagent-background-runs.js +275 -0
- package/dist/core/subagent-background-runs.js.map +1 -0
- 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/tools/task.d.ts.map +1 -1
- package/dist/core/tools/task.js +39 -35
- package/dist/core/tools/task.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/main.d.ts.map +1 -1
- package/dist/main.js +16 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- 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/components/login-dialog.d.ts +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +1 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +1 -2
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +26 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +899 -47
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +11 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +54 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +87 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +69 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/telegram/telegram-bridge-mode.d.ts +15 -0
- package/dist/modes/telegram/telegram-bridge-mode.d.ts.map +1 -0
- package/dist/modes/telegram/telegram-bridge-mode.js +2135 -0
- package/dist/modes/telegram/telegram-bridge-mode.js.map +1 -0
- package/docs/cli-reference.md +20 -3
- package/docs/configuration.md +27 -3
- package/docs/interactive-mode.md +15 -2
- package/docs/rpc-json-sdk.md +23 -0
- package/docs/sessions-traces-export.md +2 -2
- package/examples/extensions/README.md +1 -2
- package/package.json +4 -3
- package/examples/extensions/antigravity-image-gen.ts +0 -415
|
@@ -19,6 +19,8 @@ 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";
|
|
23
|
+
import { isProviderAllowed } from "../../core/provider-policy.js";
|
|
22
24
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
23
25
|
import { getMcpCommandHelp, parseMcpAddCommand, parseMcpTargetCommand, } from "../../core/mcp/index.js";
|
|
24
26
|
import { addMemoryEntry, getMemoryFilePath, readMemoryEntries, removeMemoryEntry, updateMemoryEntry, } from "../../core/memory.js";
|
|
@@ -28,12 +30,14 @@ import { buildProjectIndex, collectChangedFilesSince, ensureProjectIndex, loadPr
|
|
|
28
30
|
import { SingularService, } from "../../core/singular.js";
|
|
29
31
|
import { getDefaultSemanticSearchConfig, getSemanticConfigPath, getSemanticIndexDir, isLikelyEmbeddingModelId, listOllamaLocalModels, listOpenRouterEmbeddingModels, loadMergedSemanticConfig, readScopedSemanticConfig, SemanticConfigMissingError, SemanticIndexRequiredError, SemanticRebuildRequiredError, SemanticSearchRuntime, upsertScopedSemanticSearchConfig, } from "../../core/semantic/index.js";
|
|
30
32
|
import { DefaultResourceLoader } from "../../core/resource-loader.js";
|
|
33
|
+
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
31
34
|
import { createAgentSession } from "../../core/sdk.js";
|
|
32
35
|
import { createTeamRun, getTeamRun, listTeamRuns } from "../../core/agent-teams.js";
|
|
33
36
|
import { buildSwarmPlanFromSingular, buildSwarmPlanFromTask, runSwarmScheduler, SwarmStateStore, } from "../../core/swarm/index.js";
|
|
34
37
|
import { loadCustomSubagents, resolveCustomSubagentReference, } from "../../core/subagents.js";
|
|
35
38
|
import { getSubagentRun, listSubagentRuns } from "../../core/subagent-runs.js";
|
|
36
|
-
import { getBackgroundProcess, listBackgroundProcesses, readBackgroundProcessLogTail, stopBackgroundProcess, } from "../../core/background-processes.js";
|
|
39
|
+
import { getBackgroundProcess, listBackgroundProcesses, pruneBackgroundProcesses, readBackgroundProcessLogTail, stopBackgroundProcess, } from "../../core/background-processes.js";
|
|
40
|
+
import { getSubagentBackgroundRun, listSubagentBackgroundRuns, pruneSubagentBackgroundRuns, readSubagentBackgroundRunLogTail, requestStopAllSubagentBackgroundRuns, requestStopSubagentBackgroundRun, } from "../../core/subagent-background-runs.js";
|
|
37
41
|
import { SessionManager } from "../../core/session-manager.js";
|
|
38
42
|
import { SettingsManager } from "../../core/settings-manager.js";
|
|
39
43
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -798,7 +802,6 @@ function resolveDoctorCliToolStatuses() {
|
|
|
798
802
|
const OPENROUTER_PROVIDER_ID = "openrouter";
|
|
799
803
|
const PROVIDER_DISPLAY_NAME_OVERRIDES = {
|
|
800
804
|
"azure-openai-responses": "Azure OpenAI Responses",
|
|
801
|
-
"google-antigravity": "Google Antigravity",
|
|
802
805
|
"google-gemini-cli": "Google Gemini CLI",
|
|
803
806
|
"kimi-coding": "Kimi Coding",
|
|
804
807
|
"openai-codex": "OpenAI Codex",
|
|
@@ -996,8 +999,8 @@ export class InteractiveMode {
|
|
|
996
999
|
this.asciiLogo = undefined;
|
|
997
1000
|
// API-key provider labels cached for /login and status messages.
|
|
998
1001
|
this.apiKeyProviderDisplayNames = new Map();
|
|
999
|
-
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS;
|
|
1000
|
-
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.map((provider) => [
|
|
1002
|
+
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS.filter((provider) => isProviderAllowed(provider.id));
|
|
1003
|
+
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.filter((provider) => isProviderAllowed(provider.id)).map((provider) => [
|
|
1001
1004
|
provider.id,
|
|
1002
1005
|
{
|
|
1003
1006
|
...provider,
|
|
@@ -1005,6 +1008,7 @@ export class InteractiveMode {
|
|
|
1005
1008
|
},
|
|
1006
1009
|
]));
|
|
1007
1010
|
this.modelsDevProviderCatalogRefreshPromise = undefined;
|
|
1011
|
+
this.openRouterModelCatalogRefreshPromise = undefined;
|
|
1008
1012
|
// Custom header from extension (undefined = use built-in header)
|
|
1009
1013
|
this.customHeader = undefined;
|
|
1010
1014
|
// Active selector shown in editor container (used to restore UI after temporary dialogs)
|
|
@@ -3326,6 +3330,11 @@ export class InteractiveMode {
|
|
|
3326
3330
|
await this.handleBackgroundProcessesSlashCommand(text);
|
|
3327
3331
|
return;
|
|
3328
3332
|
}
|
|
3333
|
+
if (text === "/extensions" || text.startsWith("/extensions ") || text === "/ext" || text.startsWith("/ext ")) {
|
|
3334
|
+
this.editor.setText("");
|
|
3335
|
+
await this.handleExtensionsSlashCommand(text);
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3329
3338
|
if (text === "/team-runs" || text.startsWith("/team-runs ")) {
|
|
3330
3339
|
this.editor.setText("");
|
|
3331
3340
|
this.handleTeamRunsSlashCommand(text);
|
|
@@ -6423,8 +6432,8 @@ export class InteractiveMode {
|
|
|
6423
6432
|
}
|
|
6424
6433
|
this.modelsDevProviderCatalogRefreshPromise = (async () => {
|
|
6425
6434
|
const catalog = await loadModelsDevProviderCatalog();
|
|
6426
|
-
this.modelsDevProviderCatalogById = catalog;
|
|
6427
|
-
this.modelsDevProviderCatalog = Array.from(
|
|
6435
|
+
this.modelsDevProviderCatalogById = new Map(Array.from(catalog.entries()).filter(([providerId]) => isProviderAllowed(providerId)));
|
|
6436
|
+
this.modelsDevProviderCatalog = Array.from(this.modelsDevProviderCatalogById.values())
|
|
6428
6437
|
.map((provider) => ({
|
|
6429
6438
|
id: provider.id,
|
|
6430
6439
|
name: provider.name,
|
|
@@ -6433,8 +6442,8 @@ export class InteractiveMode {
|
|
|
6433
6442
|
.sort((a, b) => a.name.localeCompare(b.name, "en") || a.id.localeCompare(b.id, "en"));
|
|
6434
6443
|
})()
|
|
6435
6444
|
.catch(() => {
|
|
6436
|
-
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS;
|
|
6437
|
-
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.map((provider) => [
|
|
6445
|
+
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS.filter((provider) => isProviderAllowed(provider.id));
|
|
6446
|
+
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.filter((provider) => isProviderAllowed(provider.id)).map((provider) => [
|
|
6438
6447
|
provider.id,
|
|
6439
6448
|
{
|
|
6440
6449
|
...provider,
|
|
@@ -6506,6 +6515,35 @@ export class InteractiveMode {
|
|
|
6506
6515
|
return false;
|
|
6507
6516
|
}
|
|
6508
6517
|
}
|
|
6518
|
+
async hydrateOpenRouterModelsFromApi(options) {
|
|
6519
|
+
const forceRefresh = options?.forceRefresh === true;
|
|
6520
|
+
if (!forceRefresh && this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID))
|
|
6521
|
+
return true;
|
|
6522
|
+
if (forceRefresh && this.isProviderConfiguredInModelsJson(OPENROUTER_PROVIDER_ID)) {
|
|
6523
|
+
return this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID);
|
|
6524
|
+
}
|
|
6525
|
+
if (this.openRouterModelCatalogRefreshPromise) {
|
|
6526
|
+
return this.openRouterModelCatalogRefreshPromise;
|
|
6527
|
+
}
|
|
6528
|
+
this.openRouterModelCatalogRefreshPromise = (async () => {
|
|
6529
|
+
const apiKey = await this.session.modelRegistry.authStorage
|
|
6530
|
+
.getApiKey(OPENROUTER_PROVIDER_ID)
|
|
6531
|
+
.catch(() => undefined);
|
|
6532
|
+
const config = await loadOpenRouterProviderConfig({ apiKey });
|
|
6533
|
+
if (!config || !config.models || config.models.length === 0)
|
|
6534
|
+
return false;
|
|
6535
|
+
try {
|
|
6536
|
+
this.session.modelRegistry.registerProvider(OPENROUTER_PROVIDER_ID, config);
|
|
6537
|
+
return this.hasRegisteredProviderModels(OPENROUTER_PROVIDER_ID);
|
|
6538
|
+
}
|
|
6539
|
+
catch {
|
|
6540
|
+
return false;
|
|
6541
|
+
}
|
|
6542
|
+
})().finally(() => {
|
|
6543
|
+
this.openRouterModelCatalogRefreshPromise = undefined;
|
|
6544
|
+
});
|
|
6545
|
+
return this.openRouterModelCatalogRefreshPromise;
|
|
6546
|
+
}
|
|
6509
6547
|
async hydrateProviderModelsFromModelsDev(providerId, options) {
|
|
6510
6548
|
const forceRefresh = options?.forceRefresh === true;
|
|
6511
6549
|
if (!forceRefresh && this.hasRegisteredProviderModels(providerId))
|
|
@@ -6513,6 +6551,11 @@ export class InteractiveMode {
|
|
|
6513
6551
|
if (forceRefresh && this.isProviderConfiguredInModelsJson(providerId)) {
|
|
6514
6552
|
return this.hasRegisteredProviderModels(providerId);
|
|
6515
6553
|
}
|
|
6554
|
+
if (providerId === OPENROUTER_PROVIDER_ID) {
|
|
6555
|
+
const hydratedFromOpenRouter = await this.hydrateOpenRouterModelsFromApi({ forceRefresh });
|
|
6556
|
+
if (hydratedFromOpenRouter)
|
|
6557
|
+
return true;
|
|
6558
|
+
}
|
|
6516
6559
|
if (!options?.skipCatalogRefresh) {
|
|
6517
6560
|
await this.refreshModelsDevProviderCatalog();
|
|
6518
6561
|
}
|
|
@@ -6531,8 +6574,11 @@ export class InteractiveMode {
|
|
|
6531
6574
|
}
|
|
6532
6575
|
}
|
|
6533
6576
|
async hydrateMissingProviderModelsForSavedAuth(options) {
|
|
6534
|
-
const savedProviders = this.session.modelRegistry.authStorage.list();
|
|
6535
|
-
if (
|
|
6577
|
+
const savedProviders = new Set(this.session.modelRegistry.authStorage.list());
|
|
6578
|
+
if (this.session.modelRegistry.authStorage.hasAuth(OPENROUTER_PROVIDER_ID)) {
|
|
6579
|
+
savedProviders.add(OPENROUTER_PROVIDER_ID);
|
|
6580
|
+
}
|
|
6581
|
+
if (savedProviders.size === 0)
|
|
6536
6582
|
return;
|
|
6537
6583
|
await this.refreshModelsDevProviderCatalog();
|
|
6538
6584
|
const forceRefresh = options?.forceRefresh === true;
|
|
@@ -6568,11 +6614,15 @@ export class InteractiveMode {
|
|
|
6568
6614
|
const providerNames = new Map();
|
|
6569
6615
|
this.apiKeyProviderDisplayNames.clear();
|
|
6570
6616
|
for (const model of this.session.modelRegistry.getAll()) {
|
|
6617
|
+
if (!isProviderAllowed(model.provider))
|
|
6618
|
+
continue;
|
|
6571
6619
|
if (!providerNames.has(model.provider)) {
|
|
6572
6620
|
providerNames.set(model.provider, toProviderDisplayName(model.provider));
|
|
6573
6621
|
}
|
|
6574
6622
|
}
|
|
6575
6623
|
for (const provider of modelsDevProviders) {
|
|
6624
|
+
if (!isProviderAllowed(provider.id))
|
|
6625
|
+
continue;
|
|
6576
6626
|
const fallbackName = toProviderDisplayName(provider.id);
|
|
6577
6627
|
const current = providerNames.get(provider.id);
|
|
6578
6628
|
if (!current || current === fallbackName) {
|
|
@@ -6580,6 +6630,8 @@ export class InteractiveMode {
|
|
|
6580
6630
|
}
|
|
6581
6631
|
}
|
|
6582
6632
|
for (const providerId of this.session.modelRegistry.authStorage.list()) {
|
|
6633
|
+
if (!isProviderAllowed(providerId))
|
|
6634
|
+
continue;
|
|
6583
6635
|
if (!providerNames.has(providerId)) {
|
|
6584
6636
|
providerNames.set(providerId, toProviderDisplayName(providerId));
|
|
6585
6637
|
}
|
|
@@ -6656,7 +6708,7 @@ export class InteractiveMode {
|
|
|
6656
6708
|
// Providers that use callback servers (can paste redirect URL)
|
|
6657
6709
|
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
6658
6710
|
// Create login dialog component
|
|
6659
|
-
const dialog = new LoginDialogComponent(this.ui,
|
|
6711
|
+
const dialog = new LoginDialogComponent(this.ui, providerName, (_success, _message) => {
|
|
6660
6712
|
// Completion handled below
|
|
6661
6713
|
});
|
|
6662
6714
|
// Show dialog in editor container
|
|
@@ -12372,15 +12424,251 @@ export class InteractiveMode {
|
|
|
12372
12424
|
this.ui.requestRender();
|
|
12373
12425
|
}
|
|
12374
12426
|
}
|
|
12427
|
+
getSubagentBackgroundUsage() {
|
|
12428
|
+
return "/subagent-runs bg [list|running|queued|done|error|cancelled] [limit: 1..200] | /subagent-runs bg status <id> | /subagent-runs bg logs <id> [lines: 1..1000] | /subagent-runs bg stop <id> | /subagent-runs bg stop-all | /subagent-runs bg prune [hours: 1..2160]";
|
|
12429
|
+
}
|
|
12430
|
+
getSubagentBackgroundStatusWeight(status) {
|
|
12431
|
+
switch (status) {
|
|
12432
|
+
case "running":
|
|
12433
|
+
return 0;
|
|
12434
|
+
case "queued":
|
|
12435
|
+
return 1;
|
|
12436
|
+
case "error":
|
|
12437
|
+
return 2;
|
|
12438
|
+
case "cancelled":
|
|
12439
|
+
return 3;
|
|
12440
|
+
case "done":
|
|
12441
|
+
return 4;
|
|
12442
|
+
default:
|
|
12443
|
+
return 5;
|
|
12444
|
+
}
|
|
12445
|
+
}
|
|
12446
|
+
formatSubagentBackgroundStatusLabel(status) {
|
|
12447
|
+
switch (status) {
|
|
12448
|
+
case "running":
|
|
12449
|
+
return "RUNNING";
|
|
12450
|
+
case "queued":
|
|
12451
|
+
return "QUEUED";
|
|
12452
|
+
case "done":
|
|
12453
|
+
return "DONE";
|
|
12454
|
+
case "error":
|
|
12455
|
+
return "ERROR";
|
|
12456
|
+
case "cancelled":
|
|
12457
|
+
return "CANCELLED";
|
|
12458
|
+
default:
|
|
12459
|
+
return "UNKNOWN";
|
|
12460
|
+
}
|
|
12461
|
+
}
|
|
12462
|
+
sortSubagentBackgroundRecords(records) {
|
|
12463
|
+
return [...records].sort((left, right) => {
|
|
12464
|
+
const byStatus = this.getSubagentBackgroundStatusWeight(left.status) - this.getSubagentBackgroundStatusWeight(right.status);
|
|
12465
|
+
if (byStatus !== 0)
|
|
12466
|
+
return byStatus;
|
|
12467
|
+
return right.createdAt.localeCompare(left.createdAt);
|
|
12468
|
+
});
|
|
12469
|
+
}
|
|
12470
|
+
formatSubagentBackgroundOptionLabel(record, index) {
|
|
12471
|
+
const statusLabel = this.formatSubagentBackgroundStatusLabel(record.status);
|
|
12472
|
+
const age = this.formatRelativeTime(record.createdAt);
|
|
12473
|
+
const runtime = this.formatDurationMs(record.startedAt, record.finishedAt);
|
|
12474
|
+
const profile = record.profile || "-";
|
|
12475
|
+
const agent = record.agent?.trim() ? record.agent : "-";
|
|
12476
|
+
const stopFlag = record.requestedStopAt ? " · stop requested" : "";
|
|
12477
|
+
const description = record.description.length > 80 ? `${record.description.slice(0, 77)}...` : record.description;
|
|
12478
|
+
return `${index + 1}. [${statusLabel}] ${record.runId} · profile=${profile} · agent=${agent} · age=${age} · runtime=${runtime}${stopFlag}\n ${description}`;
|
|
12479
|
+
}
|
|
12480
|
+
buildSubagentBackgroundReport(records) {
|
|
12481
|
+
const counts = {
|
|
12482
|
+
queued: 0,
|
|
12483
|
+
running: 0,
|
|
12484
|
+
done: 0,
|
|
12485
|
+
error: 0,
|
|
12486
|
+
cancelled: 0,
|
|
12487
|
+
};
|
|
12488
|
+
for (const record of records) {
|
|
12489
|
+
counts[record.status] += 1;
|
|
12490
|
+
}
|
|
12491
|
+
const header = `Summary: total=${records.length} · queued=${counts.queued} · running=${counts.running} · done=${counts.done} · error=${counts.error} · cancelled=${counts.cancelled}`;
|
|
12492
|
+
const items = records.map((record, index) => this.formatSubagentBackgroundOptionLabel(record, index));
|
|
12493
|
+
const hints = [
|
|
12494
|
+
"Quick actions:",
|
|
12495
|
+
"- /subagent-runs bg status <id>",
|
|
12496
|
+
"- /subagent-runs bg logs <id> [lines]",
|
|
12497
|
+
"- /subagent-runs bg stop <id>",
|
|
12498
|
+
"- /subagent-runs bg stop-all",
|
|
12499
|
+
"- /subagent-runs bg prune [hours]",
|
|
12500
|
+
];
|
|
12501
|
+
return [header, "", ...items, "", ...hints].join("\n");
|
|
12502
|
+
}
|
|
12503
|
+
handleSubagentBackgroundRunsSlashCommand(args, cwd) {
|
|
12504
|
+
const usage = this.getSubagentBackgroundUsage();
|
|
12505
|
+
const firstArg = (args[0] ?? "").toLowerCase();
|
|
12506
|
+
const subcommand = firstArg || "list";
|
|
12507
|
+
const listFilters = {
|
|
12508
|
+
list: undefined,
|
|
12509
|
+
running: ["running"],
|
|
12510
|
+
queued: ["queued"],
|
|
12511
|
+
done: ["done"],
|
|
12512
|
+
error: ["error"],
|
|
12513
|
+
failed: ["error"],
|
|
12514
|
+
cancelled: ["cancelled"],
|
|
12515
|
+
};
|
|
12516
|
+
const hasListFilter = Object.prototype.hasOwnProperty.call(listFilters, subcommand);
|
|
12517
|
+
if (hasListFilter || /^\d+$/.test(subcommand)) {
|
|
12518
|
+
const limitRaw = subcommand === "list" ? args[1] : hasListFilter ? args[1] : subcommand;
|
|
12519
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : 20;
|
|
12520
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
12521
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12522
|
+
return;
|
|
12523
|
+
}
|
|
12524
|
+
const records = this.sortSubagentBackgroundRecords(listSubagentBackgroundRuns(cwd, limit));
|
|
12525
|
+
const statusFilter = hasListFilter ? listFilters[subcommand] : undefined;
|
|
12526
|
+
const filtered = statusFilter && statusFilter.length > 0
|
|
12527
|
+
? records.filter((record) => statusFilter.includes(record.status))
|
|
12528
|
+
: records;
|
|
12529
|
+
if (filtered.length === 0) {
|
|
12530
|
+
if (statusFilter && statusFilter.length > 0) {
|
|
12531
|
+
this.showStatus(`No background subagent runs found for "${subcommand}" filter.`);
|
|
12532
|
+
}
|
|
12533
|
+
else {
|
|
12534
|
+
this.showStatus("No background subagent runs found.");
|
|
12535
|
+
}
|
|
12536
|
+
return;
|
|
12537
|
+
}
|
|
12538
|
+
this.showCommandTextBlock("Subagent Background Runs", this.buildSubagentBackgroundReport(filtered));
|
|
12539
|
+
return;
|
|
12540
|
+
}
|
|
12541
|
+
if (subcommand === "status") {
|
|
12542
|
+
const runId = args[1];
|
|
12543
|
+
if (!runId) {
|
|
12544
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12545
|
+
return;
|
|
12546
|
+
}
|
|
12547
|
+
const record = getSubagentBackgroundRun(cwd, runId);
|
|
12548
|
+
if (!record) {
|
|
12549
|
+
this.showWarning(`Background subagent run not found: ${runId}`);
|
|
12550
|
+
return;
|
|
12551
|
+
}
|
|
12552
|
+
const lines = [
|
|
12553
|
+
`Run: ${record.runId}`,
|
|
12554
|
+
`Status: ${this.formatSubagentBackgroundStatusLabel(record.status)}${record.requestedStopAt ? " (stop requested)" : ""}`,
|
|
12555
|
+
`Created: ${record.createdAt} (${this.formatRelativeTime(record.createdAt)})`,
|
|
12556
|
+
`Started: ${record.startedAt ?? "-"}`,
|
|
12557
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)}`,
|
|
12558
|
+
`Finished: ${record.finishedAt ?? "-"}`,
|
|
12559
|
+
`Profile: ${record.profile}`,
|
|
12560
|
+
`Agent: ${record.agent ?? "-"}`,
|
|
12561
|
+
`Model: ${record.model ?? "-"}`,
|
|
12562
|
+
`Cwd: ${record.cwd}`,
|
|
12563
|
+
`Requested stop: ${record.requestedStopAt ?? "-"}`,
|
|
12564
|
+
`Status file: ${record.metaPath}`,
|
|
12565
|
+
`Log file: ${record.logPath}`,
|
|
12566
|
+
`Transcript: ${record.transcriptPath ?? "-"}`,
|
|
12567
|
+
record.error ? `Error: ${record.error}` : "",
|
|
12568
|
+
"",
|
|
12569
|
+
"Quick actions:",
|
|
12570
|
+
`- /subagent-runs bg logs ${record.runId} 120`,
|
|
12571
|
+
`- /subagent-runs bg stop ${record.runId}`,
|
|
12572
|
+
].filter((line) => line.length > 0);
|
|
12573
|
+
this.showCommandTextBlock("Subagent Background Status", lines.join("\n"));
|
|
12574
|
+
return;
|
|
12575
|
+
}
|
|
12576
|
+
if (subcommand === "logs") {
|
|
12577
|
+
const runId = args[1];
|
|
12578
|
+
if (!runId) {
|
|
12579
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12580
|
+
return;
|
|
12581
|
+
}
|
|
12582
|
+
const linesRaw = args[2];
|
|
12583
|
+
const tailLines = linesRaw ? Number.parseInt(linesRaw, 10) : 120;
|
|
12584
|
+
if (!Number.isInteger(tailLines) || tailLines < 1 || tailLines > 1000) {
|
|
12585
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12586
|
+
return;
|
|
12587
|
+
}
|
|
12588
|
+
const record = getSubagentBackgroundRun(cwd, runId);
|
|
12589
|
+
if (!record) {
|
|
12590
|
+
this.showWarning(`Background subagent run not found: ${runId}`);
|
|
12591
|
+
return;
|
|
12592
|
+
}
|
|
12593
|
+
const tail = readSubagentBackgroundRunLogTail(cwd, runId, tailLines);
|
|
12594
|
+
if (tail === undefined) {
|
|
12595
|
+
this.showWarning(`Background subagent run not found: ${runId}`);
|
|
12596
|
+
return;
|
|
12597
|
+
}
|
|
12598
|
+
const body = tail.trim().length > 0 ? tail : "(no output yet)";
|
|
12599
|
+
this.showCommandTextBlock("Subagent Background Logs", [
|
|
12600
|
+
`Run: ${record.runId} · status=${this.formatSubagentBackgroundStatusLabel(record.status)} · tail=${tailLines} lines`,
|
|
12601
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)} · stop_requested=${record.requestedStopAt ? "yes" : "no"}`,
|
|
12602
|
+
`Description: ${record.description}`,
|
|
12603
|
+
"",
|
|
12604
|
+
body,
|
|
12605
|
+
].join("\n"));
|
|
12606
|
+
return;
|
|
12607
|
+
}
|
|
12608
|
+
if (subcommand === "stop" || subcommand === "cancel" || subcommand === "kill") {
|
|
12609
|
+
const runId = args[1];
|
|
12610
|
+
if (!runId) {
|
|
12611
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12612
|
+
return;
|
|
12613
|
+
}
|
|
12614
|
+
const updated = requestStopSubagentBackgroundRun(cwd, runId);
|
|
12615
|
+
if (!updated) {
|
|
12616
|
+
this.showWarning(`Background subagent run not found: ${runId}`);
|
|
12617
|
+
return;
|
|
12618
|
+
}
|
|
12619
|
+
this.showStatus(`Stop requested for subagent run ${updated.runId} (status=${this.formatSubagentBackgroundStatusLabel(updated.status)}).`);
|
|
12620
|
+
return;
|
|
12621
|
+
}
|
|
12622
|
+
if (subcommand === "stop-all") {
|
|
12623
|
+
const result = requestStopAllSubagentBackgroundRuns(cwd);
|
|
12624
|
+
if (result.requested === 0) {
|
|
12625
|
+
this.showStatus("No running or queued background subagent runs found.");
|
|
12626
|
+
return;
|
|
12627
|
+
}
|
|
12628
|
+
const lines = [
|
|
12629
|
+
`Stop-all requested for ${result.requested} run(s).`,
|
|
12630
|
+
"",
|
|
12631
|
+
...result.requestedIds.map((id, index) => `${index + 1}. ${id}`),
|
|
12632
|
+
];
|
|
12633
|
+
this.showCommandTextBlock("Subagent Background Stop-All", lines.join("\n"));
|
|
12634
|
+
return;
|
|
12635
|
+
}
|
|
12636
|
+
if (subcommand === "prune") {
|
|
12637
|
+
const hoursRaw = args[1];
|
|
12638
|
+
const hours = hoursRaw ? Number.parseInt(hoursRaw, 10) : 24;
|
|
12639
|
+
if (!Number.isInteger(hours) || hours < 1 || hours > 2160) {
|
|
12640
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12641
|
+
return;
|
|
12642
|
+
}
|
|
12643
|
+
const result = pruneSubagentBackgroundRuns(cwd, hours);
|
|
12644
|
+
const lines = [
|
|
12645
|
+
`Threshold: ${result.thresholdHours}h`,
|
|
12646
|
+
`Removed: ${result.removed}`,
|
|
12647
|
+
`Skipped running/queued: ${result.skippedRunning}`,
|
|
12648
|
+
`Skipped recent: ${result.skippedRecent}`,
|
|
12649
|
+
];
|
|
12650
|
+
if (result.removedIds.length > 0) {
|
|
12651
|
+
lines.push("", ...result.removedIds.map((id, index) => `${index + 1}. ${id}`));
|
|
12652
|
+
}
|
|
12653
|
+
this.showCommandTextBlock("Subagent Background Prune", lines.join("\n"));
|
|
12654
|
+
return;
|
|
12655
|
+
}
|
|
12656
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12657
|
+
}
|
|
12375
12658
|
handleSubagentRunsSlashCommand(text) {
|
|
12376
12659
|
const args = this.parseSlashArgs(text).slice(1);
|
|
12660
|
+
const cwd = this.sessionManager.getCwd();
|
|
12661
|
+
const firstArg = (args[0] ?? "").toLowerCase();
|
|
12662
|
+
if (firstArg === "bg" || firstArg === "background") {
|
|
12663
|
+
this.handleSubagentBackgroundRunsSlashCommand(args.slice(1), cwd);
|
|
12664
|
+
return;
|
|
12665
|
+
}
|
|
12377
12666
|
const limitRaw = args[0];
|
|
12378
12667
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : 20;
|
|
12379
12668
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
12380
|
-
this.showWarning("Usage: /subagent-runs [limit: 1..200]");
|
|
12669
|
+
this.showWarning("Usage: /subagent-runs [limit: 1..200] | /subagent-runs bg ...");
|
|
12381
12670
|
return;
|
|
12382
12671
|
}
|
|
12383
|
-
const cwd = this.sessionManager.getCwd();
|
|
12384
12672
|
const runs = listSubagentRuns(cwd, limit);
|
|
12385
12673
|
if (runs.length === 0) {
|
|
12386
12674
|
this.showStatus("No subagent runs found.");
|
|
@@ -12461,54 +12749,273 @@ export class InteractiveMode {
|
|
|
12461
12749
|
source: "interactive",
|
|
12462
12750
|
});
|
|
12463
12751
|
}
|
|
12752
|
+
getBackgroundCommandUsage() {
|
|
12753
|
+
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]";
|
|
12754
|
+
}
|
|
12755
|
+
canUseInteractiveSelectors() {
|
|
12756
|
+
return !!this.ui && !!this.editorContainer;
|
|
12757
|
+
}
|
|
12758
|
+
getBackgroundStatusWeight(status) {
|
|
12759
|
+
switch (status) {
|
|
12760
|
+
case "running":
|
|
12761
|
+
return 0;
|
|
12762
|
+
case "unknown":
|
|
12763
|
+
return 1;
|
|
12764
|
+
case "error":
|
|
12765
|
+
return 2;
|
|
12766
|
+
case "done":
|
|
12767
|
+
return 3;
|
|
12768
|
+
case "terminated":
|
|
12769
|
+
return 4;
|
|
12770
|
+
default:
|
|
12771
|
+
return 5;
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
formatBackgroundStatusLabel(status) {
|
|
12775
|
+
switch (status) {
|
|
12776
|
+
case "running":
|
|
12777
|
+
return "RUNNING";
|
|
12778
|
+
case "done":
|
|
12779
|
+
return "DONE";
|
|
12780
|
+
case "error":
|
|
12781
|
+
return "ERROR";
|
|
12782
|
+
case "terminated":
|
|
12783
|
+
return "TERMINATED";
|
|
12784
|
+
case "unknown":
|
|
12785
|
+
return "UNKNOWN";
|
|
12786
|
+
default:
|
|
12787
|
+
return "UNKNOWN";
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
formatRelativeTime(timestamp) {
|
|
12791
|
+
if (!timestamp)
|
|
12792
|
+
return "-";
|
|
12793
|
+
const parsed = Date.parse(timestamp);
|
|
12794
|
+
if (!Number.isFinite(parsed))
|
|
12795
|
+
return timestamp;
|
|
12796
|
+
const diffMs = Date.now() - parsed;
|
|
12797
|
+
if (diffMs < 0)
|
|
12798
|
+
return "just now";
|
|
12799
|
+
const diffSec = Math.floor(diffMs / 1000);
|
|
12800
|
+
if (diffSec < 60)
|
|
12801
|
+
return `${Math.max(1, diffSec)}s ago`;
|
|
12802
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
12803
|
+
if (diffMin < 60)
|
|
12804
|
+
return `${diffMin}m ago`;
|
|
12805
|
+
const diffHours = Math.floor(diffMin / 60);
|
|
12806
|
+
if (diffHours < 24)
|
|
12807
|
+
return `${diffHours}h ago`;
|
|
12808
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
12809
|
+
return `${diffDays}d ago`;
|
|
12810
|
+
}
|
|
12811
|
+
formatDurationMs(startedAt, finishedAt) {
|
|
12812
|
+
if (!startedAt)
|
|
12813
|
+
return "-";
|
|
12814
|
+
const started = Date.parse(startedAt);
|
|
12815
|
+
if (!Number.isFinite(started))
|
|
12816
|
+
return "-";
|
|
12817
|
+
const end = finishedAt ? Date.parse(finishedAt) : Date.now();
|
|
12818
|
+
if (!Number.isFinite(end) || end < started)
|
|
12819
|
+
return "-";
|
|
12820
|
+
const diffSec = Math.max(0, Math.floor((end - started) / 1000));
|
|
12821
|
+
const hours = Math.floor(diffSec / 3600);
|
|
12822
|
+
const minutes = Math.floor((diffSec % 3600) / 60);
|
|
12823
|
+
const seconds = diffSec % 60;
|
|
12824
|
+
if (hours > 0)
|
|
12825
|
+
return `${hours}h ${minutes}m`;
|
|
12826
|
+
if (minutes > 0)
|
|
12827
|
+
return `${minutes}m ${seconds}s`;
|
|
12828
|
+
return `${seconds}s`;
|
|
12829
|
+
}
|
|
12830
|
+
sortBackgroundRecords(records) {
|
|
12831
|
+
return [...records].sort((left, right) => {
|
|
12832
|
+
const byStatus = this.getBackgroundStatusWeight(left.status) - this.getBackgroundStatusWeight(right.status);
|
|
12833
|
+
if (byStatus !== 0)
|
|
12834
|
+
return byStatus;
|
|
12835
|
+
return right.createdAt.localeCompare(left.createdAt);
|
|
12836
|
+
});
|
|
12837
|
+
}
|
|
12464
12838
|
formatBackgroundProcessOptionLabel(record, index) {
|
|
12465
|
-
const
|
|
12466
|
-
const
|
|
12467
|
-
const
|
|
12468
|
-
const
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12839
|
+
const statusLabel = this.formatBackgroundStatusLabel(record.status);
|
|
12840
|
+
const age = this.formatRelativeTime(record.createdAt);
|
|
12841
|
+
const runtime = this.formatDurationMs(record.startedAt, record.finishedAt);
|
|
12842
|
+
const source = record.source ?? "-";
|
|
12843
|
+
const commandPreview = record.command.length > 72 ? `${record.command.slice(0, 69)}...` : record.command;
|
|
12844
|
+
const stopFlag = record.requestedStopAt ? " · stop requested" : "";
|
|
12845
|
+
return `${index + 1}. [${statusLabel}] ${record.id} · pid=${record.pid} · age=${age} · runtime=${runtime} · source=${source}${stopFlag}\n ${commandPreview}`;
|
|
12846
|
+
}
|
|
12847
|
+
buildBackgroundProcessesReport(records) {
|
|
12848
|
+
const counts = {
|
|
12849
|
+
running: 0,
|
|
12850
|
+
done: 0,
|
|
12851
|
+
error: 0,
|
|
12852
|
+
terminated: 0,
|
|
12853
|
+
unknown: 0,
|
|
12854
|
+
};
|
|
12855
|
+
for (const record of records) {
|
|
12856
|
+
counts[record.status] += 1;
|
|
12857
|
+
}
|
|
12858
|
+
const header = `Summary: total=${records.length} · running=${counts.running} · done=${counts.done} · error=${counts.error} · terminated=${counts.terminated} · unknown=${counts.unknown}`;
|
|
12859
|
+
const items = records.map((record, index) => this.formatBackgroundProcessOptionLabel(record, index));
|
|
12860
|
+
const hints = [
|
|
12861
|
+
"Quick actions:",
|
|
12862
|
+
"- /bg status <id>",
|
|
12863
|
+
"- /bg logs <id> [lines]",
|
|
12864
|
+
"- /bg stop <id>",
|
|
12865
|
+
"- /bg stop-all",
|
|
12866
|
+
"- /bg prune [hours]",
|
|
12867
|
+
];
|
|
12868
|
+
return [header, "", ...items, "", ...hints].join("\n");
|
|
12869
|
+
}
|
|
12870
|
+
async runBackgroundMenu(cwd) {
|
|
12871
|
+
if (!this.canUseInteractiveSelectors())
|
|
12872
|
+
return false;
|
|
12873
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, 60));
|
|
12874
|
+
const runningCount = records.filter((record) => record.status === "running").length;
|
|
12875
|
+
const selected = await this.showExtensionSelector(`/bg menu · running=${runningCount} · total=${records.length}`, [
|
|
12876
|
+
"List all processes",
|
|
12877
|
+
"List running only",
|
|
12878
|
+
"Show process status",
|
|
12879
|
+
"Show process logs",
|
|
12880
|
+
"Stop process",
|
|
12881
|
+
"Stop all running processes",
|
|
12882
|
+
"Prune old completed records",
|
|
12883
|
+
"Help",
|
|
12884
|
+
"Cancel",
|
|
12885
|
+
]);
|
|
12886
|
+
if (!selected || selected === "Cancel") {
|
|
12887
|
+
this.showStatus("/bg menu cancelled.");
|
|
12888
|
+
return true;
|
|
12889
|
+
}
|
|
12890
|
+
if (selected === "List all processes") {
|
|
12891
|
+
await this.handleBackgroundProcessesSlashCommand("/bg list");
|
|
12892
|
+
return true;
|
|
12893
|
+
}
|
|
12894
|
+
if (selected === "List running only") {
|
|
12895
|
+
await this.handleBackgroundProcessesSlashCommand("/bg running");
|
|
12896
|
+
return true;
|
|
12897
|
+
}
|
|
12898
|
+
if (selected === "Show process status") {
|
|
12899
|
+
await this.handleBackgroundProcessesSlashCommand("/bg status");
|
|
12900
|
+
return true;
|
|
12901
|
+
}
|
|
12902
|
+
if (selected === "Show process logs") {
|
|
12903
|
+
await this.handleBackgroundProcessesSlashCommand("/bg logs");
|
|
12904
|
+
return true;
|
|
12905
|
+
}
|
|
12906
|
+
if (selected === "Stop process") {
|
|
12907
|
+
await this.handleBackgroundProcessesSlashCommand("/bg stop");
|
|
12908
|
+
return true;
|
|
12909
|
+
}
|
|
12910
|
+
if (selected === "Stop all running processes") {
|
|
12911
|
+
await this.handleBackgroundProcessesSlashCommand("/bg stop-all");
|
|
12912
|
+
return true;
|
|
12913
|
+
}
|
|
12914
|
+
if (selected === "Prune old completed records") {
|
|
12915
|
+
await this.handleBackgroundProcessesSlashCommand("/bg prune");
|
|
12916
|
+
return true;
|
|
12917
|
+
}
|
|
12918
|
+
if (selected === "Help") {
|
|
12919
|
+
await this.handleBackgroundProcessesSlashCommand("/bg help");
|
|
12920
|
+
return true;
|
|
12921
|
+
}
|
|
12922
|
+
return true;
|
|
12923
|
+
}
|
|
12924
|
+
async pickBackgroundProcessId(cwd, actionLabel, options) {
|
|
12925
|
+
const limit = options?.limit ?? 50;
|
|
12926
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, limit));
|
|
12473
12927
|
if (records.length === 0) {
|
|
12474
12928
|
this.showStatus("No background processes found.");
|
|
12475
12929
|
return undefined;
|
|
12476
12930
|
}
|
|
12477
|
-
const
|
|
12478
|
-
|
|
12931
|
+
const preferredStatuses = options?.preferredStatuses;
|
|
12932
|
+
let filtered = records;
|
|
12933
|
+
if (preferredStatuses && preferredStatuses.length > 0) {
|
|
12934
|
+
const allowed = new Set(preferredStatuses);
|
|
12935
|
+
const preferred = records.filter((record) => allowed.has(record.status));
|
|
12936
|
+
if (preferred.length > 0)
|
|
12937
|
+
filtered = preferred;
|
|
12938
|
+
}
|
|
12939
|
+
const pickerOptions = filtered.map((record, index) => this.formatBackgroundProcessOptionLabel(record, index));
|
|
12940
|
+
const selected = await this.showExtensionSelector(`/bg ${actionLabel}: select process`, pickerOptions);
|
|
12479
12941
|
if (!selected) {
|
|
12480
12942
|
this.showStatus(`/bg ${actionLabel} cancelled.`);
|
|
12481
12943
|
return undefined;
|
|
12482
12944
|
}
|
|
12483
|
-
const selectedIndex =
|
|
12484
|
-
return selectedIndex >= 0 ?
|
|
12945
|
+
const selectedIndex = pickerOptions.indexOf(selected);
|
|
12946
|
+
return selectedIndex >= 0 ? filtered[selectedIndex]?.id : undefined;
|
|
12947
|
+
}
|
|
12948
|
+
async stopAllBackgroundProcesses(cwd) {
|
|
12949
|
+
const runningRecords = this.sortBackgroundRecords(listBackgroundProcesses(cwd, 200)).filter((record) => record.status === "running");
|
|
12950
|
+
if (runningRecords.length === 0) {
|
|
12951
|
+
this.showStatus("No running background processes found.");
|
|
12952
|
+
return;
|
|
12953
|
+
}
|
|
12954
|
+
if (this.canUseInteractiveSelectors()) {
|
|
12955
|
+
const confirmed = await this.showExtensionConfirm("Stop all background processes?", `Send stop signal to ${runningRecords.length} running process(es).`);
|
|
12956
|
+
if (!confirmed) {
|
|
12957
|
+
this.showStatus("/bg stop-all cancelled.");
|
|
12958
|
+
return;
|
|
12959
|
+
}
|
|
12960
|
+
}
|
|
12961
|
+
const updatedRecords = [];
|
|
12962
|
+
for (const record of runningRecords) {
|
|
12963
|
+
const updated = stopBackgroundProcess(cwd, record.id);
|
|
12964
|
+
if (updated)
|
|
12965
|
+
updatedRecords.push(updated);
|
|
12966
|
+
}
|
|
12967
|
+
const stillRunning = updatedRecords.filter((record) => record.status === "running");
|
|
12968
|
+
const lines = [
|
|
12969
|
+
`Stop-all requested for ${runningRecords.length} process(es).`,
|
|
12970
|
+
`Still running: ${stillRunning.length}`,
|
|
12971
|
+
"",
|
|
12972
|
+
...updatedRecords.map((record, index) => `${index + 1}. ${record.id} · status=${record.status} · requested_stop=${record.requestedStopAt ?? "-"}`),
|
|
12973
|
+
];
|
|
12974
|
+
this.showCommandTextBlock("Background Stop-All", lines.join("\n"));
|
|
12485
12975
|
}
|
|
12486
12976
|
async handleBackgroundProcessesSlashCommand(text) {
|
|
12487
12977
|
const args = this.parseSlashArgs(text).slice(1);
|
|
12488
12978
|
const cwd = this.sessionManager.getCwd();
|
|
12979
|
+
const usage = this.getBackgroundCommandUsage();
|
|
12980
|
+
if (args.length === 0) {
|
|
12981
|
+
const handledByMenu = await this.runBackgroundMenu(cwd);
|
|
12982
|
+
if (handledByMenu)
|
|
12983
|
+
return;
|
|
12984
|
+
}
|
|
12489
12985
|
const firstArg = (args[0] ?? "").toLowerCase();
|
|
12490
12986
|
const subcommand = firstArg || "list";
|
|
12491
|
-
|
|
12492
|
-
|
|
12987
|
+
const listFilters = {
|
|
12988
|
+
list: undefined,
|
|
12989
|
+
running: ["running"],
|
|
12990
|
+
done: ["done"],
|
|
12991
|
+
error: ["error"],
|
|
12992
|
+
failed: ["error"],
|
|
12993
|
+
terminated: ["terminated"],
|
|
12994
|
+
unknown: ["unknown"],
|
|
12995
|
+
};
|
|
12996
|
+
const hasListFilter = Object.prototype.hasOwnProperty.call(listFilters, subcommand);
|
|
12997
|
+
if (hasListFilter || /^\d+$/.test(subcommand)) {
|
|
12998
|
+
const limitRaw = subcommand === "list" ? args[1] : hasListFilter ? args[1] : subcommand;
|
|
12493
12999
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : 20;
|
|
12494
13000
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
12495
|
-
this.showWarning(
|
|
13001
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12496
13002
|
return;
|
|
12497
13003
|
}
|
|
12498
|
-
const records = listBackgroundProcesses(cwd, limit);
|
|
12499
|
-
|
|
12500
|
-
|
|
13004
|
+
const records = this.sortBackgroundRecords(listBackgroundProcesses(cwd, limit));
|
|
13005
|
+
const statusFilter = hasListFilter ? listFilters[subcommand] : undefined;
|
|
13006
|
+
const filtered = statusFilter && statusFilter.length > 0
|
|
13007
|
+
? records.filter((record) => statusFilter.includes(record.status))
|
|
13008
|
+
: records;
|
|
13009
|
+
if (filtered.length === 0) {
|
|
13010
|
+
if (statusFilter && statusFilter.length > 0) {
|
|
13011
|
+
this.showStatus(`No background processes found for "${subcommand}" filter.`);
|
|
13012
|
+
}
|
|
13013
|
+
else {
|
|
13014
|
+
this.showStatus("No background processes found.");
|
|
13015
|
+
}
|
|
12501
13016
|
return;
|
|
12502
13017
|
}
|
|
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"));
|
|
13018
|
+
this.showCommandTextBlock("Background Processes", this.buildBackgroundProcessesReport(filtered));
|
|
12512
13019
|
return;
|
|
12513
13020
|
}
|
|
12514
13021
|
if (subcommand === "status") {
|
|
@@ -12525,10 +13032,11 @@ export class InteractiveMode {
|
|
|
12525
13032
|
}
|
|
12526
13033
|
const lines = [
|
|
12527
13034
|
`ID: ${record.id}`,
|
|
12528
|
-
`Status: ${record.status}`,
|
|
13035
|
+
`Status: ${this.formatBackgroundStatusLabel(record.status)}${record.requestedStopAt ? " (stop requested)" : ""}`,
|
|
12529
13036
|
`PID: ${record.pid}`,
|
|
12530
|
-
`Created: ${record.createdAt}`,
|
|
13037
|
+
`Created: ${record.createdAt} (${this.formatRelativeTime(record.createdAt)})`,
|
|
12531
13038
|
`Started: ${record.startedAt}`,
|
|
13039
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)}`,
|
|
12532
13040
|
`Finished: ${record.finishedAt ?? "-"}`,
|
|
12533
13041
|
`Exit code: ${typeof record.exitCode === "number" ? record.exitCode : "-"}`,
|
|
12534
13042
|
`Cwd: ${record.cwd}`,
|
|
@@ -12537,6 +13045,10 @@ export class InteractiveMode {
|
|
|
12537
13045
|
`Status file: ${record.metaPath}`,
|
|
12538
13046
|
`Log file: ${record.logPath}`,
|
|
12539
13047
|
"",
|
|
13048
|
+
"Quick actions:",
|
|
13049
|
+
`- /bg logs ${record.id} 120`,
|
|
13050
|
+
`- /bg stop ${record.id}`,
|
|
13051
|
+
"",
|
|
12540
13052
|
"Command:",
|
|
12541
13053
|
record.command,
|
|
12542
13054
|
];
|
|
@@ -12546,7 +13058,9 @@ export class InteractiveMode {
|
|
|
12546
13058
|
if (subcommand === "logs") {
|
|
12547
13059
|
let id = args[1];
|
|
12548
13060
|
if (!id) {
|
|
12549
|
-
id = await this.pickBackgroundProcessId(cwd, "logs"
|
|
13061
|
+
id = await this.pickBackgroundProcessId(cwd, "logs", {
|
|
13062
|
+
preferredStatuses: ["running", "unknown", "error", "done", "terminated"],
|
|
13063
|
+
});
|
|
12550
13064
|
}
|
|
12551
13065
|
if (!id)
|
|
12552
13066
|
return;
|
|
@@ -12567,30 +13081,368 @@ export class InteractiveMode {
|
|
|
12567
13081
|
return;
|
|
12568
13082
|
}
|
|
12569
13083
|
const body = tail.trim().length > 0 ? tail : "(no output yet)";
|
|
12570
|
-
this.showCommandTextBlock("Background Logs", [
|
|
13084
|
+
this.showCommandTextBlock("Background Logs", [
|
|
13085
|
+
`ID: ${record.id} · status=${this.formatBackgroundStatusLabel(record.status)} · tail=${tailLines} lines`,
|
|
13086
|
+
`Runtime: ${this.formatDurationMs(record.startedAt, record.finishedAt)} · stop_requested=${record.requestedStopAt ? "yes" : "no"}`,
|
|
13087
|
+
`Command: ${record.command.length > 120 ? `${record.command.slice(0, 117)}...` : record.command}`,
|
|
13088
|
+
"",
|
|
13089
|
+
body,
|
|
13090
|
+
].join("\n"));
|
|
12571
13091
|
return;
|
|
12572
13092
|
}
|
|
12573
13093
|
if (subcommand === "stop" || subcommand === "kill" || subcommand === "cancel") {
|
|
12574
13094
|
let id = args[1];
|
|
12575
13095
|
if (!id) {
|
|
12576
|
-
id = await this.pickBackgroundProcessId(cwd, "stop"
|
|
13096
|
+
id = await this.pickBackgroundProcessId(cwd, "stop", {
|
|
13097
|
+
preferredStatuses: ["running", "unknown", "error", "done", "terminated"],
|
|
13098
|
+
});
|
|
12577
13099
|
}
|
|
12578
13100
|
if (!id)
|
|
12579
13101
|
return;
|
|
13102
|
+
const current = getBackgroundProcess(cwd, id);
|
|
13103
|
+
if (!current) {
|
|
13104
|
+
this.showWarning(`Background process not found: ${id}`);
|
|
13105
|
+
return;
|
|
13106
|
+
}
|
|
13107
|
+
if (current.status !== "running") {
|
|
13108
|
+
this.showStatus(`Background process ${current.id} is already ${current.status}.`);
|
|
13109
|
+
return;
|
|
13110
|
+
}
|
|
13111
|
+
if (this.canUseInteractiveSelectors()) {
|
|
13112
|
+
const confirmed = await this.showExtensionConfirm("Stop background process?", `${current.id}\n${current.command.length > 140 ? `${current.command.slice(0, 137)}...` : current.command}`);
|
|
13113
|
+
if (!confirmed) {
|
|
13114
|
+
this.showStatus(`/bg stop cancelled for ${current.id}.`);
|
|
13115
|
+
return;
|
|
13116
|
+
}
|
|
13117
|
+
}
|
|
12580
13118
|
const record = stopBackgroundProcess(cwd, id);
|
|
12581
13119
|
if (!record) {
|
|
12582
13120
|
this.showWarning(`Background process not found: ${id}`);
|
|
12583
13121
|
return;
|
|
12584
13122
|
}
|
|
12585
13123
|
if (record.status === "running") {
|
|
12586
|
-
this.showWarning(`Stop
|
|
13124
|
+
this.showWarning(`Stop requested for ${record.id}, waiting for graceful shutdown. Use /bg status ${record.id} to monitor.`);
|
|
12587
13125
|
}
|
|
12588
13126
|
else {
|
|
12589
13127
|
this.showStatus(`Background process ${record.id} stopped (${record.status}).`);
|
|
12590
13128
|
}
|
|
12591
13129
|
return;
|
|
12592
13130
|
}
|
|
12593
|
-
|
|
13131
|
+
if (subcommand === "stop-all" || subcommand === "kill-all" || subcommand === "cancel-all") {
|
|
13132
|
+
await this.stopAllBackgroundProcesses(cwd);
|
|
13133
|
+
return;
|
|
13134
|
+
}
|
|
13135
|
+
if (subcommand === "prune" || subcommand === "cleanup") {
|
|
13136
|
+
const hoursRaw = args[1];
|
|
13137
|
+
const hours = hoursRaw ? Number.parseInt(hoursRaw, 10) : 168;
|
|
13138
|
+
if (!Number.isInteger(hours) || hours < 1 || hours > 2160) {
|
|
13139
|
+
this.showWarning("Usage: /bg prune [hours: 1..2160]");
|
|
13140
|
+
return;
|
|
13141
|
+
}
|
|
13142
|
+
const result = pruneBackgroundProcesses(cwd, { maxAgeHours: hours });
|
|
13143
|
+
const preview = result.removedIds.length > 0 ? `\n${result.removedIds.slice(0, 10).map((id) => `- ${id}`).join("\n")}` : "";
|
|
13144
|
+
const moreSuffix = result.removedIds.length > 10 ? `\n- ... (+${result.removedIds.length - 10} more)` : "";
|
|
13145
|
+
this.showCommandTextBlock("Background Prune", [
|
|
13146
|
+
`Threshold: older than ${result.thresholdHours}h`,
|
|
13147
|
+
`Removed: ${result.removed}`,
|
|
13148
|
+
`Skipped running: ${result.skippedRunning}`,
|
|
13149
|
+
`Skipped recent: ${result.skippedRecent}`,
|
|
13150
|
+
preview ? "" : undefined,
|
|
13151
|
+
preview || undefined,
|
|
13152
|
+
moreSuffix || undefined,
|
|
13153
|
+
]
|
|
13154
|
+
.filter((line) => typeof line === "string")
|
|
13155
|
+
.join("\n"));
|
|
13156
|
+
return;
|
|
13157
|
+
}
|
|
13158
|
+
if (subcommand === "help") {
|
|
13159
|
+
this.showCommandTextBlock("Background Help", [
|
|
13160
|
+
"Background command usage:",
|
|
13161
|
+
usage,
|
|
13162
|
+
"",
|
|
13163
|
+
"Examples:",
|
|
13164
|
+
"- ! npm run dev &",
|
|
13165
|
+
"- /bg",
|
|
13166
|
+
"- /bg running",
|
|
13167
|
+
"- /bg status bg_123",
|
|
13168
|
+
"- /bg logs bg_123 200",
|
|
13169
|
+
"- /bg stop bg_123",
|
|
13170
|
+
"- /bg stop-all",
|
|
13171
|
+
"- /bg prune 72",
|
|
13172
|
+
].join("\n"));
|
|
13173
|
+
return;
|
|
13174
|
+
}
|
|
13175
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13176
|
+
}
|
|
13177
|
+
getExtensionsCommandUsage() {
|
|
13178
|
+
return "/extensions (/ext) [list|install <source>|update [source]|remove <source>|enable <target>|disable <target>|help] [--global]";
|
|
13179
|
+
}
|
|
13180
|
+
createInteractivePackageManager(cwd) {
|
|
13181
|
+
return new DefaultPackageManager({
|
|
13182
|
+
cwd,
|
|
13183
|
+
agentDir: getAgentDir(),
|
|
13184
|
+
settingsManager: this.settingsManager,
|
|
13185
|
+
});
|
|
13186
|
+
}
|
|
13187
|
+
normalizeExtensionOverrideTarget(target) {
|
|
13188
|
+
const trimmed = target.trim();
|
|
13189
|
+
if (!trimmed)
|
|
13190
|
+
return "";
|
|
13191
|
+
if (path.isAbsolute(trimmed)) {
|
|
13192
|
+
return path.resolve(trimmed);
|
|
13193
|
+
}
|
|
13194
|
+
let normalized = trimmed.replace(/\\/g, "/");
|
|
13195
|
+
if (normalized.startsWith("./"))
|
|
13196
|
+
normalized = normalized.slice(2);
|
|
13197
|
+
if (!normalized.startsWith("extensions/")) {
|
|
13198
|
+
normalized = `extensions/${normalized}`;
|
|
13199
|
+
}
|
|
13200
|
+
return normalized;
|
|
13201
|
+
}
|
|
13202
|
+
setExtensionOverride(scope, target, enabled) {
|
|
13203
|
+
const normalizedTarget = this.normalizeExtensionOverrideTarget(target);
|
|
13204
|
+
if (!normalizedTarget)
|
|
13205
|
+
return { updated: false, token: undefined };
|
|
13206
|
+
const current = scope === "project"
|
|
13207
|
+
? [...(this.settingsManager.getProjectSettings().extensions ?? [])]
|
|
13208
|
+
: [...(this.settingsManager.getGlobalSettings().extensions ?? [])];
|
|
13209
|
+
const normalizeEntryTarget = (entry) => {
|
|
13210
|
+
const trimmed = entry.trim();
|
|
13211
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
13212
|
+
return this.normalizeExtensionOverrideTarget(trimmed.slice(1));
|
|
13213
|
+
}
|
|
13214
|
+
return "";
|
|
13215
|
+
};
|
|
13216
|
+
const filtered = current.filter((entry) => normalizeEntryTarget(entry) !== normalizedTarget);
|
|
13217
|
+
const token = `${enabled ? "+" : "-"}${normalizedTarget}`;
|
|
13218
|
+
const next = [...filtered, token];
|
|
13219
|
+
if (JSON.stringify(next) === JSON.stringify(current)) {
|
|
13220
|
+
return { updated: false, token };
|
|
13221
|
+
}
|
|
13222
|
+
if (scope === "project") {
|
|
13223
|
+
this.settingsManager.setProjectExtensionPaths(next);
|
|
13224
|
+
}
|
|
13225
|
+
else {
|
|
13226
|
+
this.settingsManager.setExtensionPaths(next);
|
|
13227
|
+
}
|
|
13228
|
+
return { updated: true, token };
|
|
13229
|
+
}
|
|
13230
|
+
togglePackageExtensionSource(scope, source, enabled) {
|
|
13231
|
+
const settings = scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
|
13232
|
+
const packages = [...(settings.packages ?? [])];
|
|
13233
|
+
const index = packages.findIndex((pkg) => (typeof pkg === "string" ? pkg : pkg.source) === source);
|
|
13234
|
+
if (index < 0) {
|
|
13235
|
+
return { matched: false, changed: false, message: "" };
|
|
13236
|
+
}
|
|
13237
|
+
const entry = packages[index];
|
|
13238
|
+
let nextEntry = entry;
|
|
13239
|
+
if (!enabled) {
|
|
13240
|
+
if (typeof entry === "string") {
|
|
13241
|
+
nextEntry = { source: entry, extensions: [] };
|
|
13242
|
+
}
|
|
13243
|
+
else if (Array.isArray(entry.extensions) && entry.extensions.length === 0) {
|
|
13244
|
+
return { matched: true, changed: false, message: `Extension source already disabled: ${source}` };
|
|
13245
|
+
}
|
|
13246
|
+
else {
|
|
13247
|
+
nextEntry = { ...entry, extensions: [] };
|
|
13248
|
+
}
|
|
13249
|
+
}
|
|
13250
|
+
else if (typeof entry === "string") {
|
|
13251
|
+
return { matched: true, changed: false, message: `Extension source already enabled: ${source}` };
|
|
13252
|
+
}
|
|
13253
|
+
else if (!Array.isArray(entry.extensions) || entry.extensions.length > 0) {
|
|
13254
|
+
return { matched: true, changed: false, message: `Extension source already enabled: ${source}` };
|
|
13255
|
+
}
|
|
13256
|
+
else {
|
|
13257
|
+
const { extensions: _extensions, ...rest } = entry;
|
|
13258
|
+
nextEntry = Object.keys(rest).length === 1 ? rest.source : rest;
|
|
13259
|
+
}
|
|
13260
|
+
packages[index] = nextEntry;
|
|
13261
|
+
if (scope === "project") {
|
|
13262
|
+
this.settingsManager.setProjectPackages(packages);
|
|
13263
|
+
}
|
|
13264
|
+
else {
|
|
13265
|
+
this.settingsManager.setPackages(packages);
|
|
13266
|
+
}
|
|
13267
|
+
return {
|
|
13268
|
+
matched: true,
|
|
13269
|
+
changed: true,
|
|
13270
|
+
message: `${enabled ? "Enabled" : "Disabled"} extension source (${scope}): ${source}`,
|
|
13271
|
+
};
|
|
13272
|
+
}
|
|
13273
|
+
async showExtensionsList(scopeFilter) {
|
|
13274
|
+
const cwd = this.sessionManager.getCwd();
|
|
13275
|
+
const packageManager = this.createInteractivePackageManager(cwd);
|
|
13276
|
+
const resolved = await packageManager.resolve();
|
|
13277
|
+
const packageLines = [];
|
|
13278
|
+
const projectPackages = this.settingsManager.getProjectSettings().packages ?? [];
|
|
13279
|
+
const globalPackages = this.settingsManager.getGlobalSettings().packages ?? [];
|
|
13280
|
+
const includeProject = !scopeFilter || scopeFilter === "project";
|
|
13281
|
+
const includeGlobal = !scopeFilter || scopeFilter === "user";
|
|
13282
|
+
if (includeProject) {
|
|
13283
|
+
packageLines.push("Project package sources:");
|
|
13284
|
+
if (projectPackages.length === 0) {
|
|
13285
|
+
packageLines.push(" (none)");
|
|
13286
|
+
}
|
|
13287
|
+
else {
|
|
13288
|
+
for (const pkg of projectPackages) {
|
|
13289
|
+
const source = typeof pkg === "string" ? pkg : pkg.source;
|
|
13290
|
+
const status = typeof pkg === "object" && Array.isArray(pkg.extensions) && pkg.extensions.length === 0
|
|
13291
|
+
? "disabled"
|
|
13292
|
+
: "enabled";
|
|
13293
|
+
packageLines.push(` - [${status}] ${source}`);
|
|
13294
|
+
}
|
|
13295
|
+
}
|
|
13296
|
+
}
|
|
13297
|
+
if (includeGlobal) {
|
|
13298
|
+
if (packageLines.length > 0)
|
|
13299
|
+
packageLines.push("");
|
|
13300
|
+
packageLines.push("User package sources:");
|
|
13301
|
+
if (globalPackages.length === 0) {
|
|
13302
|
+
packageLines.push(" (none)");
|
|
13303
|
+
}
|
|
13304
|
+
else {
|
|
13305
|
+
for (const pkg of globalPackages) {
|
|
13306
|
+
const source = typeof pkg === "string" ? pkg : pkg.source;
|
|
13307
|
+
const status = typeof pkg === "object" && Array.isArray(pkg.extensions) && pkg.extensions.length === 0
|
|
13308
|
+
? "disabled"
|
|
13309
|
+
: "enabled";
|
|
13310
|
+
packageLines.push(` - [${status}] ${source}`);
|
|
13311
|
+
}
|
|
13312
|
+
}
|
|
13313
|
+
}
|
|
13314
|
+
const extensions = resolved.extensions
|
|
13315
|
+
.filter((resource) => !scopeFilter || resource.metadata.scope === scopeFilter)
|
|
13316
|
+
.sort((a, b) => {
|
|
13317
|
+
if (a.metadata.scope !== b.metadata.scope)
|
|
13318
|
+
return a.metadata.scope.localeCompare(b.metadata.scope, "en");
|
|
13319
|
+
return a.path.localeCompare(b.path, "en");
|
|
13320
|
+
});
|
|
13321
|
+
const enabledCount = extensions.filter((resource) => resource.enabled).length;
|
|
13322
|
+
const disabledCount = extensions.length - enabledCount;
|
|
13323
|
+
const extensionLines = extensions.length === 0
|
|
13324
|
+
? [" (none)"]
|
|
13325
|
+
: extensions.map((resource) => {
|
|
13326
|
+
const relative = path.relative(cwd, resource.path);
|
|
13327
|
+
const displayPath = !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : resource.path;
|
|
13328
|
+
const status = resource.enabled ? "enabled" : "disabled";
|
|
13329
|
+
return ` - [${status}] ${displayPath} · scope=${resource.metadata.scope} · source=${resource.metadata.source}`;
|
|
13330
|
+
});
|
|
13331
|
+
const lines = [
|
|
13332
|
+
`Extension resources: total=${extensions.length} · enabled=${enabledCount} · disabled=${disabledCount}`,
|
|
13333
|
+
"",
|
|
13334
|
+
...extensionLines,
|
|
13335
|
+
"",
|
|
13336
|
+
...packageLines,
|
|
13337
|
+
"",
|
|
13338
|
+
"Commands:",
|
|
13339
|
+
"- /extensions install <source> [--global]",
|
|
13340
|
+
"- /extensions update [source]",
|
|
13341
|
+
"- /extensions remove <source> [--global]",
|
|
13342
|
+
"- /extensions disable <source-or-path> [--global]",
|
|
13343
|
+
"- /extensions enable <source-or-path> [--global]",
|
|
13344
|
+
];
|
|
13345
|
+
this.showCommandTextBlock("Extensions", lines.join("\n"));
|
|
13346
|
+
}
|
|
13347
|
+
async handleExtensionsSlashCommand(text) {
|
|
13348
|
+
const args = this.parseSlashArgs(text).slice(1);
|
|
13349
|
+
const usage = this.getExtensionsCommandUsage();
|
|
13350
|
+
const filteredArgs = args.filter((arg) => arg !== "/extensions" && arg !== "/ext");
|
|
13351
|
+
let scope = "project";
|
|
13352
|
+
const normalizedArgs = [];
|
|
13353
|
+
for (const arg of filteredArgs) {
|
|
13354
|
+
if (arg === "--global" || arg === "-g") {
|
|
13355
|
+
scope = "user";
|
|
13356
|
+
continue;
|
|
13357
|
+
}
|
|
13358
|
+
if (arg === "--project") {
|
|
13359
|
+
scope = "project";
|
|
13360
|
+
continue;
|
|
13361
|
+
}
|
|
13362
|
+
normalizedArgs.push(arg);
|
|
13363
|
+
}
|
|
13364
|
+
const subcommand = (normalizedArgs[0] ?? "list").toLowerCase();
|
|
13365
|
+
const subArgs = normalizedArgs.slice(1);
|
|
13366
|
+
const cwd = this.sessionManager.getCwd();
|
|
13367
|
+
const packageManager = this.createInteractivePackageManager(cwd);
|
|
13368
|
+
if (subcommand === "help") {
|
|
13369
|
+
this.showCommandTextBlock("Extensions Help", [
|
|
13370
|
+
"Extension lifecycle usage:",
|
|
13371
|
+
usage,
|
|
13372
|
+
"",
|
|
13373
|
+
"Examples:",
|
|
13374
|
+
"- /extensions list",
|
|
13375
|
+
"- /extensions list --global",
|
|
13376
|
+
"- /extensions install npm:@org/iosm-extension",
|
|
13377
|
+
"- /extensions install ./local-extension --global",
|
|
13378
|
+
"- /extensions update",
|
|
13379
|
+
"- /extensions remove npm:@org/iosm-extension",
|
|
13380
|
+
"- /extensions disable extensions/my-tool.ts",
|
|
13381
|
+
"- /ext enable npm:@org/iosm-extension --global",
|
|
13382
|
+
].join("\n"));
|
|
13383
|
+
return;
|
|
13384
|
+
}
|
|
13385
|
+
if (subcommand === "list") {
|
|
13386
|
+
await this.showExtensionsList(scope);
|
|
13387
|
+
return;
|
|
13388
|
+
}
|
|
13389
|
+
if (subcommand === "install") {
|
|
13390
|
+
const source = subArgs[0];
|
|
13391
|
+
if (!source) {
|
|
13392
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13393
|
+
return;
|
|
13394
|
+
}
|
|
13395
|
+
await packageManager.install(source, { local: scope === "project" });
|
|
13396
|
+
packageManager.addSourceToSettings(source, { local: scope === "project" });
|
|
13397
|
+
await this.reloadAfterMemoryMutation(`Installed extension source (${scope}): ${source}`, "Extension source installed but reload failed");
|
|
13398
|
+
return;
|
|
13399
|
+
}
|
|
13400
|
+
if (subcommand === "update") {
|
|
13401
|
+
const source = subArgs[0];
|
|
13402
|
+
await packageManager.update(source);
|
|
13403
|
+
await this.reloadAfterMemoryMutation(source ? `Updated extension source: ${source}` : "Updated extension sources", "Extension sources updated but reload failed");
|
|
13404
|
+
return;
|
|
13405
|
+
}
|
|
13406
|
+
if (subcommand === "remove" || subcommand === "uninstall") {
|
|
13407
|
+
const source = subArgs[0];
|
|
13408
|
+
if (!source) {
|
|
13409
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13410
|
+
return;
|
|
13411
|
+
}
|
|
13412
|
+
await packageManager.remove(source, { local: scope === "project" });
|
|
13413
|
+
const removed = packageManager.removeSourceFromSettings(source, { local: scope === "project" });
|
|
13414
|
+
if (!removed) {
|
|
13415
|
+
this.showWarning(`No matching extension source found in ${scope} scope: ${source}`);
|
|
13416
|
+
return;
|
|
13417
|
+
}
|
|
13418
|
+
await this.reloadAfterMemoryMutation(`Removed extension source (${scope}): ${source}`, "Extension source removed but reload failed");
|
|
13419
|
+
return;
|
|
13420
|
+
}
|
|
13421
|
+
if (subcommand === "enable" || subcommand === "disable") {
|
|
13422
|
+
const target = subArgs[0];
|
|
13423
|
+
if (!target) {
|
|
13424
|
+
this.showWarning(`Usage: ${usage}`);
|
|
13425
|
+
return;
|
|
13426
|
+
}
|
|
13427
|
+
const desiredEnabled = subcommand === "enable";
|
|
13428
|
+
const packageToggle = this.togglePackageExtensionSource(scope, target, desiredEnabled);
|
|
13429
|
+
if (packageToggle.matched) {
|
|
13430
|
+
if (!packageToggle.changed) {
|
|
13431
|
+
this.showStatus(packageToggle.message);
|
|
13432
|
+
return;
|
|
13433
|
+
}
|
|
13434
|
+
await this.reloadAfterMemoryMutation(packageToggle.message, "Extension source toggle applied but reload failed");
|
|
13435
|
+
return;
|
|
13436
|
+
}
|
|
13437
|
+
const override = this.setExtensionOverride(scope, target, desiredEnabled);
|
|
13438
|
+
if (!override.updated) {
|
|
13439
|
+
this.showStatus(`No extension override changes were needed for: ${target}`);
|
|
13440
|
+
return;
|
|
13441
|
+
}
|
|
13442
|
+
await this.reloadAfterMemoryMutation(`${desiredEnabled ? "Enabled" : "Disabled"} extension path (${scope}) via override: ${override.token}`, "Extension override applied but reload failed");
|
|
13443
|
+
return;
|
|
13444
|
+
}
|
|
13445
|
+
this.showWarning(`Usage: ${usage}`);
|
|
12594
13446
|
}
|
|
12595
13447
|
handleTeamRunsSlashCommand(text) {
|
|
12596
13448
|
const args = this.parseSlashArgs(text).slice(1);
|