collectPackageNames repeated collectPackageAliases' directory walk
verbatim, which jscpd reported as a 7-line clone and which failed the
lint-and-duplication gate on both the Linux and Windows lanes. Both now
read one workspacePackages() iterator: the alias collector keeps only
packages named after their directory, and the coverage collector keeps
every one of them.
Review found the CLI entry guard compared import.meta.url against a
concatenated file:// URL. That fails whenever import.meta.url encodes
something process.argv[1] does not — a repository path containing a
space, or any Windows drive path — and it fails silently: the script
exits 0 having done nothing, so verify-tsconfig-paths would pass
without checking exactly where it is needed. Running a copy from a
directory whose name contains a space reproduces it. The guard now uses
the comparison the repository's seven other generators already use.
Deleting the group wildcards also removed the fallback that resolved a
package nobody had aliased, so the generator now asserts coverage:
every workspace package with a src directory must be mapped, and
--check names any that is not. That assertion immediately found four
packages named dsh-<group>-<directory> whose siblings carry hand-written
aliases while they did not, so they reached built lib/ output through
the workspace symlink. They now carry aliases too, which takes the
resolution differences in this branch from seven to eleven.
Two comments in tsconfig.base.json still pointed at the deleted
wildcards; they now state why those aliases stay hand-written. The
package-inventory proposal recorded the wildcard collapse as current,
so both notes are cross-linked as partial supersession.
tsconfig.base.json resolved @deepseek-ai/dsh-* through 49 candidate
globs and @deepseek-ai/dsh-*/invariant through 45, one per package
group. Resolution tries candidates in order, so a package late in the
list paid for every earlier miss — and under the dsh source launch each
miss is an ERR_MODULE_NOT_FOUND that Node decorates with a full
CommonJS resolution walk. A boot profile attributed 934.6 ms, 35% of
startup, to that decoration path across 60,942 failed resolutions. The
cost landed hardest on packages/util/*, which sits at position 44 of 49
and holds the leaf utilities nearly every plugin imports.
gen-tsconfig-paths writes one explicit alias per package into a marked
region and both group wildcards are gone; verify-tsconfig-paths reports
drift and runs in the ci-static lane. Booting the headless profile from
source drops from ~2,157 ms to ~1,055 ms with --help output unchanged.
All 1,022 dsh specifiers in repository sources resolve to the same
target as before, except seven /invariant specifiers in the lsp,
terminal, and runtime-diagnostics groups that the deleted wildcard
never listed: those reached built lib/types instead of src, against the
rule that static gates resolve through paths to src on a clean tree.
Stacked on #3115: keep its windows-* setup-pnpm-js-<run_id>-<run_attempt>-<job>
destination, and extend the same isolation to non-Windows jobs in ci.yml
and ci-master.yml with setup-pnpm-<run_id>-<run_attempt>. This prevents
sequential self-hosted Windows jobs from tripping over a stale locked
pnpm.exe/reflink native module.
* ci(build-exe): drop pull_request label trigger to avoid skipped checks
* docs(build-exe): sync agent note and pin event set in workflow spec
* test(ci): type-safe event key assertion for build-exe workflow
* ci(build-exe): use present-tense trigger comment and drop label-run note
The per-case 15-30s budgets on the Windows native and coverage lanes fire
before oxlint, workflow-worker-thread, and other subprocess-spawning cases
finish under the loaded self-hosted pool; the failures rotate across cases
as load shifts, so per-case widening only moved the flake. Raise the lane
defaults (DSH_COVERAGE_TEST_TIMEOUT_MS and the native --testTimeout) to 90s,
align the oxlint and workflow-worker-thread case budgets, and keep the
built-bin SPAWN_TIMEOUT_MS at 60s under a 90s outer budget.
Both suites spawn real subprocesses (oxlint probes; the dsh built bin) that
cold-start slowly on the contended self-hosted Windows pool, so their 20-25s
timeouts fire before the child finishes. Raise the oxlint contract case
timeouts to 60s and the built-bin execa timeouts to 60s, matching the
tool-ralph budget treatment.
The windows-* jobs keep a separate standalone pnpm executable under
runner.temp/setup-pnpm-js. A previous job on the same self-hosted runner
can leave a locked @reflink native module there, so the next job's
pnpm/action-setup fails with EPERM during unlink before any test runs.
Suffix the destination with run_id, run_attempt, and job so every job
gets a fresh directory even when sequential jobs land on the same
runner; apply the same to the python SDK exe build. Update the pnpm
setup isolation note to record the Windows-specific destination.
The executor seam collapses run()/start() (and the planned begin()) into one
resolve() + execute(): the handle IS the live process, and the foreground
result, the background cursor, and the deadline's promotion offer are
projections over the same spawn. ShellExecSpec.onExpiry ('kill' default /
'offer' / 'none') replaces the implicit method split; one stdout budget applies
everywhere, the 'start ignores timeoutMs' wart dies, and sync spawn throws are
contained into the same settled-killed handle as async rejections (result()
rejects with the original error, identity preserved for the sandbox
classification).
The promotion offer settles exactly once and never rejects — offer at an
'offer' deadline while running, undefined when the process settles first — so
'await execution.promotion' alone distinguishes the outcomes. An offer must be
answered synchronously; an unanswered one auto-declines back to the
kill-on-timeout behavior. accept() ends the deadline and detaches the caller's
abort signal; decline() kills and classifies timedOut.
tool-bash and tool-pwsh promote on timeout by default (promoteOnTimeout: true,
inert without run_in_background or a job registry): at the offer the running
handle registers as a job with the same hooks as run_in_background, mirrors
into ctx.activities (the web task list streams it, stop control included), and
the call returns { kind: 'promoted', jobId, timeoutMs, output } carrying the
output so far — the job cursor continues exactly after it. Any promotion
failure declines and falls back to the plain timeout kill. Descriptions and
the timeoutMs schema text advertise the semantics only when they hold;
recorded-session and web snapshot fixtures re-recorded with them.
Also repairs the four CI failures from the human-kill push: ui-activity's
missing dsh-session peer declaration (static + windows observational), the
observe owner-arm/outer-catch and killJob-stub coverage gaps, tool-workflow's
owned-run arm, and the timing-dependent projection-cache interval flush test
(now poll-based for coarse Windows timers).
Peer evidence and rejected alternatives (Codex's session model, a third seam
method, a second threshold knob) in the Agent Note
2026-08-26-shell-execute-timeout-promotion.
The web task list gains a two-press stop control on running job rows, built on
one seam change: JobRegistry.kill now takes { reason?, reported? } instead of a
positional reason. reported defaults to true (the model's job_kill claims the
terminal report exactly as before); the web path passes reported: false because
a human kill has no model-visible channel, so the settlement flows through the
existing tool-jobs completion notice (wakeup/inject, budgets, truncation all
unchanged). A recorded kill reason merges into a killed settlement's detail
(producer facts first: 'signal: SIGTERM; cancelled by the user'), so the notice
and the web row both say who stopped the work.
- session-controller: session.killJob Remote beside cancel, live-only Agent
lookup, job-not-found/jobs-unavailable rejections joined to the closed RPC
error table; ClientSession.killJob and the ISession face carry it to the UI.
- ui-activity: arm/confirm stop button (3s disarm, pending disable, failure
hint), rendered only on running job rows; standalone activities keep no kill
handle and the README limitation now says so.
- The background-job-list web e2e now drives the whole human path through the
button; goldens show the merged killed detail end to end.
- Agent Note 2026-08-26-human-job-kill records the decision against Kimi's
suppress-own-notification precedent and Claude Code's silent-kill failure;
the 2026-08-08 blocker note and the unified-task-list note point at it.