Merge branch 'worktree/ci-native-windows-20260808' into worktree/ci-native-windows-coverage-20260808
# Conflicts: # packages/util/paths/src/index.ts
This commit is contained in:
@@ -6,7 +6,7 @@ import { Context } from 'cordis'
|
||||
import Hmr from '@cordisjs/plugin-hmr'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import Timer from '@cordisjs/plugin-timer'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
async function bootHmr(dir: string, root: string[] = []): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
@@ -33,7 +33,8 @@ describe('HMR exact config paths', () => {
|
||||
symlinkSync(target, alias, process.platform === 'win32' ? 'junction' : 'dir')
|
||||
writeFileSync(filename, 'export const generation = 0\n')
|
||||
const ctx = await bootHmr(alias, ['.'])
|
||||
const expected = pathToFileURL(filename).href
|
||||
const expected = pathToFileURL(join(realpathSync(target), 'module.ts')).href
|
||||
const cacheHas = vi.spyOn(ctx.loader.internal!.loadCache, 'has').mockReturnValue(false)
|
||||
const observed: string[] = []
|
||||
ctx.on('hmr/change', (url) => { observed.push(url) })
|
||||
try {
|
||||
@@ -43,6 +44,7 @@ describe('HMR exact config paths', () => {
|
||||
writeFileSync(filename, `export const generation = ${generation}\n`)
|
||||
await new Promise(resolve => setTimeout(resolve, 20))
|
||||
}
|
||||
expect(cacheHas).toHaveBeenCalledWith(expected)
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
rmSync(alias, { force: true })
|
||||
|
||||
@@ -338,7 +338,7 @@ describe('agent loop', () => {
|
||||
expect(adapter.requests[0]!.system).toBe('You are an AI agent powered by the DeepSeek Harness SDK.\n\nYou run on mock.')
|
||||
})
|
||||
|
||||
it('omits the system field when a system-prompt/assemble veto empties the assembly', async () => {
|
||||
it('omits the system field when system-prompt/assemble short-circuits with an empty assembly', async () => {
|
||||
// The documented escape valve: a deployment that must drop the harness
|
||||
// openers short-circuits the assemble waterfall; the request then carries
|
||||
// NO system field at all (not an empty string).
|
||||
|
||||
@@ -3306,7 +3306,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
/** The inherited `ctx` surface (cordis core + loader/hmr/timer), in curated order. */
|
||||
export const INHERITED_CTX_API: readonly InheritedApiEntry[] = [
|
||||
{ name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).' },
|
||||
{ name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).' },
|
||||
{ name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / short-circuit chain).' },
|
||||
{ name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.' },
|
||||
{ name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.' },
|
||||
{ name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.' },
|
||||
|
||||
@@ -154,7 +154,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
+ 'Write PLAIN JavaScript, not TypeScript (no `as`, no type annotations). '
|
||||
+ 'Cautions: (1) waterfall events (e.g. tools/pre-execute) hand the listener a '
|
||||
+ 'trailing `next` callback which MUST be called — returning without `next()` '
|
||||
+ 'VETOES the call; prefer plain notification events unless you intend to '
|
||||
+ 'SHORT-CIRCUITS the call; prefer plain notification events unless you intend to '
|
||||
+ 'intercept. (2) Never await something that only resolves after the current '
|
||||
+ 'turn (your code runs INSIDE a tool call of that turn — it would deadlock). '
|
||||
+ '(3) Your `ctx` is a restricted façade: you can register tools, observe '
|
||||
|
||||
@@ -224,6 +224,6 @@ export function describeEvents(events: readonly EventApiEntry[] = EVENT_API, nam
|
||||
entry.push(` ${event.signature}`)
|
||||
return entry
|
||||
})
|
||||
lines.push('waterfall listeners receive a trailing next() and MUST call it to delegate — returning without next() vetoes the chain.')
|
||||
lines.push('waterfall listeners receive a trailing next() and MUST call it to delegate — returning without next() short-circuits the chain.')
|
||||
return lines
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('cordis_inspect', () => {
|
||||
expect(report).toContain('- tools/change [emit]')
|
||||
expect(report).toContain('- tools/pre-execute [waterfall]')
|
||||
expect(report).toMatch(/'agent\/status'\(/)
|
||||
expect(report).toContain('returning without next() vetoes the chain')
|
||||
expect(report).toContain('returning without next() short-circuits the chain')
|
||||
expect(report).not.toContain('/**')
|
||||
expect(report).not.toContain('@mode waterfall')
|
||||
})
|
||||
@@ -171,7 +171,7 @@ describe('inspect renderers (direct)', () => {
|
||||
|
||||
it('describeEvents renders an empty catalog as just the waterfall caution', () => {
|
||||
expect(describeEvents([])).toEqual([
|
||||
'waterfall listeners receive a trailing next() and MUST call it to delegate — returning without next() vetoes the chain.',
|
||||
'waterfall listeners receive a trailing next() and MUST call it to delegate — returning without next() short-circuits the chain.',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,12 +20,15 @@ export const DSH_HOME_ENV = 'DSH_HOME'
|
||||
/**
|
||||
* Give a native filesystem watcher one canonical spelling of a path, even
|
||||
* when its final components do not exist yet. The deepest existing ancestor
|
||||
* is resolved through {@link realpath}; the missing suffix is then restored.
|
||||
* This prevents Windows short-name aliases from being mixed with long paths
|
||||
* emitted by the native watcher backend.
|
||||
* is resolved through {@link realpath}; when a suffix is missing, that
|
||||
* ancestor is also proved to be an enumerable directory before the suffix is
|
||||
* restored. This prevents Windows from treating a regular-file ancestor as
|
||||
* ordinary absence, and prevents short-name aliases from being mixed with
|
||||
* long paths emitted by the native watcher backend.
|
||||
* @param path - Watch target or root, resolved against the current directory.
|
||||
* @returns the target with its existing ancestor canonicalized.
|
||||
* @throws when ancestor traversal encounters an error other than absence.
|
||||
* @throws when ancestor traversal encounters an error other than absence, or
|
||||
* the existing ancestor of a missing suffix is not an enumerable directory.
|
||||
*/
|
||||
export async function canonicalizeWatchPath(path: string): Promise<string> {
|
||||
let current = resolve(path)
|
||||
@@ -34,6 +37,8 @@ export async function canonicalizeWatchPath(path: string): Promise<string> {
|
||||
try {
|
||||
const canonical = await realpath(current)
|
||||
if (missing.length > 0) {
|
||||
// A Windows file-as-parent probe reports ENOENT. Opening the resolved
|
||||
// ancestor preserves the cross-platform directory requirement.
|
||||
const directory = await opendir(canonical)
|
||||
await directory.close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user