perf(coding-agent): concurrent skill loading + startup indicator through TUI import - #1282
perf(coding-agent): concurrent skill loading + startup indicator through TUI import#12821Morganmore wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
1 issue found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/coding-agent/src/core/resource-loader.ts">
<violation number="1" location="packages/coding-agent/src/core/resource-loader.ts:523">
P2: When callers invoke `extendResources()` without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| extendResources(paths: ResourceExtensionPaths): void { | ||
| async extendResources(paths: ResourceExtensionPaths): Promise<void> { |
There was a problem hiding this comment.
P2: When callers invoke extendResources() without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/coding-agent/src/core/resource-loader.ts, line 523:
<comment>When callers invoke `extendResources()` without awaiting it, newly discovered skills are not available on the next statement. Update every caller and the existing resource-loader tests to await the new Promise-returning API before reading resources.</comment>
<file context>
@@ -520,7 +520,7 @@ export class DefaultResourceLoader implements ResourceLoader {
}
- extendResources(paths: ResourceExtensionPaths): void {
+ async extendResources(paths: ResourceExtensionPaths): Promise<void> {
const skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);
const promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);
</file context>
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/coding-agent/test/skills.test.ts">
<violation number="1" location="packages/coding-agent/test/skills.test.ts:439">
P3: The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try { | ||
| expect(skills).toHaveLength(1); | ||
| expect(skills[0]?.name).toBe("dup-skill"); | ||
| } finally { |
There was a problem hiding this comment.
P3: The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/coding-agent/test/skills.test.ts, line 439:
<comment>The new try/finally only wraps the assertions, so if the async loadSkills() call above it rejects (e.g. a read failure), the mkdtempSync() temp directory is never cleaned up. Move the loadSkills() call inside the try so the created dir is always removed.</comment>
<file context>
@@ -433,7 +433,11 @@ describe("loadSkills canonical path dedupe", () => {
+ try {
+ expect(skills).toHaveLength(1);
+ expect(skills[0]?.name).toBe("dup-skill");
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
</file context>
loadSkillsFromDirInternal read every SKILL.md sequentially with readFileSync; with many skills (and slow disks) that serializes disk access during startup. Collect the file work in traversal order, then run the IO-bound reads with Promise.all while preserving skill/diagnostic order exactly. loadSkills and loadSkillsFromDir become async; extendResources, updateSkillsFromPaths and the app-server skills loader await them. Existing skills suite adapted to the async API (27 cases) plus one new canonical-path dedupe case; the imagegen skill-gating test awaits loadSkills.
…port The startup loading indicator stopped right after createAgentSessionRuntime, before the dynamic import of the interactive-mode module graph — the single largest cold-start cost after resource loading — leaving a blank terminal that looked frozen. Stop it only after the import (still before the TUI starts writing stdout), so the spinner covers the wait.
- reload(): await updateSkillsFromPaths so the loaded state is published before reload() completes (skills were assigned asynchronously after the caller observed the loader). - main.ts: stop the startup indicator in a finally around the interactive-mode import so an import rejection cannot leave the spinner running with the cursor hidden. - skills.test.ts: clean up the dedupe fixture temp dir. - resource-loader.test.ts / hooks-builtin-extension.test.ts: await the new Promise-returning extendResources API.
d83ea2e to
7eb6b8e
Compare
What
Two startup fixes in one branch:
loadSkillsFromDirInternalread everySKILL.mdsequentially withreadFileSync, serializing disk access at startup (a user with ~110 skills paid ~1.2s cold on this Windows host). It now collects the file work in traversal order and runs the IO-bound reads withPromise.all, preserving skill/diagnostic order exactly.loadSkills/loadSkillsFromDirbecome async;extendResources,updateSkillsFromPaths, the app-server skills loader, and the agent-session call site await them.createAgentSessionRuntime, before the dynamic import of theinteractive-modemodule graph (the largest cold-start cost after resource loading), leaving a blank terminal that looked frozen. It now stops only after the import.QA
test/skills.test.ts(existing 27-case suite adapted to the async API + 1 new canonical-path dedupe case) andtest/imagegen-skill-gating.test.ts→ 31/31 green (Vitest runs both files).packages/coding-agent/changes.mdupdated with both entries.bun run check/tsgofull static gates not run locally — this machine cannot build the sibling workspace packages; CI runs them.)Why it is enough
The refactor changes only WHEN reads happen (parallel, awaited before publish) and keeps the returned order; the adapted original suite is the behavior lock.
STOP WHEN
CI green and one approval.
Summary by cubic
Speeds up startup by loading skill files concurrently and keeps the startup indicator spinning through the TUI import so the terminal no longer appears frozen.
loadSkillsandloadSkillsFromDirare now async; all call sites await them, andreload()awaits skill loading so the loaded state is published before it completes.finally, so an import failure can't leave the spinner running with the cursor hidden.Written for commit 7eb6b8e. Summary will update on new commits.