Skip to content

Commit 9deea4e

Browse files
committed
feat: add operations pulse and evidence surfaces
1 parent 5a06149 commit 9deea4e

39 files changed

Lines changed: 1869 additions & 144 deletions

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,16 @@ Operational docs: https://swarmclaw.ai/docs/observability
399399

400400
## Releases
401401

402+
### v1.8.1 Highlights
403+
404+
Operator evidence release: a focused follow-up that makes release and mission review easier to scan.
405+
406+
- **Operations Pulse.** Home and Quality now share a live triage panel that rolls missions, runs, approvals, connector readiness, and budget pressure into one next-action queue.
407+
- **Run Briefs.** Run detail sheets now open with a deterministic brief: objective, owner, timeline, warnings, usage, and evidence before the raw replay log.
408+
- **Evidence Shelf.** Runs and missions now expose linked artifacts, task outputs, protocol outputs, mission reports, public share links, and knowledge citations through a shared artifact resolver.
409+
- **Connector readiness.** Connector cards now show credential, route, pairing, gateway, connection, and doctor hints so setup gaps are visible before a platform bridge is started.
410+
- **API surface.** Added `GET /api/operations/pulse`, `GET /api/runs/:id/brief`, and `GET /api/artifacts` for external operator dashboards and release tooling.
411+
402412
### v1.8.0 Highlights
403413

404414
Mission Command release: a bigger operator update that makes autonomous missions easier to launch, inspect, and share.

next.config.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,39 @@ import path from "path";
66
import { fileURLToPath } from "url";
77

