mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Inline low-value source helpers
This commit is contained in:
parent
7a9f179020
commit
582bc0195d
49 changed files with 194 additions and 574 deletions
|
|
@ -95,7 +95,7 @@ function hookItemFromCatalogHook(hook: CatalogHookMetadata): HookItem {
|
|||
enabled: hook.enabled,
|
||||
isManaged: hook.isManaged,
|
||||
currentHash: hook.currentHash,
|
||||
trustStatus: hookTrustStatusFromCatalogTrustStatus(hook.trustStatus),
|
||||
trustStatus: hook.trustStatus,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -107,14 +107,6 @@ export function appServerHookOperationFromHookItem(hook: HookItem): AppServerHoo
|
|||
return {
|
||||
key: hook.key,
|
||||
currentHash: hook.currentHash,
|
||||
trustStatus: appServerHookTrustStatus(hook.trustStatus),
|
||||
trustStatus: hook.trustStatus,
|
||||
};
|
||||
}
|
||||
|
||||
function hookTrustStatusFromCatalogTrustStatus(status: AppServerHookTrustStatus): HookItem["trustStatus"] {
|
||||
return status;
|
||||
}
|
||||
|
||||
function appServerHookTrustStatus(status: HookItem["trustStatus"]): AppServerHookTrustStatus {
|
||||
return status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,11 +50,5 @@ function appServerUserInputItemFromCodexInputItem(item: CodexInputItem): AppServ
|
|||
function appServerImageDetailProp(detail: Extract<CodexInputItem, { type: "image" | "localImage" }>["detail"]): {
|
||||
detail?: AppServerUserInputImageDetail;
|
||||
} {
|
||||
return detail === undefined ? {} : { detail: appServerUserInputImageDetail(detail) };
|
||||
}
|
||||
|
||||
function appServerUserInputImageDetail(
|
||||
detail: NonNullable<Extract<CodexInputItem, { type: "image" | "localImage" }>["detail"]>,
|
||||
): AppServerUserInputImageDetail {
|
||||
return detail;
|
||||
return detail === undefined ? {} : { detail };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ export interface ConfigReadResult {
|
|||
}
|
||||
|
||||
export function runtimeConfigSnapshotFromAppServerConfig(response: ConfigReadResult): RuntimeConfigSnapshot {
|
||||
const config = asRecord(response.config);
|
||||
const rawConfig = response.config;
|
||||
const config = rawConfig && typeof rawConfig === "object" ? (rawConfig as Record<string, unknown>) : {};
|
||||
const effort = config["model_reasoning_effort"];
|
||||
return {
|
||||
profile: selectedConfigProfile(response.layers),
|
||||
|
|
@ -32,10 +33,6 @@ export function runtimeConfigSnapshotFromAppServerConfig(response: ConfigReadRes
|
|||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function selectedConfigProfile(layers: ConfigReadResult["layers"]): string | null {
|
||||
let selected: string | null = null;
|
||||
if (!layers) return null;
|
||||
|
|
|
|||
|
|
@ -39,19 +39,15 @@ function rateLimitSnapshotFromAppServerSnapshot(snapshot: AppServerRateLimitSnap
|
|||
}
|
||||
|
||||
export function rateLimitSnapshotFromAccountRateLimitsResponse(response: AppServerAccountRateLimitsResponse): RateLimitSnapshot {
|
||||
return rateLimitSnapshotFromAppServerSnapshot(accountRateLimitSnapshotFromResponse(response));
|
||||
const snapshots = response.rateLimitsByLimitId;
|
||||
const snapshot = (snapshots && Object.hasOwn(snapshots, "codex") ? snapshots["codex"] : undefined) ?? response.rateLimits;
|
||||
return rateLimitSnapshotFromAppServerSnapshot(snapshot);
|
||||
}
|
||||
|
||||
export function accountRateLimitsSummaryFromResponse(response: AppServerAccountRateLimitsResponse): string {
|
||||
return response.rateLimitsByLimitId ? `${String(Object.keys(response.rateLimitsByLimitId).length)} limits` : "available";
|
||||
}
|
||||
|
||||
function accountRateLimitSnapshotFromResponse(response: AppServerAccountRateLimitsResponse): AppServerRateLimitSnapshot {
|
||||
const snapshots = response.rateLimitsByLimitId;
|
||||
const codexRateLimit = snapshots && Object.hasOwn(snapshots, "codex") ? snapshots["codex"] : undefined;
|
||||
return codexRateLimit ?? response.rateLimits;
|
||||
}
|
||||
|
||||
function rateLimitWindowFromAppServerWindow(window: AppServerRateLimitWindow): RateLimitWindow {
|
||||
return {
|
||||
usedPercent: window.usedPercent,
|
||||
|
|
|
|||
|
|
@ -748,14 +748,7 @@ function bigintToNumberOrNull(value: unknown): number | null {
|
|||
|
||||
function toJsonContent(content: Record<string, McpElicitationContentValue> | null): unknown {
|
||||
if (!content) return null;
|
||||
return Object.fromEntries(Object.entries(content).map(([key, value]) => [key, toJsonValue(value)]));
|
||||
}
|
||||
|
||||
function toJsonValue(value: McpElicitationContentValue): unknown {
|
||||
if (isReadonlyStringArray(value)) return [...value];
|
||||
return value;
|
||||
}
|
||||
|
||||
function isReadonlyStringArray(value: McpElicitationContentValue): value is readonly string[] {
|
||||
return Array.isArray(value);
|
||||
return Object.fromEntries(
|
||||
Object.entries(content).map(([key, value]) => [key, Array.isArray(value) ? Array.from(value as readonly string[]) : value]),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function threadGoalFromAppServerGoal(goal: AppServerThreadGoal | null): T
|
|||
return {
|
||||
threadId: goal.threadId,
|
||||
objective: goal.objective,
|
||||
status: threadGoalStatusFromAppServerStatus(goal.status),
|
||||
status: goal.status,
|
||||
tokenBudget: goal.tokenBudget,
|
||||
tokensUsed: finiteNumber(goal.tokensUsed),
|
||||
timeUsedSeconds: finiteNumber(goal.timeUsedSeconds),
|
||||
|
|
@ -35,7 +35,7 @@ export function appServerThreadGoalUpdate(update: ThreadGoalUpdate): {
|
|||
} {
|
||||
return {
|
||||
...("objective" in update ? { objective: update.objective } : {}),
|
||||
...("status" in update ? { status: update.status === null ? null : appServerThreadGoalStatus(update.status) } : {}),
|
||||
...("status" in update ? { status: update.status === null ? null : update.status } : {}),
|
||||
...("tokenBudget" in update ? { tokenBudget: update.tokenBudget } : {}),
|
||||
};
|
||||
}
|
||||
|
|
@ -48,14 +48,6 @@ export function appServerThreadGoalUserHistoryItem(text: string): AppServerJsonV
|
|||
};
|
||||
}
|
||||
|
||||
function threadGoalStatusFromAppServerStatus(status: AppServerThreadGoalStatus): ThreadGoalStatus {
|
||||
return status;
|
||||
}
|
||||
|
||||
function appServerThreadGoalStatus(status: ThreadGoalStatus): AppServerThreadGoalStatus {
|
||||
return status;
|
||||
}
|
||||
|
||||
function finiteNumber(value: number): number {
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ export interface ThreadRecord {
|
|||
}
|
||||
|
||||
export function threadFromThreadRecord(thread: ThreadRecord, options: { archived?: boolean } = {}): Thread {
|
||||
const hasRecencyAt = Object.hasOwn(thread, "recencyAt");
|
||||
const recencyAt = hasRecencyAt ? thread.recencyAt : undefined;
|
||||
return {
|
||||
id: thread.id,
|
||||
preview: normalizeString(thread.preview),
|
||||
name: normalizeNullableString(thread.name),
|
||||
name: thread.name === null ? null : normalizeString(thread.name),
|
||||
archived: options.archived ?? false,
|
||||
createdAt: finiteTimestamp(thread.createdAt),
|
||||
updatedAt: finiteTimestamp(thread.updatedAt),
|
||||
...recencyAtPatch(thread),
|
||||
...(hasRecencyAt ? { recencyAt: typeof recencyAt === "number" && Number.isFinite(recencyAt) ? recencyAt : null } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -26,10 +28,6 @@ export function threadsFromThreadRecords(threads: readonly ThreadRecord[], optio
|
|||
return threads.map((thread) => threadFromThreadRecord(thread, options));
|
||||
}
|
||||
|
||||
function normalizeNullableString(value: string | null): string | null {
|
||||
return value === null ? null : normalizeString(value);
|
||||
}
|
||||
|
||||
function normalizeString(value: string): string {
|
||||
return typeof value === "string" ? value : "";
|
||||
}
|
||||
|
|
@ -37,9 +35,3 @@ function normalizeString(value: string): string {
|
|||
function finiteTimestamp(value: number): number {
|
||||
return Number.isFinite(value) ? value : 0;
|
||||
}
|
||||
|
||||
function recencyAtPatch(thread: ThreadRecord): { recencyAt: number | null } | Record<string, never> {
|
||||
if (!Object.hasOwn(thread, "recencyAt")) return {};
|
||||
const value = thread.recencyAt;
|
||||
return { recencyAt: typeof value === "number" && Number.isFinite(value) ? value : null };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,12 +154,10 @@ export function completedConversationSummariesFromTurnRecords(turns: readonly Tu
|
|||
});
|
||||
}
|
||||
|
||||
function conversationSummariesFromTurnRecords(turns: readonly TurnRecord[]): ThreadConversationSummary[] {
|
||||
return nonEmptyConversationSummaries(turns.map(conversationSummaryFromTurnRecord));
|
||||
}
|
||||
|
||||
export function chronologicalConversationSummariesFromTurnRecords(turns: readonly TurnRecord[]): ThreadConversationSummary[] {
|
||||
return conversationSummariesFromTurnRecords(chronologicalTurnRecords(turns));
|
||||
return nonEmptyConversationSummaries(
|
||||
[...turns].sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0)).map(conversationSummaryFromTurnRecord),
|
||||
);
|
||||
}
|
||||
|
||||
export function turnUserItemText(item: Extract<TurnItem, { type: "userMessage" }>): string {
|
||||
|
|
@ -206,7 +204,3 @@ function userInputText(content: readonly AppServerUserInput[]): string {
|
|||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function chronologicalTurnRecords(turns: readonly TurnRecord[]): TurnRecord[] {
|
||||
return [...turns].sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ export async function readSkillMetadataProbe(
|
|||
vaultPath: string,
|
||||
forceReload = false,
|
||||
): Promise<SkillMetadataProbeResult> {
|
||||
if (!client) return disconnectedSkillsResult();
|
||||
if (!client) {
|
||||
return { value: [], probe: diagnosticProbeError("skills/list", new Error("Codex app-server is not connected."), Date.now()) };
|
||||
}
|
||||
try {
|
||||
const catalog = await listSkillCatalog(client, vaultPath, { forceReload });
|
||||
return { value: catalog.skills, probe: diagnosticProbeOk("skills/list", `${String(catalog.totalCount)} skills`, Date.now()) };
|
||||
|
|
@ -28,7 +30,12 @@ export async function readSkillMetadataProbe(
|
|||
}
|
||||
|
||||
export async function readRateLimitMetadataProbe(client: AppServerClient | null): Promise<RateLimitMetadataProbeResult> {
|
||||
if (!client) return disconnectedRateLimitResult();
|
||||
if (!client) {
|
||||
return {
|
||||
value: null,
|
||||
probe: diagnosticProbeError("account/rateLimits/read", new Error("Codex app-server is not connected."), Date.now()),
|
||||
};
|
||||
}
|
||||
try {
|
||||
const response = await client.readAccountRateLimits();
|
||||
return {
|
||||
|
|
@ -39,14 +46,3 @@ export async function readRateLimitMetadataProbe(client: AppServerClient | null)
|
|||
return { value: null, probe: diagnosticProbeError("account/rateLimits/read", error, Date.now()) };
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectedSkillsResult(): SkillMetadataProbeResult {
|
||||
return { value: [], probe: diagnosticProbeError("skills/list", new Error("Codex app-server is not connected."), Date.now()) };
|
||||
}
|
||||
|
||||
function disconnectedRateLimitResult(): RateLimitMetadataProbeResult {
|
||||
return {
|
||||
value: null,
|
||||
probe: diagnosticProbeError("account/rateLimits/read", new Error("Codex app-server is not connected."), Date.now()),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ModelMetadata, SkillMetadata } from "../../domain/catalog/metadata";
|
||||
import type { ModelMetadata } from "../../domain/catalog/metadata";
|
||||
import { cloneRuntimeConfigSnapshot } from "../../domain/runtime/config";
|
||||
import type { RateLimitSnapshot } from "../../domain/runtime/metrics";
|
||||
import type { SharedServerMetadata } from "../../domain/server/metadata";
|
||||
|
|
@ -25,7 +25,7 @@ export function cloneSharedServerMetadata(metadata: SharedServerMetadata): Share
|
|||
runtimeConfig: metadata.runtimeConfig ? cloneRuntimeConfigSnapshot(metadata.runtimeConfig) : null,
|
||||
rateLimit: metadata.rateLimit ? cloneRateLimitSnapshot(metadata.rateLimit) : null,
|
||||
availableModels: cloneModelMetadata(metadata.availableModels),
|
||||
availableSkills: cloneSkillMetadata(metadata.availableSkills),
|
||||
availableSkills: metadata.availableSkills.map((skill) => ({ ...skill })),
|
||||
serverDiagnostics: {
|
||||
probes: { ...metadata.serverDiagnostics.probes },
|
||||
mcpServers: metadata.serverDiagnostics.mcpServers.map((server) => ({ ...server })),
|
||||
|
|
@ -42,7 +42,3 @@ function cloneRateLimitSnapshot(snapshot: RateLimitSnapshot): RateLimitSnapshot
|
|||
individualLimit: snapshot.individualLimit ? { ...snapshot.individualLimit } : null,
|
||||
};
|
||||
}
|
||||
|
||||
function cloneSkillMetadata(skills: readonly SkillMetadata[]): SkillMetadata[] {
|
||||
return skills.map((skill) => ({ ...skill }));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,18 +22,15 @@ export function isTurnScopedMessageForIdleActiveThread(messageScope: MessageScop
|
|||
}
|
||||
|
||||
export function fallbackMessageScope(message: { params?: unknown }): MessageScope {
|
||||
const params = messageParams(message);
|
||||
const rawParams = message.params;
|
||||
const params =
|
||||
rawParams !== null && typeof rawParams === "object" && !Array.isArray(rawParams) ? (rawParams as Record<string, unknown>) : null;
|
||||
return {
|
||||
threadId: stringParam(params, "threadId"),
|
||||
turnId: stringParam(params, "turnId"),
|
||||
};
|
||||
}
|
||||
|
||||
function messageParams(message: { params?: unknown }): Record<string, unknown> | null {
|
||||
const params = message.params;
|
||||
return params !== null && typeof params === "object" && !Array.isArray(params) ? (params as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function stringParam(params: Record<string, unknown> | null, key: string): string | null {
|
||||
const value = params?.[key];
|
||||
return typeof value === "string" && value.length > 0 ? value : null;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import {
|
|||
threadTitleFromGeneratedText,
|
||||
threadTitlePrompt,
|
||||
} from "../../domain/threads/title-generation-model";
|
||||
import type { ModelMetadataClient } from "../catalog";
|
||||
import { conversationAssistantTextFromTurnRecord, type TurnRecord } from "../protocol/turn";
|
||||
import { conversationAssistantTextFromTurnRecord } from "../protocol/turn";
|
||||
import {
|
||||
type EphemeralStructuredTurnClientFactory,
|
||||
runEphemeralStructuredTurn,
|
||||
|
|
@ -60,17 +59,10 @@ export async function generateThreadTitleWithCodex(
|
|||
serverRequests: { kind: "reject", message: "Thread title generation does not handle server requests." },
|
||||
exitedMessage: "Codex title generation app-server exited.",
|
||||
timedOutMessage: "Timed out while generating a Codex thread title.",
|
||||
resolveRuntime: (client) => threadTitleRuntimeOverrideForClient(client, runtimeSettings),
|
||||
resolveRuntime: (client) =>
|
||||
resolvedRuntimeOverrideForClient(client, { model: runtimeSettings.threadNamingModel, effort: runtimeSettings.threadNamingEffort }),
|
||||
clientFactory,
|
||||
});
|
||||
return threadTitleFromGenerationTurn(turn);
|
||||
}
|
||||
|
||||
function threadTitleFromGenerationTurn(turn: TurnRecord): string | null {
|
||||
const response = conversationAssistantTextFromTurnRecord(turn);
|
||||
return response ? threadTitleFromGeneratedText(response) : null;
|
||||
}
|
||||
|
||||
async function threadTitleRuntimeOverrideForClient(client: ModelMetadataClient, settings: ThreadTitleRuntimeSettings) {
|
||||
return resolvedRuntimeOverrideForClient(client, { model: settings.threadNamingModel, effort: settings.threadNamingEffort });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,12 +44,8 @@ export interface HookItem {
|
|||
|
||||
export type ReasoningEffort = string;
|
||||
|
||||
function nonEmptyString(value: unknown): value is string {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
export function normalizeReasoningEffort(value: unknown): ReasoningEffort | null {
|
||||
return nonEmptyString(value) ? value.trim() : null;
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||
}
|
||||
|
||||
export function supportedEffortsForModelMetadata(model: ModelMetadata | null): ReasoningEffort[] {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function explicitThreadName(thread: Thread): string | null {
|
|||
}
|
||||
|
||||
export function normalizeExplicitThreadName(value: string | null | undefined): string | null {
|
||||
const name = typeof value === "string" ? normalizeTitle(value) : "";
|
||||
const name = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
||||
return name.length > 0 ? name : null;
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,3 @@ export function upsertThread(threads: readonly Thread[], thread: Thread): Thread
|
|||
export function threadRecencyAt(thread: Thread): number {
|
||||
return thread.recencyAt ?? thread.updatedAt;
|
||||
}
|
||||
|
||||
function normalizeTitle(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ export function threadArchiveTitle(thread: Thread): string {
|
|||
}
|
||||
|
||||
export function threadArchiveDisplayTitle(thread: Thread): string {
|
||||
return truncateThreadTitle(threadArchiveTitle(thread), MAX_ARCHIVED_THREAD_DISPLAY_TITLE_LENGTH);
|
||||
const title = threadArchiveTitle(thread);
|
||||
if (title.length <= MAX_ARCHIVED_THREAD_DISPLAY_TITLE_LENGTH) return title;
|
||||
return `${title.slice(0, MAX_ARCHIVED_THREAD_DISPLAY_TITLE_LENGTH - 3).trimEnd()}...`;
|
||||
}
|
||||
|
||||
export function threadWindowTitle(activeThreadId: string | null, threads: readonly Thread[], fallbackTitle?: string | null): string {
|
||||
|
|
@ -45,8 +47,3 @@ export function threadWindowTitle(activeThreadId: string | null, threads: readon
|
|||
function normalizeThreadTitleText(value: string | null | undefined): string {
|
||||
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
||||
}
|
||||
|
||||
function truncateThreadTitle(value: string, maxLength: number): string {
|
||||
if (value.length <= maxLength) return value;
|
||||
return `${value.slice(0, maxLength - 3).trimEnd()}...`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import {
|
|||
type ApprovalAction,
|
||||
contentForPendingMcpElicitation,
|
||||
type McpElicitationAction,
|
||||
type PendingApproval,
|
||||
type PendingMcpElicitation,
|
||||
type PendingRequestId,
|
||||
type PendingUserInput,
|
||||
} from "../../../../domain/pending-requests/model";
|
||||
|
|
@ -32,34 +30,6 @@ import type { AppServerResourceEvent } from "../actions/metadata";
|
|||
import { classifyAppServerLog } from "./app-server-logs";
|
||||
import { type ChatNotificationEffect, planChatNotification } from "./notification-plan";
|
||||
|
||||
function cannotSendApprovalResponseMessage(): string {
|
||||
return "Could not send approval response because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
function cannotSendUserInputMessage(): string {
|
||||
return "Could not send user input because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
function cannotCancelUserInputMessage(): string {
|
||||
return "Could not cancel user input because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
function cannotSendMcpElicitationMessage(): string {
|
||||
return "Could not send MCP request response because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
function cannotSendCurrentTimeMessage(): string {
|
||||
return "Could not send current time because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
function userCancelledInputRequestMessage(): string {
|
||||
return "User cancelled input request.";
|
||||
}
|
||||
|
||||
function cannotRejectServerRequestMessage(): string {
|
||||
return "Could not reject app-server request because Codex app-server is not connected.";
|
||||
}
|
||||
|
||||
export interface ChatInboundHandlerActions {
|
||||
refreshActiveThreads: () => void;
|
||||
refreshServerDiagnostics: (options?: { forceResourceProbes?: boolean }) => void;
|
||||
|
|
@ -144,29 +114,34 @@ function handleNotification(context: ChatInboundHandlerContext, notification: Se
|
|||
}
|
||||
|
||||
function handleServerRequest(context: ChatInboundHandlerContext, request: ServerRequest): void {
|
||||
const route = routeServerRequest(request, activeRouteScope(context));
|
||||
const current = state(context);
|
||||
const route = routeServerRequest(request, { activeThreadId: current.activeThread.id, activeTurnId: activeTurnId(current) });
|
||||
switch (route.kind) {
|
||||
case "approval":
|
||||
queueApprovalRequest(context, route.approval);
|
||||
dispatch(context, { type: "request/approval-queued", approval: route.approval });
|
||||
return;
|
||||
case "userInput":
|
||||
queueUserInputRequest(context, route.input);
|
||||
dispatch(context, { type: "request/user-input-queued", input: route.input });
|
||||
return;
|
||||
case "mcpElicitation":
|
||||
queueMcpElicitationRequest(context, route.elicitation);
|
||||
dispatch(context, { type: "request/mcp-elicitation-queued", elicitation: route.elicitation });
|
||||
return;
|
||||
case "currentTime":
|
||||
respondToCurrentTimeRequest(context, route.request);
|
||||
if (!context.actions.respondToServerRequest(route.request.id, serverRequestCurrentTimeResponse(Date.now()))) {
|
||||
addSystemMessage(context, "Could not send current time because Codex app-server is not connected.");
|
||||
}
|
||||
return;
|
||||
case "inactive":
|
||||
rejectServerRequest(context, request, `Rejected inactive app-server request: ${request.method}`);
|
||||
return;
|
||||
case "unsupported":
|
||||
rejectUnsupportedServerRequest(context, request);
|
||||
rejectServerRequest(context, request, `Rejected unsupported app-server request: ${request.method}`);
|
||||
return;
|
||||
case "unknown":
|
||||
rejectUnknownServerRequest(context, request);
|
||||
case "unknown": {
|
||||
const message = `Rejected unknown app-server request: ${request.method}`;
|
||||
context.actions.rejectServerRequest(request.id, -32601, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,10 +156,10 @@ function handleAppServerLog(context: ChatInboundHandlerContext, message: string)
|
|||
}
|
||||
|
||||
function resolveApproval(context: ChatInboundHandlerContext, requestId: PendingRequestId, action: ApprovalAction): void {
|
||||
const approval = pendingApproval(context, requestId);
|
||||
const approval = state(context).requests.approvals.find((item) => item.requestId === requestId) ?? null;
|
||||
if (!approval) return;
|
||||
if (!context.actions.respondToServerRequest(approval.requestId, serverRequestApprovalResponse(approval, action))) {
|
||||
addSystemMessage(context, cannotSendApprovalResponseMessage());
|
||||
addSystemMessage(context, "Could not send approval response because Codex app-server is not connected.");
|
||||
return;
|
||||
}
|
||||
dispatch(context, { type: "request/resolved", requestId: approval.requestId, resultItem: createApprovalResultItem(approval, action) });
|
||||
|
|
@ -194,7 +169,7 @@ function resolveUserInput(context: ChatInboundHandlerContext, requestId: Pending
|
|||
const input = pendingUserInput(context, requestId);
|
||||
if (!input) return;
|
||||
if (!context.actions.respondToServerRequest(input.requestId, serverRequestUserInputResponse(input.params.questions, answers))) {
|
||||
addSystemMessage(context, cannotSendUserInputMessage());
|
||||
addSystemMessage(context, "Could not send user input because Codex app-server is not connected.");
|
||||
return;
|
||||
}
|
||||
dispatch(context, {
|
||||
|
|
@ -207,8 +182,8 @@ function resolveUserInput(context: ChatInboundHandlerContext, requestId: Pending
|
|||
function cancelUserInput(context: ChatInboundHandlerContext, requestId: PendingRequestId): void {
|
||||
const input = pendingUserInput(context, requestId);
|
||||
if (!input) return;
|
||||
if (!context.actions.rejectServerRequest(input.requestId, -32000, userCancelledInputRequestMessage())) {
|
||||
addSystemMessage(context, cannotCancelUserInputMessage());
|
||||
if (!context.actions.rejectServerRequest(input.requestId, -32000, "User cancelled input request.")) {
|
||||
addSystemMessage(context, "Could not cancel user input because Codex app-server is not connected.");
|
||||
return;
|
||||
}
|
||||
dispatch(context, {
|
||||
|
|
@ -219,11 +194,11 @@ function cancelUserInput(context: ChatInboundHandlerContext, requestId: PendingR
|
|||
}
|
||||
|
||||
function resolveMcpElicitation(context: ChatInboundHandlerContext, requestId: PendingRequestId, action: McpElicitationAction): void {
|
||||
const elicitation = pendingMcpElicitation(context, requestId);
|
||||
const elicitation = state(context).requests.pendingMcpElicitations.find((item) => item.requestId === requestId) ?? null;
|
||||
if (!elicitation) return;
|
||||
const content = action === "accept" ? contentForPendingMcpElicitation(elicitation, state(context).requests.mcpElicitationDrafts) : null;
|
||||
if (!context.actions.respondToServerRequest(elicitation.requestId, serverRequestMcpElicitationResponse(action, content))) {
|
||||
addSystemMessage(context, cannotSendMcpElicitationMessage());
|
||||
addSystemMessage(context, "Could not send MCP request response because Codex app-server is not connected.");
|
||||
return;
|
||||
}
|
||||
dispatch(context, {
|
||||
|
|
@ -233,27 +208,10 @@ function resolveMcpElicitation(context: ChatInboundHandlerContext, requestId: Pe
|
|||
});
|
||||
}
|
||||
|
||||
function respondToCurrentTimeRequest(
|
||||
context: ChatInboundHandlerContext,
|
||||
request: Extract<ServerRequest, { method: "currentTime/read" }>,
|
||||
): void {
|
||||
if (!context.actions.respondToServerRequest(request.id, serverRequestCurrentTimeResponse(Date.now()))) {
|
||||
addSystemMessage(context, cannotSendCurrentTimeMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function pendingApproval(context: ChatInboundHandlerContext, requestId: PendingRequestId): PendingApproval | null {
|
||||
return state(context).requests.approvals.find((approval) => approval.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function pendingUserInput(context: ChatInboundHandlerContext, requestId: PendingRequestId): PendingUserInput | null {
|
||||
return state(context).requests.pendingUserInputs.find((input) => input.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function pendingMcpElicitation(context: ChatInboundHandlerContext, requestId: PendingRequestId): PendingMcpElicitation | null {
|
||||
return state(context).requests.pendingMcpElicitations.find((elicitation) => elicitation.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function addSystemMessage(context: ChatInboundHandlerContext, text: string): void {
|
||||
dispatch(context, { type: "message-stream/system-item-added", item: createSystemItem(localItemId(context, "system"), text) });
|
||||
}
|
||||
|
|
@ -269,40 +227,10 @@ function addDedupedSystemMessage(context: ChatInboundHandlerContext, text: strin
|
|||
dispatch(context, { type: "message-stream/deduped-log-added", text, item: createSystemItem(localItemId(context, "system"), text) });
|
||||
}
|
||||
|
||||
function queueApprovalRequest(context: ChatInboundHandlerContext, approval: PendingApproval): void {
|
||||
dispatch(context, { type: "request/approval-queued", approval });
|
||||
}
|
||||
|
||||
function queueUserInputRequest(context: ChatInboundHandlerContext, userInput: PendingUserInput): void {
|
||||
dispatch(context, { type: "request/user-input-queued", input: userInput });
|
||||
}
|
||||
|
||||
function queueMcpElicitationRequest(context: ChatInboundHandlerContext, elicitation: PendingMcpElicitation): void {
|
||||
dispatch(context, { type: "request/mcp-elicitation-queued", elicitation });
|
||||
}
|
||||
|
||||
function activeRouteScope(context: ChatInboundHandlerContext): { activeThreadId: string | null; activeTurnId: string | null } {
|
||||
const current = state(context);
|
||||
return {
|
||||
activeThreadId: current.activeThread.id,
|
||||
activeTurnId: activeTurnId(current),
|
||||
};
|
||||
}
|
||||
|
||||
function rejectUnsupportedServerRequest(context: ChatInboundHandlerContext, request: ServerRequest): void {
|
||||
const message = `Rejected unsupported app-server request: ${request.method}`;
|
||||
rejectServerRequest(context, request, message);
|
||||
}
|
||||
|
||||
function rejectUnknownServerRequest(context: ChatInboundHandlerContext, request: ServerRequest): void {
|
||||
const message = `Rejected unknown app-server request: ${request.method}`;
|
||||
context.actions.rejectServerRequest(request.id, -32601, message);
|
||||
}
|
||||
|
||||
function rejectServerRequest(context: ChatInboundHandlerContext, request: ServerRequest, message: string): void {
|
||||
addSystemMessage(context, message);
|
||||
if (!context.actions.rejectServerRequest(request.id, -32601, message)) {
|
||||
addSystemMessage(context, cannotRejectServerRequestMessage());
|
||||
addSystemMessage(context, "Could not reject app-server request because Codex app-server is not connected.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -437,7 +437,10 @@ function autoApprovalReviewPlan(
|
|||
const reviewItem = createAutoReviewResultItem(notification.params);
|
||||
return actionPlan({
|
||||
type: "message-stream/items-replaced",
|
||||
items: upsertMessageStreamItemById(removeUnstructuredAutoReviewWarnings(messageStreamItems(state.messageStream)), reviewItem),
|
||||
items: upsertMessageStreamItemById(
|
||||
messageStreamItems(state.messageStream).filter((item) => !isUnstructuredAutoReviewWarning(item)),
|
||||
reviewItem,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -526,10 +529,6 @@ function messageStreamItemsWithPendingPromptSubmitHooks(state: ChatState, turnId
|
|||
return attachHookRunsToTurn(items, turnId, pending.promptSubmitHookItemIds, pending.anchorItemId);
|
||||
}
|
||||
|
||||
function removeUnstructuredAutoReviewWarnings(items: readonly MessageStreamItem[]): MessageStreamItem[] {
|
||||
return items.filter((item) => !isUnstructuredAutoReviewWarning(item));
|
||||
}
|
||||
|
||||
function hasStructuredAutoReviewResult(items: readonly MessageStreamItem[], activeTurnId: string | null): boolean {
|
||||
return items.some(
|
||||
(item) =>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
executionStateFromStatus,
|
||||
RUNNING_EXECUTION_STATE,
|
||||
} from "../../../domain/message-stream/execution-state";
|
||||
import type { ExecutionState, HookMessageStreamItem } from "../../../domain/message-stream/items";
|
||||
import type { HookMessageStreamItem } from "../../../domain/message-stream/items";
|
||||
|
||||
interface MessageStreamHookRun {
|
||||
id: string;
|
||||
|
|
@ -24,8 +24,9 @@ const HOOK_RUN_STATES: ExecutionStateByStatus = {
|
|||
|
||||
export function hookRunMessageStreamItem(run: MessageStreamHookRun, turnId: string | null, status: string): HookMessageStreamItem | null {
|
||||
if (run.id.length === 0) return null;
|
||||
const eventName = hookEventName(run.eventName);
|
||||
const displayId = hookRunDisplayId(run);
|
||||
const trimmedEventName = run.eventName?.trim();
|
||||
const eventName = trimmedEventName && trimmedEventName.length > 0 ? trimmedEventName : "Hook";
|
||||
const displayId = `hook-${run.id}-${run.startedAt.toString()}`;
|
||||
return {
|
||||
id: displayId,
|
||||
kind: "hook",
|
||||
|
|
@ -37,7 +38,7 @@ export function hookRunMessageStreamItem(run: MessageStreamHookRun, turnId: stri
|
|||
sourceItemId: displayId,
|
||||
provenance: { source: "appServer", channel: "notification", event: "hookRun", sourceItemId: displayId },
|
||||
status,
|
||||
executionState: hookRunExecutionState(status),
|
||||
executionState: executionStateFromStatus(status, HOOK_RUN_STATES),
|
||||
hookRun: {
|
||||
eventName,
|
||||
...definedProp("statusMessage", run.statusMessage ?? undefined),
|
||||
|
|
@ -48,19 +49,6 @@ export function hookRunMessageStreamItem(run: MessageStreamHookRun, turnId: stri
|
|||
};
|
||||
}
|
||||
|
||||
function hookRunExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, HOOK_RUN_STATES);
|
||||
}
|
||||
|
||||
function hookRunDisplayId(run: MessageStreamHookRun): string {
|
||||
return `hook-${run.id}-${run.startedAt.toString()}`;
|
||||
}
|
||||
|
||||
function hookEventName(eventName: string | null | undefined): string {
|
||||
const trimmed = eventName?.trim();
|
||||
return trimmed && trimmed.length > 0 ? trimmed : "Hook";
|
||||
}
|
||||
|
||||
function definedProp<Key extends string, Value>(key: Key, value: Value | null | undefined): Record<Key, Value> | Record<string, never> {
|
||||
return value === null || value === undefined ? {} : ({ [key]: value } as Record<Key, Value>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
executionStateFromStatus,
|
||||
RUNNING_EXECUTION_STATE,
|
||||
} from "../../../domain/message-stream/execution-state";
|
||||
import type { ExecutionState, MessageStreamItem } from "../../../domain/message-stream/items";
|
||||
import type { MessageStreamItem } from "../../../domain/message-stream/items";
|
||||
|
||||
const TASK_STATES = {
|
||||
pending: RUNNING_EXECUTION_STATE,
|
||||
|
|
@ -18,10 +18,6 @@ interface TaskPlanStep {
|
|||
status: TaskStepStatus;
|
||||
}
|
||||
|
||||
function taskProgressExecutionState(status: string): ExecutionState {
|
||||
return executionStateFromStatus(status, TASK_STATES);
|
||||
}
|
||||
|
||||
export function taskProgressMessageStreamItem(
|
||||
turnId: string,
|
||||
explanation: string | null,
|
||||
|
|
@ -39,6 +35,6 @@ export function taskProgressMessageStreamItem(
|
|||
explanation: trimmedExplanation !== undefined && trimmedExplanation.length > 0 ? trimmedExplanation : null,
|
||||
steps: plan.map((step) => ({ step: step.step, status: step.status })),
|
||||
status,
|
||||
executionState: taskProgressExecutionState(status),
|
||||
executionState: executionStateFromStatus(status, TASK_STATES),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,13 +48,11 @@ export function composerBoundaryScrollDirection(
|
|||
if (keyAction.kind === "scroll-to" || keyAction.amount === "page") return keyAction;
|
||||
if (composer.cursorStart !== composer.cursorEnd) return null;
|
||||
|
||||
return keyAction.direction === -1
|
||||
? composerCursorOnFirstLine(composer) && composerCursorAtVisualBoundary(keyAction.direction, composer, options)
|
||||
? keyAction
|
||||
: null
|
||||
: composerCursorOnLastLine(composer) && composerCursorAtVisualBoundary(keyAction.direction, composer, options)
|
||||
? keyAction
|
||||
: null;
|
||||
const cursorAtTextBoundary =
|
||||
keyAction.direction === -1
|
||||
? !composer.value.slice(0, composer.cursorStart).includes("\n")
|
||||
: !composer.value.slice(composer.cursorEnd).includes("\n");
|
||||
return cursorAtTextBoundary && composerCursorAtVisualBoundary(keyAction.direction, composer, options) ? keyAction : null;
|
||||
}
|
||||
|
||||
function composerBoundaryScrollKeyAction(event: ComposerBoundaryScrollKeyEvent): ComposerBoundaryScrollAction | null {
|
||||
|
|
@ -82,14 +80,6 @@ function composerBoundaryScrollByAction(
|
|||
return repeat ? { kind: "scroll-by", direction, amount, repeated: true } : { kind: "scroll-by", direction, amount };
|
||||
}
|
||||
|
||||
function composerCursorOnFirstLine(composer: ComposerBoundaryScrollTextState): boolean {
|
||||
return !composer.value.slice(0, composer.cursorStart).includes("\n");
|
||||
}
|
||||
|
||||
function composerCursorOnLastLine(composer: ComposerBoundaryScrollTextState): boolean {
|
||||
return !composer.value.slice(composer.cursorEnd).includes("\n");
|
||||
}
|
||||
|
||||
function composerCursorAtVisualBoundary(
|
||||
direction: ComposerBoundaryScrollDirection,
|
||||
composer: ComposerBoundaryScrollTextState,
|
||||
|
|
|
|||
|
|
@ -165,11 +165,7 @@ async function initializeConnection(host: ChatConnectionControllerHost, connecti
|
|||
function connectionErrorMessage(error: unknown, configuredCommand: string): string {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (!isMissingCommandError(error)) return message;
|
||||
return missingCommandConnectionErrorMessage(message, configuredCommand);
|
||||
}
|
||||
|
||||
function missingCommandConnectionErrorMessage(errorMessage: string, configuredCommand: string): string {
|
||||
return `Could not start Codex app-server because the configured command was not found: ${configuredCommand}. Check the Codex command path in settings. (${errorMessage})`;
|
||||
return `Could not start Codex app-server because the configured command was not found: ${configuredCommand}. Check the Codex command path in settings. (${message})`;
|
||||
}
|
||||
|
||||
function isMissingCommandError(error: unknown): boolean {
|
||||
|
|
|
|||
|
|
@ -64,12 +64,6 @@ function diagnosticProbeRow(probe: DiagnosticProbeResult): DiagnosticRow {
|
|||
return {
|
||||
label: probe.method,
|
||||
value: `${probe.status}${detail}`,
|
||||
level: diagnosticProbeLevel(probe.status),
|
||||
level: probe.status === "failed" ? "error" : probe.status === "unknown" ? "warning" : "normal",
|
||||
};
|
||||
}
|
||||
|
||||
function diagnosticProbeLevel(status: DiagnosticProbeResult["status"]): NonNullable<DiagnosticRow["level"]> {
|
||||
if (status === "failed") return "error";
|
||||
if (status === "unknown") return "warning";
|
||||
return "normal";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { SkillMetadata } from "../../../../domain/catalog/metadata";
|
||||
import type { Diagnostics, McpServerDiagnostic, McpServerStatusSummary } from "../../../../domain/server/diagnostics";
|
||||
import type { ToolInventoryApp, ToolInventoryPlugin, ToolInventorySnapshot } from "../../../../domain/server/tool-inventory";
|
||||
import type { ToolInventoryPlugin, ToolInventorySnapshot } from "../../../../domain/server/tool-inventory";
|
||||
import type { DiagnosticRow, DiagnosticSection } from "./diagnostic-sections";
|
||||
|
||||
const PERSONAL_SKILLS_LABEL = "Personal";
|
||||
|
|
@ -50,7 +50,7 @@ function pluginRows(inventory: ToolInventorySnapshot): DiagnosticRow[] {
|
|||
if (inventory.pluginsError) return [{ label: "Plugins", value: inventory.pluginsError, level: "error" }];
|
||||
if (!inventory.plugins) return [{ label: "Plugins", value: "not loaded", level: "warning" }];
|
||||
|
||||
const rows = inventory.plugins.filter(isUsablePlugin).map(pluginRow);
|
||||
const rows = inventory.plugins.filter((plugin) => plugin.enabled && plugin.installed).map(pluginRow);
|
||||
return rows.length > 0 ? rows : [{ label: "Plugins", value: "(none)" }];
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,10 @@ function toolProviderRows(inventory: ToolInventorySnapshot): DiagnosticRow[] {
|
|||
function codexAppsProviderRow(inventory: ToolInventorySnapshot): DiagnosticRow {
|
||||
if (inventory.appsError) return { label: CODEX_APPS_PROVIDER_LABEL, value: inventory.appsError, level: "error" };
|
||||
if (!inventory.apps) return { label: CODEX_APPS_PROVIDER_LABEL, value: "not loaded", level: "warning" };
|
||||
return { label: CODEX_APPS_PROVIDER_LABEL, value: listSummary(inventory.apps.filter(isUsableApp).map((app) => app.name)) };
|
||||
return {
|
||||
label: CODEX_APPS_PROVIDER_LABEL,
|
||||
value: listSummary(inventory.apps.filter((app) => app.enabled && app.accessible).map((app) => app.name)),
|
||||
};
|
||||
}
|
||||
|
||||
function mcpToolProviderRows(inventory: ToolInventorySnapshot): DiagnosticRow[] {
|
||||
|
|
@ -153,14 +156,6 @@ function skillRows(inventory: ToolInventorySnapshot): DiagnosticRow[] {
|
|||
.map(([provenance, skills]) => ({ label: provenance, value: listSummary([...skills]) }));
|
||||
}
|
||||
|
||||
function isUsableApp(app: ToolInventoryApp): boolean {
|
||||
return app.enabled && app.accessible;
|
||||
}
|
||||
|
||||
function isUsablePlugin(plugin: ToolInventoryPlugin): boolean {
|
||||
return plugin.enabled && plugin.installed;
|
||||
}
|
||||
|
||||
function pluginBundleSummary(plugin: ToolInventoryPlugin): string {
|
||||
if (plugin.detailsError) return "details unavailable";
|
||||
if (!plugin.details) return "details not loaded";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { AppServerClient } from "../../../../app-server/connection/client";
|
|||
import { latestImplementablePlanTargetFromItems, type PlanImplementationTarget } from "../../domain/message-stream/selectors";
|
||||
import type { ChatRuntimeState } from "../../domain/runtime/state";
|
||||
import { type ChatMessageStreamState, messageStreamItems } from "../state/message-stream";
|
||||
import type { ChatActiveThreadState, ChatState } from "../state/root-reducer";
|
||||
import type { ChatActiveThreadState } from "../state/root-reducer";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
import { type ChatTurnState, chatTurnBusy } from "./turn-state";
|
||||
|
||||
|
|
@ -15,10 +15,6 @@ export interface PlanImplementationHost {
|
|||
requestDefaultCollaborationModeForNextTurn(): void;
|
||||
}
|
||||
|
||||
function canImplementPlanItemId(state: ChatState, itemId: string): boolean {
|
||||
return itemId === implementPlanTargetFromState(state)?.itemId;
|
||||
}
|
||||
|
||||
export function implementPlanTargetFromState(state: {
|
||||
activeThread: Pick<ChatActiveThreadState, "id">;
|
||||
turn: ChatTurnState;
|
||||
|
|
@ -32,7 +28,7 @@ export function implementPlanTargetFromState(state: {
|
|||
}
|
||||
|
||||
export async function implementPlan(host: PlanImplementationHost, itemId: string): Promise<void> {
|
||||
if (!canImplementPlanItemId(host.stateStore.getState(), itemId)) return;
|
||||
if (itemId !== implementPlanTargetFromState(host.stateStore.getState())?.itemId) return;
|
||||
if (!(await host.connectedClient()) || !host.stateStore.getState().activeThread.id) return;
|
||||
|
||||
host.requestDefaultCollaborationModeForNextTurn();
|
||||
|
|
|
|||
|
|
@ -77,22 +77,6 @@ export interface ThreadReferenceInput {
|
|||
referencedThread: ReferencedThreadMetadata;
|
||||
}
|
||||
|
||||
function currentThreadReferenceMessage(): string {
|
||||
return "Use the current thread directly instead of referencing it.";
|
||||
}
|
||||
|
||||
function noActiveThreadToForkMessage(): string {
|
||||
return "No active thread to fork.";
|
||||
}
|
||||
|
||||
function noActiveThreadToRollbackMessage(): string {
|
||||
return "No active thread to roll back.";
|
||||
}
|
||||
|
||||
function noActiveThreadToCompactMessage(): string {
|
||||
return "No active thread to compact.";
|
||||
}
|
||||
|
||||
export async function executeSlashCommand(
|
||||
command: SlashCommandName,
|
||||
args: string,
|
||||
|
|
@ -132,7 +116,7 @@ export async function executeSlashCommand(
|
|||
return;
|
||||
}
|
||||
if (thread.thread.id === context.activeThreadId) {
|
||||
context.addSystemMessage(currentThreadReferenceMessage());
|
||||
context.addSystemMessage("Use the current thread directly instead of referencing it.");
|
||||
return;
|
||||
}
|
||||
const reference = await context.referThread(thread.thread, parsed.message);
|
||||
|
|
@ -141,21 +125,21 @@ export async function executeSlashCommand(
|
|||
}
|
||||
case "fork":
|
||||
if (!context.activeThreadId) {
|
||||
context.addSystemMessage(noActiveThreadToForkMessage());
|
||||
context.addSystemMessage("No active thread to fork.");
|
||||
return;
|
||||
}
|
||||
await context.threadActions.forkThread(context.activeThreadId);
|
||||
return;
|
||||
case "rollback":
|
||||
if (!context.activeThreadId) {
|
||||
context.addSystemMessage(noActiveThreadToRollbackMessage());
|
||||
context.addSystemMessage("No active thread to roll back.");
|
||||
return;
|
||||
}
|
||||
await context.threadActions.rollbackThread(context.activeThreadId);
|
||||
return;
|
||||
case "compact":
|
||||
if (!context.activeThreadId) {
|
||||
context.addSystemMessage(noActiveThreadToCompactMessage());
|
||||
context.addSystemMessage("No active thread to compact.");
|
||||
return;
|
||||
}
|
||||
await context.threadActions.compactThread(context.activeThreadId);
|
||||
|
|
|
|||
|
|
@ -25,14 +25,6 @@ export interface SlashCommandExecutorHost extends SlashCommandExecutionPorts {
|
|||
setStatus: (status: string) => void;
|
||||
}
|
||||
|
||||
function referencedThreadStatus(thread: Thread, includedTurns: number): string {
|
||||
return `Referencing ${shortThreadId(thread.id)} (${String(includedTurns)}/${String(REFERENCED_THREAD_TURN_LIMIT)} turns).`;
|
||||
}
|
||||
|
||||
function referencedThreadUnreadableMessage(): string {
|
||||
return "Referenced thread has no readable conversation turns.";
|
||||
}
|
||||
|
||||
export async function executeSlashCommandWithState(
|
||||
host: SlashCommandExecutorHost,
|
||||
command: SlashCommandName,
|
||||
|
|
@ -71,12 +63,12 @@ async function referencedThreadInput(
|
|||
try {
|
||||
const turns = await readReferencedThreadConversationSummaries(client, thread.id, REFERENCED_THREAD_TURN_LIMIT);
|
||||
if (turns.length === 0) {
|
||||
host.addSystemMessage(referencedThreadUnreadableMessage());
|
||||
host.addSystemMessage("Referenced thread has no readable conversation turns.");
|
||||
return null;
|
||||
}
|
||||
const reference = referencedThreadPromptBundle(thread, turns, message);
|
||||
const messageInput = host.codexInput(message);
|
||||
host.setStatus(referencedThreadStatus(thread, turns.length));
|
||||
host.setStatus(`Referencing ${shortThreadId(thread.id)} (${String(turns.length)}/${String(REFERENCED_THREAD_TURN_LIMIT)} turns).`);
|
||||
return {
|
||||
input: codexTextInputWithAttachments(reference.prompt, messageInput),
|
||||
referencedThread: reference.referencedThread,
|
||||
|
|
|
|||
|
|
@ -39,10 +39,6 @@ type TurnSubmissionPlan =
|
|||
| { kind: "start-thread-then-turn" }
|
||||
| { kind: "start-turn"; threadId: string };
|
||||
|
||||
function currentTurnNotSteerableMessage(): string {
|
||||
return "Current turn is not steerable yet.";
|
||||
}
|
||||
|
||||
export interface TurnSubmissionActions {
|
||||
sendTurnText(text: string, codexInputOverride?: CodexInput, referencedThread?: ReferencedThreadMetadata): Promise<void>;
|
||||
}
|
||||
|
|
@ -148,7 +144,7 @@ function planTurnSubmission(state: TurnSubmissionSnapshot): TurnSubmissionPlan {
|
|||
if (state.busy) {
|
||||
return state.activeThreadId && state.activeTurnId
|
||||
? { kind: "steer", threadId: state.activeThreadId, turnId: state.activeTurnId }
|
||||
: { kind: "blocked", message: currentTurnNotSteerableMessage() };
|
||||
: { kind: "blocked", message: "Current turn is not steerable yet." };
|
||||
}
|
||||
return state.activeThreadId ? { kind: "start-turn", threadId: state.activeThreadId } : { kind: "start-thread-then-turn" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import {
|
|||
type ApprovalAction,
|
||||
answersForPendingUserInput,
|
||||
type McpElicitationAction,
|
||||
type PendingApproval,
|
||||
type PendingMcpElicitation,
|
||||
type PendingRequestId,
|
||||
type PendingUserInput,
|
||||
} from "../../../../domain/pending-requests/model";
|
||||
|
|
@ -41,7 +39,7 @@ export function createPendingRequestActions(host: PendingRequestActionsHost): Pe
|
|||
let lastFocusSignature = "";
|
||||
|
||||
const resolveApproval = (requestId: PendingRequestId, approvalAction: ApprovalAction): void => {
|
||||
const approval = pendingApproval(host, requestId);
|
||||
const approval = host.stateStore.getState().requests.approvals.find((item) => item.requestId === requestId) ?? null;
|
||||
if (!approval) return;
|
||||
host.responder.resolveApproval(requestId, approvalAction);
|
||||
commitRequestAction(host);
|
||||
|
|
@ -65,7 +63,7 @@ export function createPendingRequestActions(host: PendingRequestActionsHost): Pe
|
|||
};
|
||||
|
||||
const resolveMcpElicitation = (requestId: PendingRequestId, action: McpElicitationAction): void => {
|
||||
const elicitation = pendingMcpElicitation(host, requestId);
|
||||
const elicitation = host.stateStore.getState().requests.pendingMcpElicitations.find((item) => item.requestId === requestId) ?? null;
|
||||
if (!elicitation) return;
|
||||
host.responder.resolveMcpElicitation(requestId, action);
|
||||
commitRequestAction(host);
|
||||
|
|
@ -132,18 +130,10 @@ export function createPendingRequestActions(host: PendingRequestActionsHost): Pe
|
|||
};
|
||||
}
|
||||
|
||||
function pendingApproval(host: PendingRequestActionsHost, requestId: PendingRequestId): PendingApproval | null {
|
||||
return host.stateStore.getState().requests.approvals.find((approval) => approval.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function pendingUserInput(host: PendingRequestActionsHost, requestId: PendingRequestId): PendingUserInput | null {
|
||||
return host.stateStore.getState().requests.pendingUserInputs.find((input) => input.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function pendingMcpElicitation(host: PendingRequestActionsHost, requestId: PendingRequestId): PendingMcpElicitation | null {
|
||||
return host.stateStore.getState().requests.pendingMcpElicitations.find((elicitation) => elicitation.requestId === requestId) ?? null;
|
||||
}
|
||||
|
||||
function commitRequestAction(host: PendingRequestActionsHost): void {
|
||||
host.refreshLiveState();
|
||||
host.focusComposer();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { AppServerClient } from "../../../../app-server/connection/client";
|
||||
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
|
||||
import { type RuntimeConfigSnapshot, runtimeConfigOrDefault } from "../../../../domain/runtime/config";
|
||||
import type { ApprovalsReviewer } from "../../../../domain/runtime/policy";
|
||||
import type { RuntimeSettingsPatch } from "../../../../domain/runtime/thread-settings";
|
||||
import {
|
||||
pendingRuntimeSettingsPatch as buildPendingRuntimeSettingsPatch,
|
||||
|
|
@ -148,7 +147,7 @@ async function setFastMode(host: RuntimeSettingsActionsHost, mode: FastModeState
|
|||
dispatch(host, { type: "runtime/fast-mode-requested", fastMode });
|
||||
return applyPendingThreadSettings(host);
|
||||
},
|
||||
fastModeToggleMessage(mode),
|
||||
mode === "enabled" ? "Fast mode on for subsequent turns." : "Fast mode off for subsequent turns.",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +161,9 @@ async function setCollaborationMode(host: RuntimeSettingsActionsHost, collaborat
|
|||
dispatch(host, { type: "runtime/requested-collaboration-mode-set", collaborationMode });
|
||||
const result = await commitPendingThreadSettings(host);
|
||||
if (result.ok) closeRuntimePanel(host);
|
||||
if (result.ok && result.collaborationModeApplied) host.addSystemMessage(collaborationModeToggleMessage(collaborationMode));
|
||||
if (result.ok && result.collaborationModeApplied) {
|
||||
host.addSystemMessage(collaborationMode === "plan" ? "Plan mode on for subsequent turns." : "Plan mode off for subsequent turns.");
|
||||
}
|
||||
return result.ok;
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ function requestDefaultCollaborationModeForNextTurn(host: RuntimeSettingsActions
|
|||
|
||||
async function toggleAutoReview(host: RuntimeSettingsActionsHost): Promise<void> {
|
||||
const { snapshot, config } = runtimeProjection(host);
|
||||
const nextState = nextAutoReviewState(resolveRuntimeControls(snapshot, config).autoReview.active);
|
||||
const nextState = resolveRuntimeControls(snapshot, config).autoReview.active ? "disabled" : "enabled";
|
||||
await setAutoReview(host, nextState);
|
||||
}
|
||||
|
||||
|
|
@ -180,30 +181,13 @@ async function setAutoReview(host: RuntimeSettingsActionsHost, mode: AutoReviewS
|
|||
await runRuntimeUiCommand(
|
||||
host,
|
||||
async () => {
|
||||
dispatch(host, { type: "runtime/approvals-reviewer-requested", approvalsReviewer: autoReviewReviewerForState(mode) });
|
||||
dispatch(host, { type: "runtime/approvals-reviewer-requested", approvalsReviewer: mode === "enabled" ? "auto_review" : "user" });
|
||||
return applyPendingThreadSettings(host);
|
||||
},
|
||||
autoReviewToggleMessage(mode),
|
||||
mode === "enabled" ? "Auto-review on for subsequent turns." : "Auto-review off for subsequent turns.",
|
||||
);
|
||||
}
|
||||
|
||||
function fastModeToggleMessage(state: FastModeState): string {
|
||||
return state === "enabled" ? "Fast mode on for subsequent turns." : "Fast mode off for subsequent turns.";
|
||||
}
|
||||
|
||||
function collaborationModeToggleMessage(mode: CollaborationModeSelection): string {
|
||||
return mode === "plan" ? "Plan mode on for subsequent turns." : "Plan mode off for subsequent turns.";
|
||||
}
|
||||
|
||||
function autoReviewToggleMessage(state: AutoReviewState): string {
|
||||
return state === "enabled" ? "Auto-review on for subsequent turns." : "Auto-review off for subsequent turns.";
|
||||
}
|
||||
|
||||
function collaborationModeWarningMessage(warning: NonNullable<PendingRuntimeSettingsPatch["collaborationModeWarning"]>): string {
|
||||
void warning;
|
||||
return "No effective model is available. Sending without a mode override.";
|
||||
}
|
||||
|
||||
async function runRuntimeUiCommand(
|
||||
host: RuntimeSettingsActionsHost,
|
||||
command: () => Promise<boolean>,
|
||||
|
|
@ -218,14 +202,6 @@ function closeRuntimePanel(host: RuntimeSettingsActionsHost): void {
|
|||
dispatch(host, { type: "ui/panel-set", panel: null });
|
||||
}
|
||||
|
||||
function nextAutoReviewState(active: boolean): AutoReviewState {
|
||||
return active ? "disabled" : "enabled";
|
||||
}
|
||||
|
||||
function autoReviewReviewerForState(state: AutoReviewState): ApprovalsReviewer {
|
||||
return state === "enabled" ? "auto_review" : "user";
|
||||
}
|
||||
|
||||
function pendingRuntimeSettingsPatch(host: RuntimeSettingsActionsHost): PendingRuntimeSettingsPatch {
|
||||
const { snapshot, config } = runtimeProjection(host);
|
||||
return buildPendingRuntimeSettingsPatch(snapshot, config);
|
||||
|
|
@ -235,7 +211,10 @@ function reportCollaborationModeWarning(
|
|||
host: RuntimeSettingsActionsHost,
|
||||
warning: NonNullable<PendingRuntimeSettingsPatch["collaborationModeWarning"]>,
|
||||
): void {
|
||||
host.addSystemMessage(`${host.collaborationModeLabel()} mode is selected, but ${collaborationModeWarningMessage(warning)}`);
|
||||
void warning;
|
||||
host.addSystemMessage(
|
||||
`${host.collaborationModeLabel()} mode is selected, but No effective model is available. Sending without a mode override.`,
|
||||
);
|
||||
}
|
||||
|
||||
function currentPendingRuntimeSettingsPatch(host: RuntimeSettingsActionsHost): RuntimeSettingsPatch {
|
||||
|
|
|
|||
|
|
@ -121,15 +121,11 @@ export function messageStreamIsEmpty(state: Pick<ChatMessageStreamState, "stable
|
|||
return state.stableItems.length === 0 && (!state.activeSegment || state.activeSegment.items.length === 0);
|
||||
}
|
||||
|
||||
function messageStreamTurnIds(state: Pick<ChatMessageStreamState, "stableItems" | "activeSegment">): string[] {
|
||||
return orderedTurnIds(messageStreamItems(state));
|
||||
}
|
||||
|
||||
export function messageStreamTurnsAfterTurnId(
|
||||
state: Pick<ChatMessageStreamState, "stableItems" | "activeSegment">,
|
||||
turnId: string,
|
||||
): number | null {
|
||||
const turnIds = messageStreamTurnIds(state);
|
||||
const turnIds = orderedTurnIds(messageStreamItems(state));
|
||||
const index = turnIds.indexOf(turnId);
|
||||
return index === -1 ? null : turnIds.length - index - 1;
|
||||
}
|
||||
|
|
@ -191,7 +187,7 @@ export function reduceMessageStreamSlice(state: ChatMessageStreamState, action:
|
|||
switch (action.type) {
|
||||
case "message-stream/item-added":
|
||||
case "message-stream/system-item-added":
|
||||
return appendMessageStreamItem(state, action.item);
|
||||
return patchObject(state, appendMessageStreamItemPatch(state, action.item));
|
||||
case "message-stream/deduped-log-added":
|
||||
if (state.reportedLogs.has(action.text)) return state;
|
||||
return patchObject(state, {
|
||||
|
|
@ -226,10 +222,6 @@ export function reduceMessageStreamSlice(state: ChatMessageStreamState, action:
|
|||
}
|
||||
}
|
||||
|
||||
function appendMessageStreamItem(state: ChatMessageStreamState, item: MessageStreamItem): ChatMessageStreamState {
|
||||
return patchObject(state, appendMessageStreamItemPatch(state, item));
|
||||
}
|
||||
|
||||
function appendMessageStreamItemPatch(state: ChatMessageStreamState, item: MessageStreamItem): Partial<ChatMessageStreamState> {
|
||||
if (shouldUseActiveSegment(state.activeSegment, item)) {
|
||||
return { activeSegment: appendActiveSegmentItem(state.activeSegment, item) };
|
||||
|
|
@ -459,7 +451,7 @@ function appendActiveSegmentItem(segment: ChatMessageStreamActiveSegment, item:
|
|||
function upsertActiveSegmentItem(segment: ChatMessageStreamActiveSegment, item: MessageStreamItem): ChatMessageStreamActiveSegment {
|
||||
const index = segment.indexById.get(item.id);
|
||||
if (index === undefined) return appendActiveSegmentItem(segment, item);
|
||||
return replaceActiveSegmentItem(segment, index, (previous) => mergeMessageStreamItem(previous, item));
|
||||
return replaceActiveSegmentItem(segment, index, (previous) => upsertMessageStreamItemById([previous], item)[0] ?? item);
|
||||
}
|
||||
|
||||
function replaceActiveSegmentItem(
|
||||
|
|
@ -476,10 +468,6 @@ function replaceActiveSegmentItem(
|
|||
return activeSegmentFromItems(segment.turnId, items);
|
||||
}
|
||||
|
||||
function mergeMessageStreamItem(previous: MessageStreamItem, next: MessageStreamItem): MessageStreamItem {
|
||||
return upsertMessageStreamItemById([previous], next)[0] ?? next;
|
||||
}
|
||||
|
||||
function activeSegmentFromItems(turnId: string | null, items: readonly MessageStreamItem[]): ChatMessageStreamActiveSegment {
|
||||
const indexById = new Map<string, number>();
|
||||
const indexBySourceItemId = new Map<string, number>();
|
||||
|
|
|
|||
|
|
@ -84,10 +84,6 @@ export type ChatConnectionPhase =
|
|||
| { kind: "failed"; message: string }
|
||||
| { kind: "disconnected"; message: string };
|
||||
|
||||
function turnCompletedStatus(status: string): string {
|
||||
return `Turn ${status}.`;
|
||||
}
|
||||
|
||||
interface ChatConnectionState {
|
||||
readonly phase: ChatConnectionPhase;
|
||||
readonly statusText: string;
|
||||
|
|
@ -244,7 +240,7 @@ export function createChatState(): ChatState {
|
|||
connection: initialConnectionState(),
|
||||
threadList: initialThreadListState(),
|
||||
activeThread: initialActiveThreadState(),
|
||||
runtime: initialRuntimeState(),
|
||||
runtime: initialChatRuntimeState(),
|
||||
turn: initialTurnState(),
|
||||
messageStream: initialMessageStreamState(),
|
||||
requests: initialRequestState(),
|
||||
|
|
@ -394,7 +390,7 @@ function reduceTurnCompletedTransition(state: ChatState, action: TurnCompletedAc
|
|||
return patchChatState(state, {
|
||||
turn: { lifecycle },
|
||||
messageStream: messageStreamWithItems(state.messageStream, action.items),
|
||||
connection: { ...state.connection, statusText: turnCompletedStatus(action.status) },
|
||||
connection: { ...state.connection, statusText: `Turn ${action.status}.` },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -630,10 +626,6 @@ function initialActiveThreadState(): ChatActiveThreadState {
|
|||
};
|
||||
}
|
||||
|
||||
function initialRuntimeState(): ChatRuntimeState {
|
||||
return initialChatRuntimeState();
|
||||
}
|
||||
|
||||
function initialTurnState(): ChatTurnState {
|
||||
return initialChatTurnState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import type { Thread } from "../../../../domain/threads/model";
|
|||
import type { ThreadTitleContext } from "../../../../domain/threads/title-generation-model";
|
||||
import type { ThreadConversationSummary } from "../../../../domain/threads/transcript";
|
||||
import type { ThreadTitleService } from "../../../threads/thread-title-service";
|
||||
import type { ChatState } from "../state/root-reducer";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
|
||||
export interface AutoTitleCoordinatorHost {
|
||||
|
|
@ -22,7 +21,8 @@ export function createAutoTitleCoordinator(host: AutoTitleCoordinatorHost): Auto
|
|||
const attemptedThreadIds = new Set<string>();
|
||||
const inFlightThreadIds = new Set<string>();
|
||||
|
||||
const thread = (threadId: string): Thread | undefined => state(host).threadList.listedThreads.find((item) => item.id === threadId);
|
||||
const thread = (threadId: string): Thread | undefined =>
|
||||
host.stateStore.getState().threadList.listedThreads.find((item) => item.id === threadId);
|
||||
const threadHasTitle = (threadId: string): boolean => Boolean(thread(threadId)?.name?.trim());
|
||||
const threadCanReceiveGeneratedTitle = (threadId: string): boolean => {
|
||||
const candidate = thread(threadId);
|
||||
|
|
@ -64,7 +64,3 @@ export function createAutoTitleCoordinator(host: AutoTitleCoordinatorHost): Auto
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
function state(host: AutoTitleCoordinatorHost): ChatState {
|
||||
return host.stateStore.getState();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ type GoalObjectiveSavePlan =
|
|||
|
||||
type NormalizedGoalObjective = string & { readonly __brand: "NormalizedGoalObjective" };
|
||||
|
||||
function emptyGoalObjectiveMessage(): string {
|
||||
return "Goal objective cannot be empty.";
|
||||
}
|
||||
const EMPTY_GOAL_OBJECTIVE_MESSAGE = "Goal objective cannot be empty.";
|
||||
|
||||
export function createThreadGoalSyncActions(host: ThreadGoalSyncHost): ThreadGoalSyncActions {
|
||||
return {
|
||||
|
|
@ -93,7 +91,7 @@ async function syncThreadGoal(host: ThreadGoalSyncHost, threadId: string): Promi
|
|||
async function setObjective(host: GoalActionsHost, threadId: string, objective: string, tokenBudget: number | null): Promise<boolean> {
|
||||
const normalized = normalizedGoalObjective(objective);
|
||||
if (!normalized) {
|
||||
host.addSystemMessage(emptyGoalObjectiveMessage());
|
||||
host.addSystemMessage(EMPTY_GOAL_OBJECTIVE_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
return setNormalizedObjective(host, threadId, normalized, tokenBudget);
|
||||
|
|
@ -186,7 +184,7 @@ function startEditing(host: GoalActionsHost, threadId: string | null, objective:
|
|||
|
||||
function planGoalObjectiveSave(activeThreadId: string | null, objective: string, tokenBudget: number | null): GoalObjectiveSavePlan {
|
||||
const normalized = normalizedGoalObjective(objective);
|
||||
if (!normalized) return { kind: "reject", message: emptyGoalObjectiveMessage() };
|
||||
if (!normalized) return { kind: "reject", message: EMPTY_GOAL_OBJECTIVE_MESSAGE };
|
||||
return activeThreadId
|
||||
? { kind: "save-existing", threadId: activeThreadId, objective: normalized, tokenBudget }
|
||||
: { kind: "start-thread-and-save", objective: normalized, tokenBudget };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import type { Thread } from "../../../../domain/threads/model";
|
||||
import { threadRenameDraftTitle } from "../../../../domain/threads/title";
|
||||
import type { ThreadTitleContext } from "../../../../domain/threads/title-generation-model";
|
||||
import type { ThreadOperations } from "../../../threads/thread-operations";
|
||||
|
|
@ -50,7 +49,7 @@ export function createThreadRenameEditorActions(host: ThreadRenameEditorActionsH
|
|||
},
|
||||
|
||||
start(threadId: string): void {
|
||||
const thread = threadById(host, threadId);
|
||||
const thread = host.stateStore.getState().threadList.listedThreads.find((item) => item.id === threadId);
|
||||
if (!thread) return;
|
||||
dispatch(host, { type: "ui/rename-started", threadId, draft: threadRenameDraftTitle(thread) });
|
||||
},
|
||||
|
|
@ -134,7 +133,3 @@ function finishAutoNameDraftGeneration(
|
|||
): void {
|
||||
dispatch(host, { type: "ui/rename-generation-finished", threadId, generatingState });
|
||||
}
|
||||
|
||||
function threadById(host: ThreadRenameEditorActionsHost, threadId: string): Thread | undefined {
|
||||
return host.stateStore.getState().threadList.listedThreads.find((item) => item.id === threadId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,17 +36,9 @@ export function createResumeActions(host: ResumeActionsHost): ResumeActions {
|
|||
};
|
||||
}
|
||||
|
||||
function finishBeforeSwitchingThreadsMessage(): string {
|
||||
return "Finish or interrupt the current turn before switching threads.";
|
||||
}
|
||||
|
||||
function resumedThreadMessage(threadId: string): string {
|
||||
return `Resumed thread ${threadId}`;
|
||||
}
|
||||
|
||||
async function resumeThread(host: ResumeActionsHost, threadId: string): Promise<void> {
|
||||
if (!canSwitchToThread(host.stateStore.getState(), threadId)) {
|
||||
host.addSystemMessage(finishBeforeSwitchingThreadsMessage());
|
||||
host.addSystemMessage("Finish or interrupt the current turn before switching threads.");
|
||||
return;
|
||||
}
|
||||
const resume = host.resumeWork.begin(threadId);
|
||||
|
|
@ -70,7 +62,7 @@ async function resumeThread(host: ResumeActionsHost, threadId: string): Promise<
|
|||
if (isStaleResume(host, resume)) return;
|
||||
const renderFallbackMessage = messageStreamIsEmpty(host.stateStore.getState().messageStream);
|
||||
if (renderFallbackMessage) {
|
||||
host.addSystemMessage(resumedThreadMessage(response.activation.thread.id));
|
||||
host.addSystemMessage(`Resumed thread ${response.activation.thread.id}`);
|
||||
}
|
||||
host.refreshLiveState();
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -68,50 +68,10 @@ export function createThreadManagementActions(host: ThreadManagementActionsHost)
|
|||
};
|
||||
}
|
||||
|
||||
function finishBeforeArchivingThreadsMessage(): string {
|
||||
return "Finish or interrupt the current turn before archiving threads.";
|
||||
}
|
||||
|
||||
function finishBeforeForkingThreadsMessage(): string {
|
||||
return "Finish or interrupt the current turn before forking threads.";
|
||||
}
|
||||
|
||||
function selectedTurnNotFoundForForkMessage(): string {
|
||||
return "Could not find the selected turn to fork.";
|
||||
}
|
||||
|
||||
function forkNameCopyFailedMessage(threadId: string, message: string): string {
|
||||
return `Forked thread ${threadId}, but could not copy the source thread name: ${message}`;
|
||||
}
|
||||
|
||||
function archivedSourceOpenForkFailedMessage(sourceThreadId: string, forkedThreadId: string, message: string): string {
|
||||
return `Archived thread ${sourceThreadId}, but could not open forked thread ${forkedThreadId}: ${message}`;
|
||||
}
|
||||
|
||||
function openForkInNewPanelFailedMessage(forkedThreadId: string, message: string): string {
|
||||
return `Forked thread ${forkedThreadId}, but could not open it in a new panel: ${message}`;
|
||||
}
|
||||
|
||||
function interruptBeforeRollbackMessage(): string {
|
||||
return "Interrupt the current turn before rolling back.";
|
||||
}
|
||||
|
||||
function noCompletedTurnToRollbackMessage(): string {
|
||||
return "No completed turn to roll back.";
|
||||
}
|
||||
|
||||
function noActiveThreadToCompactMessage(): string {
|
||||
return "No active thread to compact.";
|
||||
}
|
||||
|
||||
function rollbackCompletedMessage(): string {
|
||||
return "Rolled back the latest turn. Local file changes were not reverted.";
|
||||
}
|
||||
|
||||
async function compactActiveThread(host: ThreadManagementActionsHost): Promise<void> {
|
||||
const threadId = threadManagementState(host).activeThread.id;
|
||||
if (!threadId) {
|
||||
host.addSystemMessage(noActiveThreadToCompactMessage());
|
||||
host.addSystemMessage("No active thread to compact.");
|
||||
return;
|
||||
}
|
||||
await compactThread(host, threadId);
|
||||
|
|
@ -136,7 +96,7 @@ async function archiveThread(host: ThreadManagementActionsHost, threadId: string
|
|||
|
||||
async function archiveThreadFromPanel(host: ThreadManagementActionsHost, threadId: string, saveMarkdown?: boolean): Promise<boolean> {
|
||||
if (chatTurnBusy(threadManagementState(host))) {
|
||||
host.addSystemMessage(finishBeforeArchivingThreadsMessage());
|
||||
host.addSystemMessage("Finish or interrupt the current turn before archiving threads.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
|
@ -159,7 +119,7 @@ async function forkThreadFromTurn(
|
|||
archiveSource: boolean,
|
||||
): Promise<void> {
|
||||
if (chatTurnBusy(threadManagementState(host))) {
|
||||
host.addSystemMessage(finishBeforeForkingThreadsMessage());
|
||||
host.addSystemMessage("Finish or interrupt the current turn before forking threads.");
|
||||
return;
|
||||
}
|
||||
const scope = await captureThreadManagementOperationScope(host, threadId);
|
||||
|
|
@ -167,7 +127,7 @@ async function forkThreadFromTurn(
|
|||
|
||||
const turnsToDrop = turnId ? messageStreamTurnsAfterTurnId(threadManagementState(host).messageStream, turnId) : 0;
|
||||
if (turnsToDrop === null) {
|
||||
host.addSystemMessage(selectedTurnNotFoundForForkMessage());
|
||||
host.addSystemMessage("Could not find the selected turn to fork.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +148,7 @@ async function forkThreadFromTurn(
|
|||
if (!(await host.operations.renameThread(forkedThreadId, sourceName))) return;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
host.addSystemMessage(forkNameCopyFailedMessage(forkedThreadId, message));
|
||||
host.addSystemMessage(`Forked thread ${forkedThreadId}, but could not copy the source thread name: ${message}`);
|
||||
}
|
||||
}
|
||||
if (archiveSource) {
|
||||
|
|
@ -197,7 +157,7 @@ async function forkThreadFromTurn(
|
|||
await host.openThreadInCurrentPanel(forkedThreadId);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
host.addSystemMessage(archivedSourceOpenForkFailedMessage(threadId, forkedThreadId, message));
|
||||
host.addSystemMessage(`Archived thread ${threadId}, but could not open forked thread ${forkedThreadId}: ${message}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -205,7 +165,7 @@ async function forkThreadFromTurn(
|
|||
await host.openThreadInNewView(forkedThreadId);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
host.addSystemMessage(openForkInNewPanelFailedMessage(forkedThreadId, message));
|
||||
host.addSystemMessage(`Forked thread ${forkedThreadId}, but could not open it in a new panel: ${message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
host.addSystemMessage(error instanceof Error ? error.message : String(error));
|
||||
|
|
@ -225,7 +185,7 @@ async function renameThread(host: ThreadManagementActionsHost, threadId: string,
|
|||
|
||||
async function rollbackThread(host: ThreadManagementActionsHost, threadId: string): Promise<void> {
|
||||
if (chatTurnBusy(threadManagementState(host))) {
|
||||
host.addSystemMessage(interruptBeforeRollbackMessage());
|
||||
host.addSystemMessage("Interrupt the current turn before rolling back.");
|
||||
return;
|
||||
}
|
||||
const scope = await captureThreadManagementOperationScope(host, threadId);
|
||||
|
|
@ -233,7 +193,7 @@ async function rollbackThread(host: ThreadManagementActionsHost, threadId: strin
|
|||
|
||||
const candidate = messageStreamRollbackCandidate(threadManagementState(host).messageStream);
|
||||
if (!candidate) {
|
||||
host.addSystemMessage(noCompletedTurnToRollbackMessage());
|
||||
host.addSystemMessage("No completed turn to roll back.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +217,7 @@ async function rollbackThread(host: ThreadManagementActionsHost, threadId: strin
|
|||
loadingHistory: false,
|
||||
});
|
||||
host.setComposerText(candidate.text);
|
||||
host.addSystemMessage(rollbackCompletedMessage());
|
||||
host.addSystemMessage("Rolled back the latest turn. Local file changes were not reverted.");
|
||||
host.setStatus(STATUS_ROLLBACK_COMPLETE);
|
||||
host.notifyActiveThreadIdentityChanged();
|
||||
await host.refreshAfterThreadMutation();
|
||||
|
|
|
|||
|
|
@ -19,14 +19,10 @@ export interface ThreadNavigationActions {
|
|||
selectThreadFromToolbar(threadId: string): Promise<void>;
|
||||
}
|
||||
|
||||
function finishBeforeSwitchingThreadsMessage(): string {
|
||||
return "Finish or interrupt the current turn before switching threads.";
|
||||
}
|
||||
|
||||
export function createThreadNavigationActions(host: ThreadNavigationActionsHost): ThreadNavigationActions {
|
||||
const selectThread = async (threadId: string): Promise<void> => {
|
||||
if (!canSwitchToThread(host.stateStore.getState(), threadId)) {
|
||||
host.addSystemMessage(finishBeforeSwitchingThreadsMessage());
|
||||
host.addSystemMessage("Finish or interrupt the current turn before switching threads.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export function resolveRuntimeControls(snapshot: RuntimeSnapshot, config: Runtim
|
|||
pending: snapshot.pending.approvalsReviewer,
|
||||
});
|
||||
const autoReview = resolveAutoReview(approvalsReviewer);
|
||||
const serviceTiers = modelServiceTiers(snapshot.availableModels, model.effective);
|
||||
const serviceTiers = findModelMetadataByIdOrName(snapshot.availableModels, model.effective)?.serviceTiers ?? [];
|
||||
const serviceTier = resolveServiceTier(snapshot, config);
|
||||
const fastMode = resolveFastMode(snapshot.pending.fastMode, serviceTier, serviceTiers);
|
||||
const collaborationMode = resolveCollaborationMode(snapshot, model.effective);
|
||||
|
|
@ -84,7 +84,7 @@ export function resolveRuntimeControls(snapshot: RuntimeSnapshot, config: Runtim
|
|||
|
||||
function resolveAutoReview(approvalsReviewer: RuntimeLayeredValue<ApprovalsReviewer>): AutoReviewResolution {
|
||||
return {
|
||||
active: isAutoReviewReviewer(approvalsReviewer.effective),
|
||||
active: approvalsReviewer.effective === "auto_review" || approvalsReviewer.effective === "guardian_subagent",
|
||||
reviewer: approvalsReviewer.effective,
|
||||
source: approvalsReviewer.source,
|
||||
};
|
||||
|
|
@ -159,7 +159,7 @@ function resolveFastMode(
|
|||
active: isFastServiceTier(serviceTier.effective, serviceTiers),
|
||||
source: serviceTier.source,
|
||||
effectiveServiceTier: serviceTier.effective,
|
||||
serviceTierRequestValue: fastServiceTierRequestValue(serviceTiers),
|
||||
serviceTierRequestValue: serviceTiers.find((tier) => tier.name.trim().toLowerCase() === "fast")?.id ?? "fast",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -183,15 +183,3 @@ function isFastServiceTier(value: string | null | undefined, serviceTiers: Model
|
|||
if (serviceTiers.length === 0) return value === "priority";
|
||||
return serviceTiers.some((tier) => tier.id === value && tier.name.trim().toLowerCase() === "fast");
|
||||
}
|
||||
|
||||
function isAutoReviewReviewer(value: ApprovalsReviewer | null): boolean {
|
||||
return value === "auto_review" || value === "guardian_subagent";
|
||||
}
|
||||
|
||||
function fastServiceTierRequestValue(serviceTiers: ModelMetadata["serviceTiers"]): string {
|
||||
return serviceTiers.find((tier) => tier.name.trim().toLowerCase() === "fast")?.id ?? "fast";
|
||||
}
|
||||
|
||||
function modelServiceTiers(models: readonly ModelMetadata[], model: string | null): ModelMetadata["serviceTiers"] {
|
||||
return findModelMetadataByIdOrName(models, model)?.serviceTiers ?? [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import type {
|
|||
} from "../../domain/message-stream/items";
|
||||
import { messageStreamReasoningIsActive } from "../../domain/message-stream/semantics/active-turn";
|
||||
|
||||
export type StatusChecklistItem = TaskProgressMessageStreamItem["steps"][number];
|
||||
type StatusChecklistItem = TaskProgressMessageStreamItem["steps"][number];
|
||||
|
||||
export type MessageStreamStatusView =
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { ComponentChild as UiNode } from "preact";
|
||||
|
||||
import type { ExecutionState } from "../../domain/message-stream/items";
|
||||
import type { AgentRunSummaryView, MessageStreamStatusView, StatusChecklistItem } from "../../presentation/message-stream/status-view";
|
||||
import type { AgentRunSummaryView, MessageStreamStatusView } from "../../presentation/message-stream/status-view";
|
||||
|
||||
export function agentRunSummaryNode(view: AgentRunSummaryView): UiNode {
|
||||
return <AgentRunSummary view={view} />;
|
||||
|
|
@ -44,8 +44,19 @@ function TaskProgress({ view }: { view: Extract<MessageStreamStatusView, { kind:
|
|||
) : (
|
||||
<ul className="codex-panel__task-list">
|
||||
{view.checklist.map((step) => (
|
||||
<li key={`${step.status}\n${step.step}`} className={taskStepClassName(step.status)}>
|
||||
<span className="codex-panel__task-marker">{taskStatusMarker(step.status)}</span>
|
||||
<li
|
||||
key={`${step.status}\n${step.step}`}
|
||||
className={
|
||||
step.status === "completed"
|
||||
? "codex-panel__task-step codex-panel__task-step--completed"
|
||||
: step.status === "inProgress"
|
||||
? "codex-panel__task-step codex-panel__task-step--inProgress"
|
||||
: "codex-panel__task-step"
|
||||
}
|
||||
>
|
||||
<span className="codex-panel__task-marker">
|
||||
{step.status === "completed" ? "[x]" : step.status === "inProgress" ? "[>]" : "[ ]"}
|
||||
</span>
|
||||
<span className="codex-panel__task-text">{step.step}</span>
|
||||
</li>
|
||||
))}
|
||||
|
|
@ -55,18 +66,6 @@ function TaskProgress({ view }: { view: Extract<MessageStreamStatusView, { kind:
|
|||
);
|
||||
}
|
||||
|
||||
function taskStatusMarker(status: StatusChecklistItem["status"]): string {
|
||||
if (status === "completed") return "[x]";
|
||||
if (status === "inProgress") return "[>]";
|
||||
return "[ ]";
|
||||
}
|
||||
|
||||
function taskStepClassName(status: StatusChecklistItem["status"]): string {
|
||||
if (status === "completed") return "codex-panel__task-step codex-panel__task-step--completed";
|
||||
if (status === "inProgress") return "codex-panel__task-step codex-panel__task-step--inProgress";
|
||||
return "codex-panel__task-step";
|
||||
}
|
||||
|
||||
function ContextCompaction({ view }: { view: Extract<MessageStreamStatusView, { kind: "contextCompaction" }> }): UiNode {
|
||||
return (
|
||||
<StatusMessage label={view.label} className={view.className} state={view.state}>
|
||||
|
|
|
|||
|
|
@ -206,7 +206,16 @@ function RateLimitPanel({ rateLimit }: { rateLimit: RateLimitSummary | null }):
|
|||
<div className="codex-panel__limit-panel-title">Usage limit</div>
|
||||
<div className="codex-panel__limit-panel-list">
|
||||
{rateLimit.rows.map((row) => (
|
||||
<div key={`${row.label}:${row.value}:${row.resetLabel ?? ""}`} className={rateLimitRowClassName(row.level)}>
|
||||
<div
|
||||
key={`${row.label}:${row.value}:${row.resetLabel ?? ""}`}
|
||||
className={
|
||||
row.level === "danger"
|
||||
? "codex-panel__limit-panel-row codex-panel__limit-panel-row--danger"
|
||||
: row.level === "warn"
|
||||
? "codex-panel__limit-panel-row codex-panel__limit-panel-row--warn"
|
||||
: "codex-panel__limit-panel-row"
|
||||
}
|
||||
>
|
||||
<div className="codex-panel__limit-panel-label">{row.label}</div>
|
||||
<div className="codex-panel__limit-panel-value">{row.value}</div>
|
||||
<div className="codex-panel__limit-panel-meter-cell">
|
||||
|
|
@ -214,7 +223,11 @@ function RateLimitPanel({ rateLimit }: { rateLimit: RateLimitSummary | null }):
|
|||
className={[
|
||||
"codex-panel__limit-panel-meter",
|
||||
row.meterDivisions ? "codex-panel__limit-panel-meter--divided" : "",
|
||||
rateLimitMeterDivisionClassName(row.meterDivisions),
|
||||
row.meterDivisions === 5
|
||||
? "codex-panel__limit-panel-meter--5"
|
||||
: row.meterDivisions === 7
|
||||
? "codex-panel__limit-panel-meter--7"
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
|
|
@ -265,32 +278,23 @@ function DiagnosticSection({ section }: { section: ToolbarDiagnosticSection }):
|
|||
}
|
||||
|
||||
function DiagnosticRow({ row }: { row: ToolbarDiagnosticRow }): UiNode {
|
||||
const level = row.level ?? "normal";
|
||||
return (
|
||||
<div className={diagnosticRowClassName(row.level ?? "normal")}>
|
||||
<div
|
||||
className={
|
||||
level === "error"
|
||||
? "codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--error"
|
||||
: level === "warning"
|
||||
? "codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--warning"
|
||||
: "codex-panel__connection-diagnostics-row"
|
||||
}
|
||||
>
|
||||
<dt>{row.label}</dt>
|
||||
<dd>{row.value}</dd>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function rateLimitRowClassName(level: RateLimitSummary["rows"][number]["level"]): string {
|
||||
if (level === "danger") return "codex-panel__limit-panel-row codex-panel__limit-panel-row--danger";
|
||||
if (level === "warn") return "codex-panel__limit-panel-row codex-panel__limit-panel-row--warn";
|
||||
return "codex-panel__limit-panel-row";
|
||||
}
|
||||
|
||||
function rateLimitMeterDivisionClassName(divisions: RateLimitSummary["rows"][number]["meterDivisions"]): string {
|
||||
if (divisions === 5) return "codex-panel__limit-panel-meter--5";
|
||||
if (divisions === 7) return "codex-panel__limit-panel-meter--7";
|
||||
return "";
|
||||
}
|
||||
|
||||
function diagnosticRowClassName(level: NonNullable<ToolbarDiagnosticRow["level"]>): string {
|
||||
if (level === "error") return "codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--error";
|
||||
if (level === "warning") return "codex-panel__connection-diagnostics-row codex-panel__connection-diagnostics-row--warning";
|
||||
return "codex-panel__connection-diagnostics-row";
|
||||
}
|
||||
|
||||
function ThreadList({ threads, actions }: { threads: ToolbarThreadRow[]; actions: ToolbarThreadActions }): UiNode {
|
||||
if (threads.length === 0) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ function TurnDiffHeader({
|
|||
<div className="codex-panel-chat-turn-diff__title-block">
|
||||
<div className="codex-panel-chat-turn-diff__title">Turn diff</div>
|
||||
<div className="codex-panel-chat-turn-diff__meta">
|
||||
{shortThreadId(state.threadId)} / {shortThreadId(state.turnId)} · {fileCountLabel(state.files)}
|
||||
{shortThreadId(state.threadId)} / {shortThreadId(state.turnId)} ·{" "}
|
||||
{state.files.length === 1 ? "Edited 1 file" : `Edited ${String(state.files.length)} files`}
|
||||
</div>
|
||||
</div>
|
||||
{copyDiff ? (
|
||||
|
|
@ -84,7 +85,3 @@ function ChangedFiles({ files }: { files: string[] }): UiNode {
|
|||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
function fileCountLabel(files: string[]): string {
|
||||
return files.length === 1 ? "Edited 1 file" : `Edited ${String(files.length)} files`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,13 @@ export async function runSelectionRewrite(options: RunSelectionRewriteOptions):
|
|||
timedOutMessage: "Timed out while rewriting the selection.",
|
||||
abortMessage: "Selection rewrite cancelled.",
|
||||
signal: options.signal,
|
||||
resolveRuntime: runtimeSettings ? (client) => selectionRewriteRuntimeOverrideForClient(client, runtimeSettings) : undefined,
|
||||
resolveRuntime: runtimeSettings
|
||||
? (client) =>
|
||||
resolvedRuntimeOverrideForClient(client, {
|
||||
model: runtimeSettings.rewriteSelectionModel,
|
||||
effort: runtimeSettings.rewriteSelectionEffort,
|
||||
})
|
||||
: undefined,
|
||||
clientFactory: options.clientFactory,
|
||||
onProgress: (event) => {
|
||||
if (event.type === "reasoning-activity") {
|
||||
|
|
@ -71,7 +77,3 @@ export async function runSelectionRewrite(options: RunSelectionRewriteOptions):
|
|||
if (!output) throw new SelectionRewriteOutputError("Codex did not return a valid selection rewrite response.", rawText);
|
||||
return output;
|
||||
}
|
||||
|
||||
async function selectionRewriteRuntimeOverrideForClient(client: ModelMetadataClient, settings: SelectionRewriteRuntimeSettings) {
|
||||
return resolvedRuntimeOverrideForClient(client, { model: settings.rewriteSelectionModel, effort: settings.rewriteSelectionEffort });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const THREAD_PICKER_MODIFIER_ENTER_LISTENER_OPTIONS = { capture: true } as const
|
|||
|
||||
export async function openThreadPicker(host: ThreadPickerHost): Promise<void> {
|
||||
try {
|
||||
const threads = await loadThreadPickerThreads(host);
|
||||
const threads = await host.threadCatalog.loadActive();
|
||||
if (threads.length === 0) {
|
||||
new Notice("No Codex threads found.");
|
||||
return;
|
||||
|
|
@ -70,10 +70,6 @@ function threadOpenModeFromEvent(evt: MouseEvent | KeyboardEvent): ThreadOpenMod
|
|||
return "current";
|
||||
}
|
||||
|
||||
async function loadThreadPickerThreads(host: ThreadPickerHost): Promise<readonly Thread[]> {
|
||||
return host.threadCatalog.loadActive();
|
||||
}
|
||||
|
||||
class ThreadPickerModal extends SuggestModal<ThreadSuggestion> {
|
||||
constructor(
|
||||
private readonly host: ThreadPickerHost,
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export class ThreadsViewSession {
|
|||
renderThreadsViewShell(
|
||||
this.environment.root,
|
||||
{
|
||||
status: threadsViewStatusText(this.status),
|
||||
status: this.status.kind === "idle" ? null : this.status.message,
|
||||
loading: this.refreshLifecycle.kind === "loading",
|
||||
rows: threadRows(
|
||||
this.threads,
|
||||
|
|
@ -359,7 +359,3 @@ export class ThreadsViewSession {
|
|||
return this.environment.viewWindow() ?? window;
|
||||
}
|
||||
}
|
||||
|
||||
function threadsViewStatusText(status: ThreadsViewStatus): string | null {
|
||||
return status.kind === "idle" ? null : status.message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,9 @@ export function threadRows(
|
|||
.map((thread) => {
|
||||
const threadSnapshots = snapshotsByThread.get(thread.id) ?? [];
|
||||
const live = liveStateForSnapshots(threadSnapshots);
|
||||
const selected = selectedStateForSnapshots(threadSnapshots);
|
||||
const core = threadRowCoreProjection({
|
||||
thread,
|
||||
selected,
|
||||
selected: threadSnapshots.some((snapshot) => snapshot.threadId !== null && snapshot.lastFocused),
|
||||
renameState: renameStates.get(thread.id),
|
||||
archiveConfirmActive: archiveConfirmThreadId === thread.id,
|
||||
defaultArchiveSaveMarkdown,
|
||||
|
|
@ -77,10 +76,6 @@ function liveStateForSnapshots(snapshots: OpenCodexPanelSnapshot[]): ThreadsLive
|
|||
};
|
||||
}
|
||||
|
||||
function selectedStateForSnapshots(snapshots: OpenCodexPanelSnapshot[]): boolean {
|
||||
return snapshots.some((snapshot) => snapshot.threadId !== null && snapshot.lastFocused);
|
||||
}
|
||||
|
||||
export function transitionThreadsRenameState(
|
||||
state: ThreadsRenameLifecycleState,
|
||||
event: ThreadsRenameLifecycleEvent,
|
||||
|
|
|
|||
|
|
@ -15,26 +15,19 @@ import {
|
|||
transitionSettingsDynamicSectionLifecycle,
|
||||
} from "./lifecycle";
|
||||
|
||||
function archivedThreadTitleForStatus(thread: Thread | undefined, threadId: string): string {
|
||||
return thread ? threadArchiveDisplayTitle(thread) : threadId;
|
||||
}
|
||||
|
||||
interface LoadedHookCatalog extends HookCatalog {
|
||||
status: string;
|
||||
}
|
||||
|
||||
async function loadHookCatalog(client: AppServerClient, cwd: string): Promise<LoadedHookCatalog> {
|
||||
const hooks = await listHookCatalog(client, cwd);
|
||||
const hookCount = hooks.hooks.length;
|
||||
return {
|
||||
...hooks,
|
||||
status: hooksStatus(hooks.hooks.length),
|
||||
status: `Loaded ${String(hookCount)} hook${hookCount === 1 ? "" : "s"}.`,
|
||||
};
|
||||
}
|
||||
|
||||
function hooksStatus(count: number): string {
|
||||
return `Loaded ${String(count)} hook${count === 1 ? "" : "s"}.`;
|
||||
}
|
||||
|
||||
interface SettingsDynamicSectionsControllerCallbacks {
|
||||
display(target: SettingsDynamicSectionsDisplayTarget): void;
|
||||
notify(message: string): void;
|
||||
|
|
@ -369,10 +362,8 @@ export class SettingsDynamicSectionsController {
|
|||
}
|
||||
|
||||
async deleteArchivedThread(threadId: string): Promise<void> {
|
||||
const title = archivedThreadTitleForStatus(
|
||||
this.archivedThreads.find((thread) => thread.id === threadId),
|
||||
threadId,
|
||||
);
|
||||
const thread = this.archivedThreads.find((item) => item.id === threadId);
|
||||
const title = thread ? threadArchiveDisplayTitle(thread) : threadId;
|
||||
await this.runDynamicSectionOperation({
|
||||
section: "archivedThreads",
|
||||
loadingStatus: "Loading archived threads...",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export function normalizeSettings(storedSettings: unknown): CodexPanelSettings {
|
|||
rewriteSelectionModel: modelOrDefault(record["rewriteSelectionModel"], DEFAULT_SETTINGS.rewriteSelectionModel),
|
||||
rewriteSelectionEffort: reasoningEffortOrDefault(record["rewriteSelectionEffort"], DEFAULT_SETTINGS.rewriteSelectionEffort),
|
||||
showToolbar: booleanOrDefault(record["showToolbar"], DEFAULT_SETTINGS.showToolbar),
|
||||
sendShortcut: sendShortcutOrDefault(record["sendShortcut"]),
|
||||
sendShortcut: record["sendShortcut"] === "mod-enter" ? "mod-enter" : DEFAULT_SETTINGS.sendShortcut,
|
||||
scrollThreadFromComposerEdges: booleanOrDefault(
|
||||
record["scrollThreadFromComposerEdges"],
|
||||
DEFAULT_SETTINGS.scrollThreadFromComposerEdges,
|
||||
|
|
@ -90,10 +90,6 @@ function reasoningEffortOrDefault(value: unknown, fallback: ReasoningEffort | nu
|
|||
return normalizeReasoningEffort(value) ?? fallback;
|
||||
}
|
||||
|
||||
function sendShortcutOrDefault(value: unknown): SendShortcut {
|
||||
return value === "mod-enter" ? "mod-enter" : DEFAULT_SETTINGS.sendShortcut;
|
||||
}
|
||||
|
||||
export function getVaultPath(app: App): string {
|
||||
const adapter = app.vault.adapter;
|
||||
if (adapter instanceof FileSystemAdapter) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ let sourceSequence = 0;
|
|||
|
||||
export function createLocalIdSource(options: LocalIdSourceOptions = {}): LocalIdSource {
|
||||
const nowMs = options.nowMs ?? Date.now;
|
||||
const seed = sanitizeIdPart(options.seed ?? defaultIdSeed());
|
||||
const seed = sanitizeIdPart(options.seed ?? Date.now().toString(36));
|
||||
sourceSequence += 1;
|
||||
const sourceId = `${seed}-${sourceSequence.toString(36)}`;
|
||||
let itemSequence = 0;
|
||||
|
|
@ -24,10 +24,6 @@ export function createLocalIdSource(options: LocalIdSourceOptions = {}): LocalId
|
|||
};
|
||||
}
|
||||
|
||||
function defaultIdSeed(): string {
|
||||
return Date.now().toString(36);
|
||||
}
|
||||
|
||||
function sanitizeIdPart(value: string): string {
|
||||
return value.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 24) || "local";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue