@oh-my-pi/pi-coding-agent 17.2.12 → 17.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 +57 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-d8xh7keh.md} +57 -0
- package/dist/cli.js +3069 -3047
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +4 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +24 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +5 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +139 -8
- package/src/session/agent-session-types.ts +2 -0
- package/src/session/agent-session.ts +66 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +321 -184
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
ToolLoadMode,
|
|
10
10
|
} from "@oh-my-pi/pi-agent-core";
|
|
11
11
|
import type { ComputerSafetyCheck, ImageContent, Static, TextContent, TSchema } from "@oh-my-pi/pi-ai";
|
|
12
|
-
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { sanitizeText, untilAborted } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import type { Settings } from "../../config/settings";
|
|
14
14
|
import type { Theme } from "../../modes/theme/theme";
|
|
15
15
|
import { type ApprovalMode, formatApprovalPrompt, resolveApproval, truncateForPrompt } from "../../tools/approval";
|
|
@@ -190,10 +190,11 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
190
190
|
const configuredMode = (settings?.get("tools.approvalMode") ?? "yolo") as ApprovalMode;
|
|
191
191
|
const approvalMode: ApprovalMode = cliAutoApprove ? "yolo" : configuredMode;
|
|
192
192
|
const userPolicies = (settings?.get("tools.approval") ?? {}) as Record<string, unknown>;
|
|
193
|
-
|
|
193
|
+
const preResolved = resolveApproval(this.tool, approvalArgs(params, context), approvalMode, userPolicies);
|
|
194
|
+
if (preResolved.policy === "deny") {
|
|
194
195
|
throw new Error(
|
|
195
|
-
`Tool "${this.tool.name}" is blocked by user policy.\n` +
|
|
196
|
-
`To allow: remove "tools.approval.${this.tool.name}: deny" from config.`,
|
|
196
|
+
`Tool "${preResolved.policyKey ?? this.tool.name}" is blocked by user policy.\n` +
|
|
197
|
+
`To allow: remove "tools.approval.${preResolved.policyKey ?? this.tool.name}: deny" from config.`,
|
|
197
198
|
);
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -242,8 +243,8 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
242
243
|
context?.xdevTierResolved?.(resolved.tier);
|
|
243
244
|
if (resolved.policy === "deny") {
|
|
244
245
|
throw new Error(
|
|
245
|
-
`Tool "${this.tool.name}" is blocked by user policy.\n` +
|
|
246
|
-
`To allow: remove "tools.approval.${this.tool.name}: deny" from config.`,
|
|
246
|
+
`Tool "${resolved.policyKey ?? this.tool.name}" is blocked by user policy.\n` +
|
|
247
|
+
`To allow: remove "tools.approval.${resolved.policyKey ?? this.tool.name}: deny" from config.`,
|
|
247
248
|
);
|
|
248
249
|
}
|
|
249
250
|
const pendingSafetyChecks = computerSafetyChecks(context);
|
|
@@ -255,7 +256,7 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
255
256
|
// and tool-demanded overrides still prompt. Provider safety checks are
|
|
256
257
|
// stronger: yolo, per-tool allow, and xdev approval never acknowledge
|
|
257
258
|
// them on the user's behalf.
|
|
258
|
-
const explicitPrompt = resolved.override || Object.hasOwn(userPolicies, this.tool.name);
|
|
259
|
+
const explicitPrompt = resolved.override || Object.hasOwn(userPolicies, resolved.policyKey ?? this.tool.name);
|
|
259
260
|
const xdevBypass = context?.xdevApproved === true && effectiveParams === params;
|
|
260
261
|
const approvalCheck = {
|
|
261
262
|
required: pendingSafetyChecks.length > 0 || (resolved.policy === "prompt" && (explicitPrompt || !xdevBypass)),
|
|
@@ -263,6 +264,11 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
263
264
|
};
|
|
264
265
|
|
|
265
266
|
if (approvalCheck.required) {
|
|
267
|
+
const scheduledCall = context?.toolCall?.toolCalls[context.toolCall.index];
|
|
268
|
+
if (scheduledCall?.id === toolCallId && scheduledCall.name === this.tool.name) {
|
|
269
|
+
await untilAborted(signal, () => this.runner.waitForToolApprovalPreview(toolCallId));
|
|
270
|
+
}
|
|
271
|
+
|
|
266
272
|
const hasApprovalHandlers =
|
|
267
273
|
this.runner.hasHandlers("tool_approval_requested") || this.runner.hasHandlers("tool_approval_resolved");
|
|
268
274
|
const sessionId = context?.sessionManager?.getSessionId() ?? "";
|
|
@@ -440,14 +440,14 @@ export class MarketplaceManager {
|
|
|
440
440
|
return "0.0.0";
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
/** Validates and removes a marketplace plugin, or only validates when `dryRun` is set. */
|
|
444
|
+
async uninstallPlugin(pluginId: string, scope?: "user" | "project", options?: { dryRun?: boolean }): Promise<void> {
|
|
444
445
|
const parsed = parsePluginId(pluginId);
|
|
445
446
|
if (!parsed) {
|
|
446
447
|
throw new Error(`Invalid plugin ID format: "${pluginId}". Expected "name@marketplace".`);
|
|
447
448
|
}
|
|
448
449
|
|
|
449
450
|
const { userEntries, projectEntries, userReg, projectReg } = await this.#findInBothRegistries(pluginId);
|
|
450
|
-
|
|
451
451
|
const inUser = userEntries && userEntries.length > 0;
|
|
452
452
|
const inProject = projectEntries && projectEntries.length > 0;
|
|
453
453
|
|
|
@@ -481,6 +481,10 @@ export class MarketplaceManager {
|
|
|
481
481
|
const registryPath = this.#registryPath(targetScope);
|
|
482
482
|
const packageNames = await this.#resolveInstalledPackageNames(targetEntries, parsed.name);
|
|
483
483
|
|
|
484
|
+
if (options?.dryRun) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
484
488
|
const updatedReg = removeInstalledPlugin(targetReg, pluginId);
|
|
485
489
|
await writeInstalledPluginsRegistry(registryPath, updatedReg);
|
|
486
490
|
|
package/src/hindsight/client.ts
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* tests to spy on.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
|
11
12
|
import { isTimeoutError, withTimeoutSignal } from "../utils/fetch-timeout";
|
|
12
13
|
import type { HindsightConfig } from "./config";
|
|
13
14
|
|
|
14
|
-
const USER_AGENT = "oh-my-pi-coding-agent";
|
|
15
15
|
const DEFAULT_USER_AGENT = USER_AGENT;
|
|
16
16
|
/** Fallback deadlines (ms) applied when the caller supplies no override. */
|
|
17
17
|
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
package/src/lib/xai-http.ts
CHANGED
|
@@ -12,10 +12,6 @@ interface XAICredentials {
|
|
|
12
12
|
baseURL: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function ohMyPiXAIUserAgent(): string {
|
|
16
|
-
return "oh-my-pi/xai";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
/** xAI provider ids supported by shared HTTP tool transport resolution. */
|
|
20
16
|
export type XAIHttpProvider = "xai-oauth" | "xai";
|
|
21
17
|
|
package/src/lsp/client.ts
CHANGED
|
@@ -1339,6 +1339,7 @@ export async function sendRequest(
|
|
|
1339
1339
|
timeout = setTimeout(() => {
|
|
1340
1340
|
if (client.pendingRequests.has(id)) {
|
|
1341
1341
|
client.pendingRequests.delete(id);
|
|
1342
|
+
void sendNotification(client, "$/cancelRequest", { id }).catch(() => {});
|
|
1342
1343
|
const err = new Error(`LSP request ${method} timed out after ${effectiveTimeoutMs}ms`);
|
|
1343
1344
|
cleanup();
|
|
1344
1345
|
reject(err);
|
|
@@ -1405,6 +1406,7 @@ export async function sendNotification(
|
|
|
1405
1406
|
* Shutdown all LSP clients.
|
|
1406
1407
|
*/
|
|
1407
1408
|
export async function shutdownAll(): Promise<void> {
|
|
1409
|
+
stopIdleChecker();
|
|
1408
1410
|
const clientsToShutdown = Array.from(clients.values());
|
|
1409
1411
|
clients.clear();
|
|
1410
1412
|
// Mid-initialize clients live only in clientLocks (publication is deferred
|
package/src/lsp/servers.ts
CHANGED
|
@@ -197,9 +197,9 @@ export function getConfig(cwd: string): LspConfig {
|
|
|
197
197
|
let config = configCache.get(cwd);
|
|
198
198
|
if (!config) {
|
|
199
199
|
config = loadConfig(cwd);
|
|
200
|
-
setIdleTimeout(config.idleTimeoutMs);
|
|
201
200
|
configCache.set(cwd, config);
|
|
202
201
|
}
|
|
202
|
+
setIdleTimeout(config.idleTimeoutMs);
|
|
203
203
|
return config;
|
|
204
204
|
}
|
|
205
205
|
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -357,6 +357,18 @@ export function createMCPToolName(serverName: string, toolName: string): string
|
|
|
357
357
|
return `mcp__${sanitizedServerName}_${normalizedToolName}`;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
export interface MCPToolOriginSource {
|
|
361
|
+
readonly name: string;
|
|
362
|
+
readonly mcpServerName?: unknown;
|
|
363
|
+
readonly mcpToolName?: unknown;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Stable identity for a tool's original MCP route, before its public name was normalized. */
|
|
367
|
+
export function getMCPToolOriginKey(tool: MCPToolOriginSource): string | undefined {
|
|
368
|
+
if (typeof tool.mcpServerName !== "string" || typeof tool.mcpToolName !== "string") return undefined;
|
|
369
|
+
return `${tool.mcpServerName}\u0000${tool.mcpToolName}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
360
372
|
/**
|
|
361
373
|
* Keeps one MCP tool per minted name and logs collisions between distinct MCP
|
|
362
374
|
* origins. The winner is chosen by a stable origin key (server name + original
|
|
@@ -365,19 +377,16 @@ export function createMCPToolName(serverName: string, toolName: string): string
|
|
|
365
377
|
* silently flip ownership of the minted name. Non-MCP tools pass through
|
|
366
378
|
* unchanged.
|
|
367
379
|
*/
|
|
368
|
-
export function deduplicateMCPToolsByName<T extends
|
|
369
|
-
tools: readonly T[],
|
|
370
|
-
): T[] {
|
|
380
|
+
export function deduplicateMCPToolsByName<T extends MCPToolOriginSource>(tools: readonly T[]): T[] {
|
|
371
381
|
const deduplicated: T[] = [];
|
|
372
382
|
const registered = new Map<string, { tool: T; originKey: string; index: number }>();
|
|
373
383
|
|
|
374
384
|
for (const tool of tools) {
|
|
375
|
-
|
|
385
|
+
const originKey = getMCPToolOriginKey(tool);
|
|
386
|
+
if (originKey === undefined) {
|
|
376
387
|
deduplicated.push(tool);
|
|
377
388
|
continue;
|
|
378
389
|
}
|
|
379
|
-
|
|
380
|
-
const originKey = `${tool.mcpServerName}\u0000${tool.mcpToolName}`;
|
|
381
390
|
const existing = registered.get(tool.name);
|
|
382
391
|
if (!existing) {
|
|
383
392
|
registered.set(tool.name, { tool, originKey, index: deduplicated.length });
|
|
@@ -95,19 +95,25 @@ function formatResolvedModelBadge(resolved: string, preserveProvider = false, fa
|
|
|
95
95
|
/**
|
|
96
96
|
* Resolved model + reasoning level for a hub row. Exact executor progress is
|
|
97
97
|
* authoritative (and survives completion); direct live sessions are the
|
|
98
|
-
* fallback for agents without an observer snapshot
|
|
98
|
+
* fallback for agents without an observer snapshot — the main session has no
|
|
99
|
+
* snapshot at all, so its row is read straight off the live session.
|
|
100
|
+
*
|
|
101
|
+
* Every source reports the model that produced the row's work, never the one
|
|
102
|
+
* the session merely points at: an armed fallback that has not served yet stays
|
|
103
|
+
* attributed to whichever model last actually spoke.
|
|
99
104
|
*/
|
|
100
105
|
export function modelBadge(ref: AgentRef, observed: ObservableSession | undefined): string | undefined {
|
|
101
106
|
const progress = observed?.progress;
|
|
102
107
|
const liveThinkingLevel = ref.session?.thinkingLevel;
|
|
108
|
+
const serving = ref.session?.servingModel;
|
|
103
109
|
const fallbackSelector =
|
|
104
|
-
|
|
110
|
+
(serving?.isFallback ? serving.selector : undefined) ??
|
|
105
111
|
(progress?.resolvedModelIsFallback ? progress.resolvedModel : undefined) ??
|
|
106
112
|
(ref.history?.resolvedModelIsFallback ? ref.history.resolvedModel : undefined);
|
|
107
113
|
if (fallbackSelector) {
|
|
108
114
|
return `${theme.fg("warning", "fallback →")} ${formatResolvedModelBadge(fallbackSelector, true, liveThinkingLevel)}`;
|
|
109
115
|
}
|
|
110
|
-
const resolvedModel = progress?.resolvedModel ?? ref.history?.resolvedModel;
|
|
116
|
+
const resolvedModel = progress?.resolvedModel ?? ref.history?.resolvedModel ?? serving?.selector;
|
|
111
117
|
if (resolvedModel) return formatResolvedModelBadge(resolvedModel, false, liveThinkingLevel);
|
|
112
118
|
const model = ref.session?.model;
|
|
113
119
|
if (!model) return undefined;
|
|
@@ -36,6 +36,7 @@ import { type AgentRef, AgentRegistry, type AgentStatus, MAIN_AGENT_ID } from ".
|
|
|
36
36
|
import { registerPersistedSubagents } from "../../registry/persisted-agents";
|
|
37
37
|
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
38
38
|
import { shortenPath, truncateToWidth } from "../../tools/render-utils";
|
|
39
|
+
import { formatLocalDateTimeWithOffset } from "../../utils/local-date";
|
|
39
40
|
import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
|
|
40
41
|
import { theme } from "../theme/theme";
|
|
41
42
|
import { matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
|
|
@@ -829,7 +830,7 @@ export class AgentHubOverlayComponent extends Container implements SelectListMou
|
|
|
829
830
|
`Spawned by ${sanitizeDisplayText(ref.parentId ?? MAIN_AGENT_ID)}${children.length > 0 ? ` · ${children.length} children` : ""}`,
|
|
830
831
|
);
|
|
831
832
|
if (children.length > 0) add(theme.fg("dim", formatChildIds(children, width)));
|
|
832
|
-
add(theme.fg("dim", `Registered ${new Date(ref.createdAt)
|
|
833
|
+
add(theme.fg("dim", `Registered ${formatLocalDateTimeWithOffset(new Date(ref.createdAt))}`));
|
|
833
834
|
|
|
834
835
|
section("Changes");
|
|
835
836
|
add(
|
|
@@ -397,6 +397,7 @@ export class StatusLineComponent implements Component {
|
|
|
397
397
|
tier?: string;
|
|
398
398
|
fiveHour?: { percent: number; resetMinutes?: number };
|
|
399
399
|
sevenDay?: { percent: number; resetHours?: number };
|
|
400
|
+
monthly?: { percent: number; resetHours?: number };
|
|
400
401
|
} | null = null;
|
|
401
402
|
#cachedUsageContextKey: string | null = null;
|
|
402
403
|
#usageFetchedAt = 0;
|
|
@@ -1258,8 +1259,12 @@ export class StatusLineComponent implements Component {
|
|
|
1258
1259
|
const normalized = this.#normalizeUsageReports(reports, activeProvider, activeIdentity);
|
|
1259
1260
|
const resetSnapshot =
|
|
1260
1261
|
activeProvider === "openai-codex" ? this.#normalizeCodexResetSnapshot(reports, activeIdentity) : null;
|
|
1262
|
+
const usageChanged = this.#cachedUsage !== normalized;
|
|
1261
1263
|
this.#cachedUsage = normalized;
|
|
1262
1264
|
this.#usageFetchedAt = Date.now();
|
|
1265
|
+
// Usage fetch is async; without a repaint the top border stays blank until
|
|
1266
|
+
// some unrelated event (git resolve, keystroke, …) rebuilds it.
|
|
1267
|
+
if (usageChanged) this.#onBranchChange?.();
|
|
1263
1268
|
if (!resetSnapshot) return;
|
|
1264
1269
|
const contextKey = this.#formatUsageContextKey(activeProvider, activeIdentity);
|
|
1265
1270
|
const previous = this.#codexResetSnapshots.get(contextKey);
|
|
@@ -1368,13 +1373,25 @@ export class StatusLineComponent implements Component {
|
|
|
1368
1373
|
tier?: string;
|
|
1369
1374
|
fiveHour?: { percent: number; resetMinutes?: number };
|
|
1370
1375
|
sevenDay?: { percent: number; resetHours?: number };
|
|
1376
|
+
monthly?: { percent: number; resetHours?: number };
|
|
1371
1377
|
} | null {
|
|
1372
1378
|
if (!Array.isArray(reports)) return null;
|
|
1373
1379
|
let fiveHour: { percent: number; resetMinutes?: number } | undefined;
|
|
1374
1380
|
let sevenDay: { percent: number; resetHours?: number } | undefined;
|
|
1381
|
+
let monthly: { percent: number; resetHours?: number } | undefined;
|
|
1375
1382
|
let fiveHourTier: string | undefined;
|
|
1376
1383
|
let sevenDayTier: string | undefined;
|
|
1384
|
+
let monthlyTier: string | undefined;
|
|
1385
|
+
let monthlyPriority = Number.POSITIVE_INFINITY;
|
|
1377
1386
|
const now = Date.now();
|
|
1387
|
+
const cursorMonthlyPriority = (limitId: unknown): number => {
|
|
1388
|
+
// When /auth/usage and /api/usage-summary are merged, prefer the personal
|
|
1389
|
+
// dashboard rails over legacy per-model request fractions.
|
|
1390
|
+
if (limitId === "cursor:usd:individual-auto") return 0;
|
|
1391
|
+
if (limitId === "cursor:usd:individual-plan" || limitId === "cursor:usd:individual-overall") return 1;
|
|
1392
|
+
if (typeof limitId === "string" && limitId.startsWith("cursor:usd:individual-")) return 2;
|
|
1393
|
+
return 3;
|
|
1394
|
+
};
|
|
1378
1395
|
for (const report of reports) {
|
|
1379
1396
|
if (!report || typeof report !== "object") continue;
|
|
1380
1397
|
const provider = (report as { provider?: unknown }).provider;
|
|
@@ -1388,8 +1405,9 @@ export class StatusLineComponent implements Component {
|
|
|
1388
1405
|
continue;
|
|
1389
1406
|
}
|
|
1390
1407
|
const l = limit as {
|
|
1408
|
+
id?: string;
|
|
1391
1409
|
scope?: { windowId?: string; tier?: string };
|
|
1392
|
-
window?: { resetsAt?: number };
|
|
1410
|
+
window?: { resetsAt?: number; durationMs?: number };
|
|
1393
1411
|
amount?: { usedFraction?: number };
|
|
1394
1412
|
};
|
|
1395
1413
|
const fraction = l.amount?.usedFraction;
|
|
@@ -1397,9 +1415,22 @@ export class StatusLineComponent implements Component {
|
|
|
1397
1415
|
const windowId = l.scope?.windowId;
|
|
1398
1416
|
const tier = l.scope?.tier;
|
|
1399
1417
|
const resetsAt = l.window?.resetsAt;
|
|
1418
|
+
// Canonical window ids win. Fall back to the reported span (same
|
|
1419
|
+
// tolerance as the 5h priority-boost check) so providers that emit
|
|
1420
|
+
// non-canonical ids, and cache rows written before a provider was
|
|
1421
|
+
// canonicalized, still map onto the two subscription windows.
|
|
1422
|
+
const durationMs = l.window?.durationMs;
|
|
1423
|
+
const windowClass =
|
|
1424
|
+
windowId === "5h" || windowId === "7d"
|
|
1425
|
+
? windowId
|
|
1426
|
+
: durationMs !== undefined && Math.abs(durationMs - 5 * 3_600_000) <= 60_000
|
|
1427
|
+
? "5h"
|
|
1428
|
+
: durationMs !== undefined && Math.abs(durationMs - 7 * 86_400_000) <= 60_000
|
|
1429
|
+
? "7d"
|
|
1430
|
+
: undefined;
|
|
1400
1431
|
// Accept tiered limits, but prefer untiered (backward compat with Anthropic).
|
|
1401
1432
|
// An untiered limit always replaces a tiered one; among same-tieredness, first wins.
|
|
1402
|
-
if (
|
|
1433
|
+
if (windowClass === "5h" && (!fiveHour || (fiveHourTier !== undefined && !tier))) {
|
|
1403
1434
|
fiveHour = {
|
|
1404
1435
|
percent: fraction * 100,
|
|
1405
1436
|
resetMinutes:
|
|
@@ -1407,7 +1438,7 @@ export class StatusLineComponent implements Component {
|
|
|
1407
1438
|
};
|
|
1408
1439
|
fiveHourTier = tier || undefined;
|
|
1409
1440
|
}
|
|
1410
|
-
if (
|
|
1441
|
+
if (windowClass === "7d" && (!sevenDay || (sevenDayTier !== undefined && !tier))) {
|
|
1411
1442
|
sevenDay = {
|
|
1412
1443
|
percent: fraction * 100,
|
|
1413
1444
|
resetHours:
|
|
@@ -1415,12 +1446,33 @@ export class StatusLineComponent implements Component {
|
|
|
1415
1446
|
};
|
|
1416
1447
|
sevenDayTier = tier || undefined;
|
|
1417
1448
|
}
|
|
1449
|
+
// Conservatively gate monthly status-line rendering to Cursor for now —
|
|
1450
|
+
// Copilot/OpenCode also emit monthly windows, but their multi-bucket
|
|
1451
|
+
// shape needs a dedicated selector before we surface `mo N%` for them.
|
|
1452
|
+
if (activeProvider === "cursor" && (windowId === "monthly" || windowId === "30d")) {
|
|
1453
|
+
const priority = cursorMonthlyPriority(l.id);
|
|
1454
|
+
const shouldReplace =
|
|
1455
|
+
!monthly ||
|
|
1456
|
+
priority < monthlyPriority ||
|
|
1457
|
+
(priority === monthlyPriority && monthlyTier !== undefined && !tier);
|
|
1458
|
+
if (shouldReplace) {
|
|
1459
|
+
monthly = {
|
|
1460
|
+
percent: fraction * 100,
|
|
1461
|
+
resetHours:
|
|
1462
|
+
typeof resetsAt === "number"
|
|
1463
|
+
? Math.max(0, Math.round((resetsAt - now) / 3_600_000))
|
|
1464
|
+
: undefined,
|
|
1465
|
+
};
|
|
1466
|
+
monthlyTier = tier || undefined;
|
|
1467
|
+
monthlyPriority = priority;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1418
1470
|
}
|
|
1419
1471
|
}
|
|
1420
|
-
if (!fiveHour && !sevenDay) return null;
|
|
1472
|
+
if (!fiveHour && !sevenDay && !monthly) return null;
|
|
1421
1473
|
// Single compact label; prefer the five-hour tier if displayed windows ever disagree.
|
|
1422
|
-
const effectiveTier = fiveHourTier ?? sevenDayTier;
|
|
1423
|
-
return { tier: effectiveTier, fiveHour, sevenDay };
|
|
1474
|
+
const effectiveTier = fiveHourTier ?? sevenDayTier ?? monthlyTier;
|
|
1475
|
+
return { tier: effectiveTier, fiveHour, sevenDay, monthly };
|
|
1424
1476
|
}
|
|
1425
1477
|
|
|
1426
1478
|
/**
|
|
@@ -639,7 +639,7 @@ const usageSegment: StatusLineSegment = {
|
|
|
639
639
|
id: "usage",
|
|
640
640
|
render(ctx) {
|
|
641
641
|
const u = ctx.usage;
|
|
642
|
-
if (!u || (!u.fiveHour && !u.sevenDay)) {
|
|
642
|
+
if (!u || (!u.fiveHour && !u.sevenDay && !u.monthly)) {
|
|
643
643
|
return { content: "", visible: false };
|
|
644
644
|
}
|
|
645
645
|
const parts: string[] = [];
|
|
@@ -665,6 +665,17 @@ const usageSegment: StatusLineSegment = {
|
|
|
665
665
|
: "";
|
|
666
666
|
parts.push(`7d ${pctText}${reset}`);
|
|
667
667
|
}
|
|
668
|
+
if (u.monthly) {
|
|
669
|
+
const pct = u.monthly.percent;
|
|
670
|
+
// Cursor-only today (normalize gates monthly to provider === "cursor").
|
|
671
|
+
// Cursor's web dashboard floors included-usage percents (1.88 → "1% used").
|
|
672
|
+
const pctText = theme.fg(pickUsageColor(pct), `${Math.floor(pct)}%`);
|
|
673
|
+
const reset =
|
|
674
|
+
u.monthly.resetHours !== undefined
|
|
675
|
+
? theme.fg("muted", ` (${formatUsageReset(u.monthly.resetHours, "h")})`)
|
|
676
|
+
: "";
|
|
677
|
+
parts.push(`mo ${pctText}${reset}`);
|
|
678
|
+
}
|
|
668
679
|
const content = withIcon(theme.icon.time, parts.join(theme.sep.dot));
|
|
669
680
|
return { content, visible: true };
|
|
670
681
|
},
|
|
@@ -4,12 +4,27 @@ import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
|
4
4
|
import { imageReferenceHyperlink, renderPlaceholders } from "../image-references";
|
|
5
5
|
import { highlightMagicKeywords } from "../magic-keywords";
|
|
6
6
|
|
|
7
|
-
// OSC 133 shell integration: marks prompt zones for terminal multiplexers
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// OSC 133 shell integration: marks prompt zones for terminal multiplexers.
|
|
8
|
+
//
|
|
9
|
+
// The zone must be *closed* within the same render. `133;B` sets a sticky
|
|
10
|
+
// cursor semantic of `.input` in Ghostty (and Ghostty-derived terminals such
|
|
11
|
+
// as cmux) that only a command-start marker clears; leaving it latched makes
|
|
12
|
+
// `cursorIsAtPrompt()` permanently true and tags every subsequently painted
|
|
13
|
+
// cell as `.input`. Combined with `cursor-click-to-move = true` (Ghostty's
|
|
14
|
+
// default) that turns every left-click inside the pane into a burst of
|
|
15
|
+
// synthesized arrow keys on omp's pty, slamming the editor caret to column 0
|
|
16
|
+
// (#8030, #6115).
|
|
17
|
+
//
|
|
18
|
+
// `133;C` is therefore emitted immediately followed by `133;D;0` at the end of
|
|
19
|
+
// the bubble. That clears the input state without reintroducing the grouping
|
|
20
|
+
// problem the marker was originally omitted to avoid: the command zone opens
|
|
21
|
+
// and finishes inside this component, so later assistant/tool output can never
|
|
22
|
+
// be grouped under the first submitted prompt.
|
|
11
23
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
12
24
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
25
|
+
const OSC133_COMMAND_START = "\x1b]133;C\x07";
|
|
26
|
+
const OSC133_COMMAND_DONE = "\x1b]133;D;0\x07";
|
|
27
|
+
const OSC133_ZONE_CLOSE = OSC133_ZONE_END + OSC133_COMMAND_START + OSC133_COMMAND_DONE;
|
|
13
28
|
|
|
14
29
|
/**
|
|
15
30
|
* Component that renders a user message
|
|
@@ -61,7 +76,7 @@ export class UserMessageComponent extends Container {
|
|
|
61
76
|
}
|
|
62
77
|
const wrapped = lines.slice();
|
|
63
78
|
wrapped[0] = OSC133_ZONE_START + wrapped[0];
|
|
64
|
-
wrapped[wrapped.length - 1] = wrapped[wrapped.length - 1] +
|
|
79
|
+
wrapped[wrapped.length - 1] = wrapped[wrapped.length - 1] + OSC133_ZONE_CLOSE;
|
|
65
80
|
this.#zoneSource = lines;
|
|
66
81
|
this.#zoneLines = wrapped;
|
|
67
82
|
return wrapped;
|
|
@@ -72,6 +72,12 @@ function exposesRawPartialJson(toolName: string, rawInput: boolean, tool: unknow
|
|
|
72
72
|
type AgentSessionEventHandlers = {
|
|
73
73
|
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
74
74
|
};
|
|
75
|
+
interface ApprovalPreviewGate {
|
|
76
|
+
promise: Promise<void>;
|
|
77
|
+
resolve(): void;
|
|
78
|
+
reject(reason?: unknown): void;
|
|
79
|
+
started: boolean;
|
|
80
|
+
}
|
|
75
81
|
|
|
76
82
|
export class EventController {
|
|
77
83
|
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
@@ -88,6 +94,8 @@ export class EventController {
|
|
|
88
94
|
/** Tool calls whose approval prompt drove the title into `attention`; cleared
|
|
89
95
|
* at their tool_execution_end so the title returns to `working`. */
|
|
90
96
|
#approvalAttentionToolCallIds = new Set<string>();
|
|
97
|
+
#approvalPreviewGates = new Map<string, ApprovalPreviewGate>();
|
|
98
|
+
#detachToolApprovalPreviewWaiter: (() => void) | undefined;
|
|
91
99
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
92
100
|
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
93
101
|
#toolTimelineComponents = new Map<string, Component>();
|
|
@@ -197,6 +205,9 @@ export class EventController {
|
|
|
197
205
|
// vocalizer falls back to mechanical cleanup when unset. Tolerates
|
|
198
206
|
// partial contexts (tests, minimal embeddings) by wiring null.
|
|
199
207
|
const session = ctx.session;
|
|
208
|
+
this.#detachToolApprovalPreviewWaiter = session?.extensionRunner?.setToolApprovalPreviewWaiter(toolCallId =>
|
|
209
|
+
this.#waitForToolApprovalPreview(toolCallId),
|
|
210
|
+
);
|
|
200
211
|
vocalizer.setEnhancer(
|
|
201
212
|
session?.modelRegistry && session.agent && session.settings
|
|
202
213
|
? new SpeechEnhancer({
|
|
@@ -273,6 +284,9 @@ export class EventController {
|
|
|
273
284
|
}
|
|
274
285
|
|
|
275
286
|
dispose(): void {
|
|
287
|
+
this.#detachToolApprovalPreviewWaiter?.();
|
|
288
|
+
this.#detachToolApprovalPreviewWaiter = undefined;
|
|
289
|
+
this.#clearApprovalPreviewGates();
|
|
276
290
|
if (this.#messageUpdateTimer) {
|
|
277
291
|
clearTimeout(this.#messageUpdateTimer);
|
|
278
292
|
this.#messageUpdateTimer = undefined;
|
|
@@ -294,6 +308,36 @@ export class EventController {
|
|
|
294
308
|
this.#lastReadGroup?.finalize();
|
|
295
309
|
this.#lastReadGroup = undefined;
|
|
296
310
|
}
|
|
311
|
+
#approvalPreviewGate(toolCallId: string): ApprovalPreviewGate {
|
|
312
|
+
let gate = this.#approvalPreviewGates.get(toolCallId);
|
|
313
|
+
if (!gate) {
|
|
314
|
+
const deferred = Promise.withResolvers<void>();
|
|
315
|
+
gate = { ...deferred, started: false };
|
|
316
|
+
this.#approvalPreviewGates.set(toolCallId, gate);
|
|
317
|
+
}
|
|
318
|
+
return gate;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async #waitForToolApprovalPreview(toolCallId: string): Promise<void> {
|
|
322
|
+
await this.#approvalPreviewGate(toolCallId).promise;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#startToolApprovalPreview(toolCallId: string): void {
|
|
326
|
+
const gate = this.#approvalPreviewGate(toolCallId);
|
|
327
|
+
if (gate.started) return;
|
|
328
|
+
gate.started = true;
|
|
329
|
+
const component = this.ctx.pendingTools.get(toolCallId);
|
|
330
|
+
const ready = component instanceof ToolExecutionComponent ? component.whenPreviewSettled() : Promise.resolve();
|
|
331
|
+
void ready.then(() => {
|
|
332
|
+
this.ctx.ui.requestRender();
|
|
333
|
+
gate.resolve();
|
|
334
|
+
}, gate.reject);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#clearApprovalPreviewGates(): void {
|
|
338
|
+
for (const gate of this.#approvalPreviewGates.values()) gate.resolve();
|
|
339
|
+
this.#approvalPreviewGates.clear();
|
|
340
|
+
}
|
|
297
341
|
|
|
298
342
|
#getReadGroup(): ReadToolGroupComponent {
|
|
299
343
|
if (!this.#lastReadGroup) {
|
|
@@ -687,6 +731,7 @@ export class EventController {
|
|
|
687
731
|
}
|
|
688
732
|
|
|
689
733
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
734
|
+
this.#clearApprovalPreviewGates();
|
|
690
735
|
this.#toolTimelineComponents.clear();
|
|
691
736
|
this.#streamedToolCallIdByIndex.clear();
|
|
692
737
|
this.#retractedToolCallIds.clear();
|
|
@@ -1313,6 +1358,7 @@ export class EventController {
|
|
|
1313
1358
|
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
1314
1359
|
this.#toolTimelineComponents.set(event.toolCallId, group);
|
|
1315
1360
|
}
|
|
1361
|
+
this.#startToolApprovalPreview(event.toolCallId);
|
|
1316
1362
|
this.ctx.ui.requestRender();
|
|
1317
1363
|
return;
|
|
1318
1364
|
}
|
|
@@ -1336,6 +1382,7 @@ export class EventController {
|
|
|
1336
1382
|
this.ctx.sessionManager.getCwd(),
|
|
1337
1383
|
event.toolCallId,
|
|
1338
1384
|
);
|
|
1385
|
+
component.setArgsComplete(event.toolCallId);
|
|
1339
1386
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1340
1387
|
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1341
1388
|
this.ctx.chatContainer.addChild(component);
|
|
@@ -1363,6 +1410,7 @@ export class EventController {
|
|
|
1363
1410
|
this.ctx.ui.requestRender();
|
|
1364
1411
|
}
|
|
1365
1412
|
}
|
|
1413
|
+
this.#startToolApprovalPreview(event.toolCallId);
|
|
1366
1414
|
}
|
|
1367
1415
|
|
|
1368
1416
|
/**
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ExtensionAskDialogResultItem,
|
|
11
11
|
ExtensionCommandContextActions,
|
|
12
12
|
ExtensionContextActions,
|
|
13
|
+
ExtensionCustomOptions,
|
|
13
14
|
ExtensionError,
|
|
14
15
|
ExtensionUIContext,
|
|
15
16
|
ExtensionUIDialogOptions,
|
|
@@ -1047,7 +1048,7 @@ export class ExtensionUiController {
|
|
|
1047
1048
|
keybindings: KeybindingsManager,
|
|
1048
1049
|
done: (result: T) => void,
|
|
1049
1050
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
1050
|
-
options?:
|
|
1051
|
+
options?: ExtensionCustomOptions,
|
|
1051
1052
|
): Promise<T> {
|
|
1052
1053
|
const savedText = this.ctx.editor.getText();
|
|
1053
1054
|
const keybindings = KeybindingsManager.inMemory();
|
|
@@ -1080,12 +1081,18 @@ export class ExtensionUiController {
|
|
|
1080
1081
|
}
|
|
1081
1082
|
component = c;
|
|
1082
1083
|
if (options?.overlay) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1084
|
+
const overlayOptions =
|
|
1085
|
+
typeof options.overlayOptions === "function" ? options.overlayOptions() : options.overlayOptions;
|
|
1086
|
+
overlayHandle = this.ctx.ui.showOverlay(
|
|
1087
|
+
component,
|
|
1088
|
+
overlayOptions ?? {
|
|
1089
|
+
anchor: "bottom-center",
|
|
1090
|
+
width: "100%",
|
|
1091
|
+
maxHeight: "100%",
|
|
1092
|
+
margin: 0,
|
|
1093
|
+
},
|
|
1094
|
+
);
|
|
1095
|
+
options.onHandle?.(overlayHandle);
|
|
1089
1096
|
return;
|
|
1090
1097
|
}
|
|
1091
1098
|
this.ctx.editorContainer.clear();
|
|
@@ -23,6 +23,7 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
23
23
|
import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
|
|
24
24
|
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
25
25
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
26
|
+
import { parseSlashCommand } from "../../slash-commands/helpers/parse";
|
|
26
27
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
27
28
|
import { tinyTitleClient } from "../../tiny/title-client";
|
|
28
29
|
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
@@ -678,6 +679,7 @@ export class InputController {
|
|
|
678
679
|
let inputImageLinks =
|
|
679
680
|
this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
|
|
680
681
|
let hasInputImages = (inputImages?.length ?? 0) > 0;
|
|
682
|
+
const submittedImages = inputImages;
|
|
681
683
|
|
|
682
684
|
if (runner?.hasHandlers("input")) {
|
|
683
685
|
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
@@ -697,6 +699,22 @@ export class InputController {
|
|
|
697
699
|
}
|
|
698
700
|
hasInputImages = (inputImages?.length ?? 0) > 0;
|
|
699
701
|
}
|
|
702
|
+
const submittedMode = parseSlashCommand(text)?.name;
|
|
703
|
+
const draftDetached =
|
|
704
|
+
submittedMode === "plan" ||
|
|
705
|
+
submittedMode === "vibe" ||
|
|
706
|
+
submittedMode === "goal" ||
|
|
707
|
+
submittedMode === "guided-goal";
|
|
708
|
+
if (
|
|
709
|
+
draftDetached &&
|
|
710
|
+
submittedImages?.length &&
|
|
711
|
+
submittedImages.every((image, index) => this.ctx.editor.pendingImages[index] === image)
|
|
712
|
+
) {
|
|
713
|
+
this.ctx.editor.pendingImages.splice(0, submittedImages.length);
|
|
714
|
+
this.ctx.editor.pendingImageLinks.splice(0, submittedImages.length);
|
|
715
|
+
this.ctx.editor.imageLinks =
|
|
716
|
+
this.ctx.editor.pendingImageLinks.length > 0 ? this.ctx.editor.pendingImageLinks : undefined;
|
|
717
|
+
}
|
|
700
718
|
|
|
701
719
|
if (!text && !hasInputImages) return;
|
|
702
720
|
|
|
@@ -712,9 +730,11 @@ export class InputController {
|
|
|
712
730
|
|
|
713
731
|
// Handle built-in slash commands
|
|
714
732
|
if (text) {
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
733
|
+
const input =
|
|
734
|
+
(inputImages?.length ?? 0) > 0 || (inputImageLinks?.length ?? 0) > 0
|
|
735
|
+
? { images: inputImages, imageLinks: inputImageLinks }
|
|
736
|
+
: undefined;
|
|
737
|
+
const slashResult = await executeBuiltinSlashCommand(text, { ctx: this.ctx, input, draftDetached });
|
|
718
738
|
if (slashResult === true) {
|
|
719
739
|
if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
|
|
720
740
|
return;
|
|
@@ -1330,9 +1350,8 @@ export class InputController {
|
|
|
1330
1350
|
}
|
|
1331
1351
|
|
|
1332
1352
|
if (text) {
|
|
1333
|
-
const
|
|
1334
|
-
|
|
1335
|
-
});
|
|
1353
|
+
const input = (images?.length ?? 0) > 0 || (imageLinks?.length ?? 0) > 0 ? { images, imageLinks } : undefined;
|
|
1354
|
+
const slashResult = await executeBuiltinSlashCommand(text, { ctx: this.ctx, input });
|
|
1336
1355
|
if (slashResult === true) {
|
|
1337
1356
|
if (!shouldSkipHistory(text)) this.ctx.editor.addToHistory(text);
|
|
1338
1357
|
return;
|