88
const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url))
9-
const RUNTIME_STATE_GLOBS = [
10-
'data/**/*',
11-
'.tmp-swarmclaw-build/**/*',
9+
const OUTPUT_TRACE_EXCLUDE_GLOBS = [
10+
'./.env*',
11+
'./.git/**/*',
12+
'./.next/cache/**/*',
13+
'./.tmp-swarmclaw-build/**/*',
14+
'./AGENTS.md',
15+
'./CLAUDE.md',
16+
'./CONTRIBUTING.md',
17+
'./Dockerfile',
18+
'./Dockerfile.*',
19+
'./README.md',
20+
'./SWARMDOCK.md',
21+
'./artifacts/**/*',
22+
'./components.json',
23+
'./coverage/**/*',
24+
'./data/**/*',
25+
'./daemon.log',
26+
'./docker-compose.yml',
27+
'./electron-builder.yml',
28+
'./electron-dist/**/*',
29+
'./eslint.config.mjs',
30+
'./fly.toml',
31+
'./install.sh',
32+
'./next.config.ts',
33+
'./package-lock.json',
34+
'./postcss.config.mjs',
35+
'./railway.json',
36+
'./release/**/*',
37+
'./render.yaml',
38+
'./research.md',
39+
'./swarmclaw-skill.md',
40+
'./test-results/**/*',
41+
'./tsconfig.json',
1242
]
1343

1444
function getGitSha(): string {
@@ -50,11 +80,11 @@ function getAllowedDevOrigins(): string[] {
5080
const nextConfig: NextConfig = {
5181
output: 'standalone',
5282
outputFileTracingExcludes: {
53-
'/*': RUNTIME_STATE_GLOBS,
54-
'/api/**': RUNTIME_STATE_GLOBS,
55-
instrumentation: RUNTIME_STATE_GLOBS,
56-
'/instrumentation': RUNTIME_STATE_GLOBS,
57-
'next-server': RUNTIME_STATE_GLOBS,
83+
'/*': OUTPUT_TRACE_EXCLUDE_GLOBS,
84+
'/api/**': OUTPUT_TRACE_EXCLUDE_GLOBS,
85+
instrumentation: OUTPUT_TRACE_EXCLUDE_GLOBS,
86+
'/instrumentation': OUTPUT_TRACE_EXCLUDE_GLOBS,
87+
'next-server': OUTPUT_TRACE_EXCLUDE_GLOBS,
5888
},
5989
turbopack: {
6090
// Pin workspace root to the project directory so a stale lockfile

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@swarmclawai/swarmclaw",
3-
"version": "1.8.0",
3+
"version": "1.8.1",
44
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
55
"main": "electron-dist/main.js",
66
"license": "MIT",
@@ -87,7 +87,7 @@
8787
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/electron-after-pack.test.mjs scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
8888
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
8989
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/connectors/swarmdock.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
90-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
90+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/runs/run-brief.test.ts src/lib/server/operations/operation-pulse.test.ts src/lib/server/artifacts/artifact-resolver.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/server/sharing/share-link-repository.test.ts src/lib/server/sharing/share-resolver.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
9191
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
9292
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
9393
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",

scripts/run-next-build.mjs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import { ensureBuildBootstrapPaths } from './build-bootstrap-env.mjs'
1111

1212
const require = createRequire(import.meta.url)
1313

14-
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '12288'
14+
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '24576'
1515
export const MIN_MAX_OLD_SPACE_SIZE_MB = 1024
1616
export const FALLBACK_MIN_MAX_OLD_SPACE_SIZE_MB = 512
1717
export const RESERVED_BUILD_MEMORY_MB = 768
18-
export const MAX_OLD_SPACE_RATIO = 0.75
18+
export const MAX_OLD_SPACE_RATIO = 0.85
1919
export const LOW_MEMORY_RATIO = 0.6
2020
export const BUILD_MAX_OLD_SPACE_SIZE_ENV = 'SWARMCLAW_BUILD_MAX_OLD_SPACE_SIZE_MB'
21+
export const BUILD_BUNDLER_ENV = 'SWARMCLAW_BUILD_BUNDLER'
22+
export const DEFAULT_BUILD_BUNDLER = 'turbopack'
2123
export const CGROUP_MEMORY_LIMIT_PATHS = [
2224
'/sys/fs/cgroup/memory.max',
2325
'/sys/fs/cgroup/memory/memory.limit_in_bytes',
@@ -40,6 +42,15 @@ export const REQUIRED_STANDALONE_BROWSER_PACKAGES = [
4042
'playwright',
4143
'playwright-core',
4244
]
45+
export const STANDALONE_LOCAL_STATE_ENTRIES = [
46+
'.git',
47+
'.tmp-swarmclaw-build',
48+
'artifacts',
49+
'coverage',
50+
'data',
51+
'release',
52+
'test-results',
53+
]
4354

4455
function parsePositiveInteger(value) {
4556
const parsed = Number.parseInt(String(value ?? '').trim(), 10)
@@ -133,6 +144,18 @@ export function buildNextBuildEnv(
133144
}
134145
}
135146

147+
export function resolveNextBuildBundlerFlag(args = [], env = process.env) {
148+
if (args.includes('--webpack') || args.includes('--turbopack')) return null
149+
150+
const requested = String(env[BUILD_BUNDLER_ENV] || DEFAULT_BUILD_BUNDLER).trim().toLowerCase()
151+
if (requested === 'webpack') return '--webpack'
152+
if (requested === 'turbopack') return '--turbopack'
153+
154+
throw new Error(
155+
`${BUILD_BUNDLER_ENV} must be "turbopack" or "webpack"; received ${JSON.stringify(requested)}.`,
156+
)
157+
}
158+
136159
export function hasTraceCopyWarning(output = '') {
137160
return output.includes(TRACE_COPY_WARNING)
138161
}
@@ -239,14 +262,36 @@ export function repairStandaloneNextMetadata(cwd = process.cwd()) {
239262
return true
240263
}
241264

265+
export function pruneStandaloneLocalState(cwd = process.cwd()) {
266+
const standaloneDir = path.join(cwd, '.next', 'standalone')
267+
if (!fs.existsSync(standaloneDir)) return false
268+
269+
let pruned = false
270+
for (const entry of STANDALONE_LOCAL_STATE_ENTRIES) {
271+
const target = path.join(standaloneDir, entry)
272+
if (!fs.existsSync(target)) continue
273+
fs.rmSync(target, { recursive: true, force: true })
274+
pruned = true
275+
}
276+
277+
for (const entry of fs.readdirSync(standaloneDir, { withFileTypes: true })) {
278+
if (entry.name !== '.env' && !entry.name.startsWith('.env.')) continue
279+
fs.rmSync(path.join(standaloneDir, entry.name), { recursive: entry.isDirectory(), force: true })
280+
pruned = true
281+
}
282+
283+
return pruned
284+
}
285+
242286
export function runNextBuild(
243287
args = process.argv.slice(2),
244288
env = process.env,
245289
cwd = process.cwd(),
246290
maxOldSpaceSizeMb = resolveNextBuildMaxOldSpaceSizeMb(env),
247291
) {
248292
const nextBin = require.resolve('next/dist/bin/next')
249-
return spawnSync(process.execPath, [nextBin, 'build', '--webpack', ...args], {
293+
const bundlerFlag = resolveNextBuildBundlerFlag(args, env)
294+
return spawnSync(process.execPath, [nextBin, 'build', ...(bundlerFlag ? [bundlerFlag] : []), ...args], {
250295
stdio: 'pipe',
251296
encoding: 'utf-8',
252297
env: buildNextBuildEnv(env, maxOldSpaceSizeMb, cwd),
@@ -277,6 +322,9 @@ function main() {
277322
if (result.status === 0 && repairStandaloneBrowserMcpRuntime(process.cwd())) {
278323
console.error('Copied Playwright MCP runtime packages into standalone build output.')
279324
}
325+
if (result.status === 0 && pruneStandaloneLocalState(process.cwd())) {
326+
console.error('Pruned local state and release artifacts from standalone build output.')
327+
}
280328
process.exit(result.status)
281329
}
282330
if (result.signal) {

scripts/run-next-build.test.mjs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { describe, it } from 'node:test'
66

77
import { BUILD_BOOTSTRAP_ROOT_NAME } from './build-bootstrap-env.mjs'
88
import {
9+
BUILD_BUNDLER_ENV,
910
BUILD_MAX_OLD_SPACE_SIZE_ENV,
1011
DEFAULT_MAX_OLD_SPACE_SIZE_MB,
1112
NEXT_STANDALONE_METADATA_RELATIVE_DIR,
@@ -15,10 +16,12 @@ import {
1516
deriveMaxOldSpaceSizeMb,
1617
hasTraceCopyWarning,
1718
mergeNodeOptions,
19+
pruneStandaloneLocalState,
1820
repairStandaloneBrowserMcpRuntime,
1921
readCgroupMemoryLimitBytes,
2022
repairStandaloneCssTreeData,
2123
repairStandaloneNextMetadata,
24+
resolveNextBuildBundlerFlag,
2225
resolveNextBuildMaxOldSpaceSizeMb,
2326
} from './run-next-build.mjs'
2427

@@ -47,7 +50,7 @@ describe('run-next-build', () => {
4750
it('derives a lower heap cap for constrained Docker-style memory limits', () => {
4851
assert.equal(
4952
deriveMaxOldSpaceSizeMb(4 * 1024 * 1024 * 1024),
50-
'3072',
53+
'3328',
5154
)
5255
assert.equal(
5356
deriveMaxOldSpaceSizeMb(2 * 1024 * 1024 * 1024),
@@ -85,12 +88,12 @@ describe('run-next-build', () => {
8588
assert.equal(
8689
resolveNextBuildMaxOldSpaceSizeMb(
8790
{},
88-
{
89-
readCgroupMemoryLimitBytes: () => 4 * 1024 * 1024 * 1024,
90-
totalMem: () => 16 * 1024 * 1024 * 1024,
91-
},
92-
),
93-
'3072',
91+
{
92+
readCgroupMemoryLimitBytes: () => 4 * 1024 * 1024 * 1024,
93+
totalMem: () => 16 * 1024 * 1024 * 1024,
94+
},
95+
),
96+
'3328',
9497
)
9598
})
9699

@@ -112,6 +115,16 @@ describe('run-next-build', () => {
112115
assert.equal(env.SWARMCLAW_BUILD_MODE, 'custom')
113116
})
114117

118+
it('uses Turbopack by default and supports Webpack override', () => {
119+
assert.equal(resolveNextBuildBundlerFlag([], {}), '--turbopack')
120+
assert.equal(resolveNextBuildBundlerFlag([], { [BUILD_BUNDLER_ENV]: 'webpack' }), '--webpack')
121+
assert.equal(resolveNextBuildBundlerFlag(['--webpack'], {}), null)
122+
assert.throws(
123+
() => resolveNextBuildBundlerFlag([], { [BUILD_BUNDLER_ENV]: 'rspack' }),
124+
/SWARMCLAW_BUILD_BUNDLER/,
125+
)
126+
})
127+
115128
it('detects standalone trace copy warnings in build output', () => {
116129
assert.equal(hasTraceCopyWarning('all good'), false)
117130
assert.equal(
@@ -270,4 +283,28 @@ describe('run-next-build', () => {
270283
fs.rmSync(tempDir, { recursive: true, force: true })
271284
}
272285
})
286+
287+
it('pruneStandaloneLocalState removes local runtime and release output from standalone', () => {
288+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-standalone-prune-'))
289+
try {
290+
const standaloneDir = path.join(tempDir, '.next', 'standalone')
291+
fs.mkdirSync(path.join(standaloneDir, 'data'), { recursive: true })
292+
fs.mkdirSync(path.join(standaloneDir, 'release'), { recursive: true })
293+
fs.mkdirSync(path.join(standaloneDir, 'artifacts'), { recursive: true })
294+
fs.mkdirSync(path.join(standaloneDir, 'node_modules'), { recursive: true })
295+
fs.writeFileSync(path.join(standaloneDir, '.env.local'), 'SECRET=value\n')
296+
fs.writeFileSync(path.join(standaloneDir, 'server.js'), 'require("next")\n')
297+
298+
const pruned = pruneStandaloneLocalState(tempDir)
299+
assert.equal(pruned, true)
300+
assert.equal(fs.existsSync(path.join(standaloneDir, 'data')), false)
301+
assert.equal(fs.existsSync(path.join(standaloneDir, 'release')), false)
302+
assert.equal(fs.existsSync(path.join(standaloneDir, 'artifacts')), false)
303+
assert.equal(fs.existsSync(path.join(standaloneDir, '.env.local')), false)
304+
assert.equal(fs.existsSync(path.join(standaloneDir, 'node_modules')), true)
305+
assert.equal(fs.existsSync(path.join(standaloneDir, 'server.js')), true)
306+
} finally {
307+
fs.rmSync(tempDir, { recursive: true, force: true })
308+
}
309+
})
273310
})

src/app/api/artifacts/route.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { NextResponse } from 'next/server'
2+
import { listEvidenceArtifacts } from '@/lib/server/artifacts/artifact-resolver'
3+
4+
export const dynamic = 'force-dynamic'
5+
6+
export async function GET(req: Request) {
7+
const { searchParams } = new URL(req.url)
8+
const runId = searchParams.get('runId')
9+
const missionId = searchParams.get('missionId')
10+
const taskId = searchParams.get('taskId')
11+
if (!runId && !missionId && !taskId) {
12+
return NextResponse.json({ error: 'runId, missionId, or taskId is required' }, { status: 400 })
13+
}
14+
return NextResponse.json(listEvidenceArtifacts({ runId, missionId, taskId }))
15+
}

src/app/api/clawhub/install/route.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async function writeClawHubBundleToWorkspace(bundle: ClawHubSkillBundle): Promis
4848
.filter((entry): entry is { file: ClawHubSkillBundle['files'][number], path: string } => Boolean(entry.path))
4949

5050
const workspaceSkillsDir = resolveWorkspaceSkillsDir()
51-
const targetDir = path.join(workspaceSkillsDir, sanitizeSkillDirName(bundle.slug))
51+
const targetDir = path.join(/*turbopackIgnore: true*/ workspaceSkillsDir, sanitizeSkillDirName(bundle.slug))
5252
const normalizedPaths = stripSharedTopLevelDir(normalizedEntries.map((entry) => entry.path))
5353

5454
await fs.rm(targetDir, { recursive: true, force: true })
@@ -57,12 +57,12 @@ async function writeClawHubBundleToWorkspace(bundle: ClawHubSkillBundle): Promis
5757
for (let index = 0; index < normalizedEntries.length; index += 1) {
5858
const relativePath = normalizedPaths[index]
5959
if (!relativePath) continue
60-
const destination = path.join(targetDir, relativePath)
60+
const destination = path.join(/*turbopackIgnore: true*/ targetDir, relativePath)
6161
if (!destination.startsWith(targetDir + path.sep) && destination !== targetDir) {
6262
throw new Error(`Refusing to write bundle file outside the target directory: ${relativePath}`)
6363
}
64-
await fs.mkdir(path.dirname(destination), { recursive: true })
65-
await fs.writeFile(destination, normalizedEntries[index].file.content)
64+
await fs.mkdir(/*turbopackIgnore: true*/ path.dirname(destination), { recursive: true })
65+
await fs.writeFile(/*turbopackIgnore: true*/ destination, normalizedEntries[index].file.content)
6666
}
6767

6868
clearDiscoveredSkillsCache()

src/app/api/dirs/route.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ export async function GET(req: NextRequest) {
99

1010
// Resolve ~ to home dir
1111
const resolved = targetDir.startsWith('~')
12-
? path.join(os.homedir(), targetDir.slice(1))
13-
: path.resolve(targetDir)
12+
? path.join(/*turbopackIgnore: true*/ os.homedir(), targetDir.slice(1))
13+
: path.resolve(/*turbopackIgnore: true*/ targetDir)
1414

1515
let dirs: Array<{ name: string; path: string }> = []
1616
try {
17-
dirs = fs.readdirSync(resolved)
17+
dirs = fs.readdirSync(/*turbopackIgnore: true*/ resolved)
1818
.filter(d => {
1919
if (d.startsWith('.')) return false
20-
try { return fs.statSync(path.join(resolved, d)).isDirectory() } catch { return false }
20+
try {
21+
const childPath = path.join(/*turbopackIgnore: true*/ resolved, d)
22+
return fs.statSync(/*turbopackIgnore: true*/ childPath).isDirectory()
23+
} catch { return false }
2124
})
2225
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
23-
.map(d => ({ name: d, path: path.join(resolved, d) }))
26+
.map(d => ({ name: d, path: path.join(/*turbopackIgnore: true*/ resolved, d) }))
2427
} catch {}
2528

2629
const parentPath = resolved === '/' ? null : path.dirname(resolved)

0 commit comments

Comments
 (0)