Skip to content

Commit a0023fb

Browse files
committed
perf: speed up local TUI startup
1 parent d0ab0d9 commit a0023fb

9 files changed

Lines changed: 325 additions & 26 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
3232
- Agent transcript: include OpenClaw agent session logs when finding local transcript candidates.
3333
- Crabbox: bootstrap raw AWS macOS shell commands wrapped in absolute `time` paths so RSS probes can run Node and pnpm on fresh macOS runners.
3434
- Crabbox: bootstrap raw AWS macOS shell commands even when setup statements precede Node or pnpm usage.
35+
- TUI/local: skip unnecessary secret resolution, gateway model catalog loading, bootstrap, and skill scans in explicit local-model runs so startup reaches the model request faster.
3536
- Sessions/doctor: load large session stores without clone amplification during read-only doctor checks and reclaim stale `sessions.json.*.tmp` sidecars. Fixes #56827. Thanks @openperf.
3637
- Tests: clean successful plugin gateway gauntlet isolated temp roots while keeping an explicit preservation switch for failed/debug runs.
3738
- Plugins/perf: reuse derived plugin metadata snapshots for the lifetime of the process so reply-time skill setup no longer rescans plugin metadata on every turn.

src/agents/agent-command.live-model-switch.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const state = vi.hoisted(() => ({
2626
resolveAcpExplicitTurnPolicyErrorMock: vi.fn(),
2727
runWithModelFallbackMock: vi.fn(),
2828
runAgentAttemptMock: vi.fn(),
29+
resolveAgentSkillsFilterMock: vi.fn((): string[] | undefined => undefined),
2930
resolveEffectiveModelFallbacksMock: vi.fn().mockReturnValue(undefined),
3031
emitAgentEventMock: vi.fn(),
3132
registerAgentRunContextMock: vi.fn(),
@@ -312,7 +313,7 @@ vi.mock("./agent-scope.js", () => ({
312313
resolveEffectiveModelFallbacks: state.resolveEffectiveModelFallbacksMock,
313314
resolveSessionAgentIds: () => ({ defaultAgentId: "default", sessionAgentId: "default" }),
314315
resolveSessionAgentId: () => "default",
315-
resolveAgentSkillsFilter: () => undefined,
316+
resolveAgentSkillsFilter: (...args: unknown[]) => state.resolveAgentSkillsFilterMock(...args),
316317
resolveAgentWorkspaceDir: () => "/tmp/workspace",
317318
}));
318319

@@ -757,6 +758,7 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
757758
state.runtimeConfigMock = undefined;
758759
state.isThinkingLevelSupportedMock.mockReturnValue(true);
759760
state.resolveThinkingDefaultMock.mockReturnValue("low");
761+
state.resolveAgentSkillsFilterMock.mockReturnValue(undefined);
760762
state.loadManifestModelCatalogMock.mockReturnValue([]);
761763
state.acpRunTurnMock.mockImplementation(async (params: unknown) => {
762764
const onEvent = (params as { onEvent?: (event: unknown) => void }).onEvent;
@@ -1002,6 +1004,7 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
10021004
],
10031005
},
10041006
},
1007+
list: [{ id: "main", default: true, skills: [] }],
10051008
},
10061009
};
10071010
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {
@@ -1426,6 +1429,42 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
14261429
expect(state.buildWorkspaceSkillSnapshotMock).toHaveBeenCalledTimes(1);
14271430
});
14281431

1432+
it("uses an empty skill snapshot without loading workspace skills for an empty skill filter", async () => {
1433+
state.runtimeConfigMock = {
1434+
agents: {
1435+
defaults: {
1436+
models: {
1437+
"anthropic/claude": {},
1438+
"openai/claude": {},
1439+
"openai/gpt-5.4": {},
1440+
},
1441+
skills: [],
1442+
},
1443+
},
1444+
};
1445+
state.sessionEntryMock = {
1446+
sessionId: "session-1",
1447+
updatedAt: Date.now(),
1448+
};
1449+
state.resolveAgentSkillsFilterMock.mockReturnValue([]);
1450+
setupSingleAttemptFallback();
1451+
state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("anthropic", "claude"));
1452+
1453+
await runBasicAgentCommand();
1454+
1455+
const attemptParams = mockCallArg(state.runAgentAttemptMock) as {
1456+
skillsSnapshot?: Record<string, unknown>;
1457+
};
1458+
expectRecordFields(attemptParams?.skillsSnapshot, {
1459+
prompt: "",
1460+
skills: [],
1461+
resolvedSkills: [],
1462+
skillFilter: [],
1463+
version: 0,
1464+
});
1465+
expect(state.buildWorkspaceSkillSnapshotMock).not.toHaveBeenCalled();
1466+
});
1467+
14291468
it("classifies empty embedded run results before model fallback accepts them", async () => {
14301469
let observedClassification: unknown;
14311470
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {

src/agents/agent-command.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-codex-ro
9797
import { classifyEmbeddedPiRunResultForModelFallback } from "./pi-embedded-runner/result-fallback-classifier.js";
9898
import { resolveProviderIdForAuth } from "./provider-auth-aliases.js";
9999
import { hydrateResolvedSkillsAsync } from "./skills/snapshot-hydration.js";
100+
import type { SkillSnapshot } from "./skills/types.js";
100101
import { normalizeSpawnedRunMetadata } from "./spawned-context.js";
101102
import { resolveAgentTimeoutMs } from "./timeout.js";
102103
import { ensureAgentWorkspace } from "./workspace.js";
@@ -161,6 +162,19 @@ const skillsRemoteRuntimeLoader = createLazyImportLoader<SkillsRemoteRuntime>(
161162
() => import("../infra/skills-remote.js"),
162163
);
163164

165+
function createEmptySkillsSnapshot(params: {
166+
skillFilter: string[];
167+
version?: number;
168+
}): SkillSnapshot {
169+
return {
170+
prompt: "",
171+
skills: [],
172+
resolvedSkills: [],
173+
skillFilter: params.skillFilter,
174+
version: params.version,
175+
};
176+
}
177+
164178
function loadAttemptExecutionRuntime(): Promise<AttemptExecutionRuntime> {
165179
return attemptExecutionRuntimeLoader.load();
166180
}
@@ -780,7 +794,14 @@ async function agentCommandInternal(
780794
shouldRefreshSnapshotForVersion(currentSkillsSnapshot.version, skillsSnapshotVersion) ||
781795
!matchesSkillFilter(currentSkillsSnapshot.skillFilter, skillFilter);
782796
const needsSkillsSnapshot = isNewSession || shouldRefreshSkillsSnapshot;
797+
const emptySkillsFilter = skillFilter !== undefined && skillFilter.length === 0;
783798
const buildSkillsSnapshot = async () => {
799+
if (emptySkillsFilter) {
800+
return createEmptySkillsSnapshot({
801+
skillFilter,
802+
version: skillsSnapshotVersion,
803+
});
804+
}
784805
const [
785806
{ buildWorkspaceSkillSnapshot },
786807
{ getRemoteSkillEligibility },

src/agents/agent-runtime-config.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export async function resolveAgentRuntimeConfig(
1414
cfg: OpenClawConfig;
1515
}> {
1616
const loadedRaw = getRuntimeConfig();
17+
const includeChannelTargets = params?.runtimeTargetsChannelSecrets === true;
18+
const hasRuntimeSecretRefs = hasAgentRuntimeSecretRefs({
19+
config: loadedRaw,
20+
includeChannelTargets,
21+
});
1722
const sourceConfig = await (async () => {
1823
try {
1924
const { snapshot } = await readConfigFileSnapshotForWrite();
@@ -25,11 +30,7 @@ export async function resolveAgentRuntimeConfig(
2530
}
2631
return loadedRaw;
2732
})();
28-
const includeChannelTargets = params?.runtimeTargetsChannelSecrets === true;
29-
const cfg = hasAgentRuntimeSecretRefs({
30-
config: loadedRaw,
31-
includeChannelTargets,
32-
})
33+
const cfg = hasRuntimeSecretRefs
3334
? (
3435
await (
3536
await import("../cli/command-config-resolution.runtime.js")

src/agents/skills/workspace.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1100,8 +1100,11 @@ function resolveWorkspaceSkillPromptState(
11001100
prompt: string;
11011101
resolvedSkills: Skill[];
11021102
} {
1103-
const skillEntries = opts?.entries ?? loadSkillEntries(workspaceDir, opts);
11041103
const effectiveSkillFilter = resolveEffectiveWorkspaceSkillFilter(opts);
1104+
if (effectiveSkillFilter !== undefined && effectiveSkillFilter.length === 0) {
1105+
return { eligible: [], prompt: "", resolvedSkills: [] };
1106+
}
1107+
const skillEntries = opts?.entries ?? loadSkillEntries(workspaceDir, opts);
11051108
const eligible = filterSkillEntries(
11061109
skillEntries,
11071110
opts?.config,

src/commands/agent.runtime-config.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ describe("agentCommand runtime config", () => {
201201

202202
const prepared = await resolveAgentRuntimeConfig(runtime);
203203

204+
expect(readConfigFileSnapshotForWriteMock).toHaveBeenCalledTimes(1);
204205
expect(resolveCommandConfigWithSecretsMock).not.toHaveBeenCalled();
206+
expect(setRuntimeConfigSnapshotMock).toHaveBeenCalledWith(loadedConfig, loadedConfig);
205207
expect(prepared.cfg).toBe(loadedConfig);
206208
});
207209
});

0 commit comments

Comments
 (0)