* feat(release): publish package source, declaration maps, and documentation Every release member publishes its TypeScript source and the declaration maps that resolve into it, and @deepseek-ai/dsh publishes the repository Markdown documentation staged from docs/ at prepack time. The ./src/* export most members already declared now resolves for an installed consumer, and a consumer editor follows a declaration to the implementation. Bundled JavaScript maps stay unpublished; the vendored family and the Desktop runtime file policy are unchanged. Rationale: .agents/notes/implemented/process/2026-10-08-published-source-and-docs.md * feat(release): publish JavaScript maps and compress bundles Every emitted JavaScript map ships beside the bundle it describes, and the workspace build and the client preset compress bundles without mangling identifiers. The documentation payload now mirrors the website publication manifest and is removed again by `postpack`; Desktop and the Python executable exclude the published source, documentation, and maps; `clean` removes a staged copy. Typert's publication contract gains the Remote declaration map. * fix(release): keep node bundles readable for the worker image The browser worker image lowers `yield*` only as a whole statement and rewrites `for await` loops by their source layout, so compressed node-side entries either fail that lowering or lower to unparseable code. Only the browser bundles keep compression, which removes about a third of their text. The bundle-purity assertion now matches the module-loader call regardless of the quote style compression picks, and the release-pack fixture exercises the installed package's new documentation requirement. * feat(release): publish the whole documentation tree `@deepseek-ai/dsh` copies `docs/` in full instead of the website's page set: the maintainer documents, the generated migration records, the translation guidelines, and the `*.i18n.yaml` consistency records ship with the installed package, so the documentation's own links resolve without a checkout. * feat(release): publish the upgrade guides with the documentation The staged documentation keeps the website's page set and adds every `docs/upgrade-guide/` document, which is what an installed reader consults when a released change breaks something. The rest of the maintainer tree stays out. * test(release): expect source and maps in master's new payload cases * feat(release): keep installed docs and source readable in Desktop Desktop keeps the published src/ and docs/ trees inside app.asar, and every model shell call receives DSH_INSTALL_DIR and DSH_NODE_EXECUTABLE so an agent can read them through Electron's Node mode. Staging also generates docs/upgrade-guide/README.md, an index of the upgrade guides grouped by the release they upgrade from. Minified client bundles print string literals with backticks: the worker image packer now recognizes those chunk requests, client license banners are prepended after minification so their text survives, and the affected artifact tests accept either quote. * test(desktop): read archived docs through the Windows PowerShell form Regenerate the config catalog for the shifted shell-env source line. * test(desktop): wait for the GUI-subsystem Electron process from PowerShell * build(release): publish source and maps from the message-quotes package * build(release): publish source and maps from packages new on master * build(release): publish source and maps from the tool-office package
description, kind
| description | kind |
|---|---|
| Strict instant and zone parsing, millisecond spans, and bounded Cron/RRULE recurrence for callers that own time-based behavior. | package-library |
dsh-util-time
English | 中文
Summary
Shared time vocabulary for instant, zone, elapsed-time, and recurrence boundaries. Strict parsers return accepted values or refusal reasons; resolveLocalInstant applies maintained Temporal gap and overlap behavior. Duration stores a millisecond span. Cron and RRule parse distinct expression types and compute bounded occurrences from explicit decision times. The library owns no task state, delivery policy, domain errors, or model-facing text.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
When to use it
It is a library, not a service or plugin: no ctx, registers nothing, holds no state.
Call it at the boundary that receives the zone or the wall-clock selector, before the value reaches anything durable. dsh-api-session-controller and dsh-subagent admit a caller-reported zone through canonicalClientTimeZone; dsh-time-context admits the zone a request turn reports through canonicalizeTimeZone and refuses an alias rather than rewriting it; dsh-schedule reads absolute and wall-clock selectors through the strict parsers, resolves local targets through resolveLocalInstant, and uses the shared Cron / RRule recurrence values; dsh-session-format-v0-to-v1 validates a stored schedule instant through fourDigitYearEpoch while keeping its own released-log error. dsh-time-context reads its elapsed reading through Duration and keeps the wording of the line it injects. Schedule retains its record types, relative intervals, daily/weekly policy, and delivery limits. The library works in Host and browser code without a Cordis service.
Entry point
import { Duration, canonicalClientTimeZone, fourDigitYearEpoch, parseLocalCalendar, resolveLocalInstant } from '@deepseek-ai/dsh-util-time'
const zone = canonicalClientTimeZone('US/Eastern') // 'America/New_York' or undefined
const stored = fourDigitYearEpoch('2026-03-08T02:30:00.000Z') // { kind: 'accepted', epoch } or a rejection reason
const local = parseLocalCalendar('2026-03-08', '02:30:00') // { kind: 'accepted', time } or a rejection reason
if (local.kind === 'accepted') {
resolveLocalInstant(local.time, zone ?? 'UTC') // epoch milliseconds, or undefined inside a DST gap
}
Duration.fromMilliseconds(1_500).decompose() // { days: 0, hours: 0, minutes: 0, seconds: 1, milliseconds: 500 }
canonicalClientTimeZone answers undefined for a name it refuses, and the caller raises its own refusal — session/invalid-time-zone for the Session prompt, subagent/invalid-time-zone for a subagent continuation. canonicalizeTimeZone, the strict parsers, and fourDigitYearEpoch instead report why they refused, as a discriminated result whose rejected arm carries a stable reason, so each caller chooses its own code and diagnostic. formatFourDigitYearInstant and resolveLocalInstant take already-validated values and answer a string, or epoch milliseconds, with undefined for an unrepresentable epoch or a wall-clock value inside a DST gap. Duration is a frozen value type over one millisecond count: it validates that the count is finite and inside the exactly representable range, and splits a non-negative span into whole parts. It publishes only the operations its consumers call; a member for another unit or for span arithmetic lands with the caller that needs it.
Understand the implementation
Implementation internals — click to expand
src/instant.ts owns strict time boundaries, src/duration.ts owns spans, and src/cron.ts / src/rrule.ts own recurrence. Their public barrel is src/index.ts; parsers return discriminated results, leaving error codes and presentation to callers.
The strict parsers match a fixed regular expression before constructing any date, so a malformed spelling, an out-of-range clock field, and a date the calendar does not have answer distinct reasons. Local resolution defers to @js-temporal/polyfill: disambiguation: 'earlier' picks the first instant of an overlap, and the local-field round trip answers undefined for a value inside a forward transition's gap.
Both recurrence values validate committed scheduledAt values with fourDigitYearEpoch: each must name a real date in the canonical UTC profile with three fractional digits. Cron constructs calendar values only for candidate minutes it visits; its day matching, gap and overlap policy, and search bounds remain shared with every caller.
| Entry | Accepted input |
|---|---|
canonicalClientTimeZone |
UTC or an IANA Area/Location name; answers undefined when refused. |
canonicalizeTimeZone |
The same names; answers the canonical spelling or a refusal reason, and carries the platform error when the platform refused a well-formed name. |
parseLocalCalendar |
YYYY-MM-DD and HH:mm:ss, with optional one-to-three fractional digits. |
parseOffsetInstant |
RFC 3339 with an explicit Z or numeric offset; the offset is part of the input. |
fourDigitYearEpoch |
The canonical stored profile: exactly three fractional digits and a Z. |
formatFourDigitYearInstant |
An integral epoch inside the four-digit-year range; answers undefined outside it or when non-integral. |
isFourDigitYearInstant |
The same range, as a predicate, so a caller that only needs the bound does not re-derive it. |
resolveLocalInstant |
Exact calendar fields of a real date, or an already-built Temporal.PlainDateTime, plus an already-canonical zone. |
Duration |
One finite, exactly representable millisecond count and its decomposition. |
Cron.parse(expression, zone) |
Five-field Vixie Cron with an explicit IANA zone and star-sensitive day matching; bounded to a Gregorian 400-year search. |
RRule.parse(expression, options?) |
One DTSTART and RRULE, bounded RFC 5545 profile, minute floor, static expansion limits, and optional stricter authoring checks. |
Further Exploration
- Schedule Host storage — the consumer that stores committed UTC targets.
- Session controller — the request boundary that canonicalizes a browser zone.
- Time context — the request consumer that refuses a non-canonical browser zone.
- util/ package map — where this library sits among the shared utilities.
Model Experience
Indirectly, through the consumers that store a canonical zone or a committed UTC instant on durable records: dsh-time-context renders the turn's model-visible zone instruction and timestamp from the zone, and dsh-schedule renders its model-visible scheduler text from the committed instant.
KV Cache effect
None of its own. The consumer that injects a zone- or schedule-derived line into a request owns that request's cache behavior.
Known Limitations and Deferred Work
- Alias resolution follows the runtime's ICU data — which name an alias group canonicalizes to is the platform's answer, so two processes on different Node builds can disagree about it.
- Recurrence is bounded — the Schedule reference lists shared expression profiles. A successful parse returns a frozen
rulewithexpression,timeZone,next(after), andresolve(scheduledAt, acceptedAt); resolution preserves a committed occurrence and coalesces missed ones. Refusals carrysyntax,time_zone,density, orrange; invalid decision times throwRangeError. No method reads the wall clock or starts a timer. Durationhas no calendar units — a day is exactly 24 hours and a month has no fixed length, so a caller that needs local calendar days derives them from the zone.Durationpublishes only what a consumer calls —fromMillisecondsanddecompose(); a conversion, an arithmetic operation, or azeroidentity lands with the caller that needs it, so the published contract grows with a caller rather than ahead of one.- Seconds-unit spans still multiply —
dsh-schedule's interval and delay fields and the DeepSeek file store's refresh margin are second-valued spans converted with* 1_000; none has moved onto this library, and an epoch expressed in seconds is not a span and must not be built into one. - The root entry loads recurrence dependencies — importing it resolves both
@js-temporal/polyfillandrrule, including for a consumer that only needs a zone or span. Browser consumers must account for those dependencies in their bundle.
Dev Note
Working context for maintainers — click to expand
None.