DeepSeek Harness Plugin

omdsh-dev/dsh-tool-time

Stars ★ 4 Category Tools & Capabilities Added 2026-08-13

Strict ISO 8601 parsing, IANA timezone conversion, and UTC calendar arithmetic.

Install

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:omdsh-dev/dsh-tool-time

GitHub-sourced plugins run build scripts on your machine at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).

README

中文

DSH time tool plugin — strict ISO parsing, IANA timezone conversion, UTC calendar arithmetic, fixed-duration diffs. Zero dependencies, zero processes, pure functions.

License

Motivation

Handling time information is one of the most frequent Agent needs — "what time is it now", "what date is 3 days from now", "convert UTC to Beijing time". Problems with the current bash date approach: a process is spawned every time, cross-platform syntax is inconsistent (macOS and GNU date -d are completely different), and timezone conversion relies on hand-stitching TZ=.... Mentally computing timezone offsets and leap-year/DST boundaries is a high-error area for models.

Security Model

No eval, no new Function. Input only passes through strict validation:

  • Strict ISO 8601 subset: only accepts YYYY-MM-DD (UTC midnight), YYYY-MM-DDTHH:mm:ssZ, YYYY-MM-DDTHH:mm:ss±HH:MM (within ±14:00, optional .SSS milliseconds); datetimes without a timezone, RFC 2822, and natural-language dates are all rejected; calendar overflow such as 2026-02-30 is rejected by table-lookup validation (not relying on Date's silent normalization)
  • Timezone names: IANA names are validated via Intl.DateTimeFormat; invalid ones throw time: unknown timezone
  • Numbers: amount must be a safe integer; unit is an enumerated whitelist; all strings ≤ 200 characters
  • Fixed Intl environment: 'en-CA' + hourCycle: 'h23' (avoids midnight 24:00 and localized numerals)

Architecture

DSH Agent
    │ ctx.tools.register()
    ▼
src/index.ts(Cordis 插件入口 + action 分发 + 独立校验)
    │
    ▼
src/time.ts
    ├── parseStrictISO() — 严格 ISO 解析(正则 + 查表校验)
    ├── formatInTimezone() — Intl formatToParts 组装(时钟可注入)
    ├── addMonthsClamped() — 先置 1 日 → 目标年月 → 天数钳制
    └── diffBetween() — 固定时长(sign + absolute 分解)

Tool Declaration

ctx.tools.register(defineTool({
  name: 'time',
  parameters: {
    action: { type: 'string', required: true, enum: ['now', 'convert', 'add', 'diff'] },
    value:  { type: 'string', description: 'Strict ISO 8601 timestamp' },
    timezone: { type: 'string', description: 'IANA timezone (default UTC; display only)' },
    from:   { type: 'string', description: 'diff start' },
    to:     { type: 'string', description: 'diff end' },
    amount: { type: 'integer', description: 'Signed safe integer' },
    unit:   { type: 'string', enum: ['seconds','minutes','hours','days','weeks','months','years'] },
  },
  output: { schema: { type: 'json' }, render: (_a, v) => [{ type: 'text', text: JSON.stringify(v, null, 2) }] },
  execute: (args) => Promise.resolve(executeAction(args.action, args) as JsonValue),
  timeoutMs: 1000,
}))

Supported Operations

action Parameters Returns
now timezone? (default UTC) Structured representation of the current time
convert value + timezone (required) Converted structured time
add value + amount + unit (timezone? display only) Structured time after addition/subtraction
diff from + to Fixed-duration diff (no months/years)

Return structure (canonical value):

{
  "iso": "2026-08-05T06:00:00.000Z",
  "unix": 1785909600000,
  "timezone": "Asia/Shanghai",
  "timezoneSource": "explicit",
  "local": "2026-08-05T14:00:00+08:00",
  "formatted": "2026-08-05 14:00:00 (Asia/Shanghai)"
}

Semantic contract:

  • add always operates in UTC (calendar-day semantics; keeps the "calendar day" intuition across DST); timezone only determines the local/formatted display
  • Month/year clamping: 2026-01-31 + 1 month = 2026-02-28; 2028-02-29 + 1 year = 2029-02-28 (set to day 1 → locate the target year/month → clamp to the target month's max days)
  • diff is fixed-duration only: sign + signed total + absolute remainder decomposition (months/years cannot be uniquely derived from milliseconds, so they are not provided). Field semantics (AUDIT-TIME-03):
    • milliseconds is the exact signed total duration
    • seconds/minutes/hours/days/weeks are sign * floor(abs / unit) (truncated on the absolute value, then signed)
    • absolute is a non-negative remainder decomposition of abs (e.g. -1500ms → milliseconds: -1500, seconds: -1, absolute: {seconds: 1, milliseconds: 500})
    • A diff exceeding the safe integer range throws time: diff duration out of range
  • Default UTC: when timezone is omitted, the result is independent of the runtime environment (timezoneSource: "default-utc")

npm 0.1.0-rc.6 Compatibility (Verified)

This plugin has been migrated to the npm 0.1.0-rc.6 dependency line and fully verified end-to-end in an isolated consumer of @deepseek-ai/dsh@0.1.0-rc.6 (npm private package):

  • Types/runtime: peers are @deepseek-ai/cordis: ^4.0.1 + @deepseek-ai/dsh-tools: >=0.0.1-rc.1 <0.2.0 + @deepseek-ai/dsh-invariants: >=0.0.1-rc.1 <0.2.0; no longer depends on the unscoped cordis
  • Standalone build: npm install (devDependencies are self-contained: typescript/vitest/@types/node) → npm run typechecknpm testnpm run buildnpm pack
  • Consumption verification: tarball installed into a DSH 0.1.0-rc.6 (npm) consumer → dsh --profile compat --dump-config shows this plugin's row → tool registration and execution actually pass
  • Launch method: npx -p @deepseek-ai/dsh@0.1.0-rc.6 dsh web (lib production mode; do not install -g globally)

Version Adaptation

  • Adapted DSH: DSH 0.1.0-rc.6 (npm) (profile/bundle plugin system)
  • bundle declaration: dsh.bundle in package.json (patch points to cordis.patch.yml) + exports exports
  • patch format: cordis.patch.yml uses a - insert: list (the patch is id-targeted; bare - id: entries report entry not found)
  • files: the published tarball includes lib/, src/, cordis.patch.yml

Installation

Profile Bundle (Recommended)

Install this plugin into a profile as a standalone bundle (DSH 0.1.0-rc.6 (npm)). This repository lives under the omdsh-dev organization and is publicly accessible:

# Interactive (web) profile —— install from the GitHub repository
dsh plugin --profile web add github:omdsh-dev/dsh-tool-time
# One-off task (headless) profile —— dsh run uses headless by default
dsh plugin --profile headless add github:omdsh-dev/dsh-tool-time

Or install from the tarball produced by npm pack:

npm pack     # produces dsh-tool-time-<version>.tgz
# Interactive (web) profile
dsh plugin --profile web add ./dsh-tool-time-<version>.tgz
# One-off task (headless) profile
dsh plugin --profile headless add ./dsh-tool-time-<version>.tgz

The bundled dsh.bundle.patch (pointing to cordis.patch.yml) automatically adds the plugin to the profile's layer stack after installation; the plugin's cordis.patch.yml inserts the tool-time entry via - insert:. The plugin's missing peer dependencies (@deepseek-ai/cordis, @deepseek-ai/dsh-tools) are provided by the profile's healed profiles/node_modules fallback installation.

⚠️ web and headless are different profiles: installing into web does not automatically cover headless; dsh run uses the headless profile by default. Use forward slashes for Windows paths (C:/...).

Verify Installation

dsh --profile web --dump-config | grep tool-time

Runtime Verification

dsh run "使用 time 工具获取当前 UTC 时间"

Manual Installation and Legacy Compatibility (legacy monorepo scenario)

The monorepo way is only for legacy scenarios: old snapshots that do not support Profile Bundle, or plugin development/debugging environments:

  1. Place into the monorepo: cp -r time ~/.dsh/source/master/packages/tools/time (development debugging)
  2. Add "@deepseek-ai/dsh-tool-time": "workspace:^" to apps/cli/package.json; add { "path": "./packages/tools/time" } to the tsconfig.host.json references
  3. pnpm install && pnpm run build
  4. Insert the plugin in the profile's user-layer patch (~/.dsh/profiles/<name>/cordis.patch.yml):
- insert:
    - id: tool-time
      name: '@deepseek-ai/dsh-tool-time'
  1. Verify: dsh --profile <name> --dump-config | grep tool-time

Note: the patch is id-targeted (DSH 0.1.0-rc.6 (npm)) — a bare - id: entry reports entry "xxx" not found; you must wrap it in a - insert: list.

Known Limitations

  1. Distribution chain: peer dependencies (e.g. @deepseek-ai/dsh-tools) are npm private packages; standalone installation does not depend on the monorepo workspace (monorepo is legacy only)
  2. add is UTC calendar arithmetic, not local wall-clock semantics (local wall-clock requires v2)
  3. Natural-language dates ("next week") and calendar month/year diffs are not supported (v2)
  4. Timezone data comes from Node's built-in ICU: "cross-platform consistency" means "consistency under the same Node/ICU data"
  5. Input year range is 1000–9999 (00000999 rejected: JavaScript Date.UTC has a historical 1900+ mapping behavior for those years)
  6. Precision contract: iso/unix preserve milliseconds; local/formatted are second-precision display fields
  7. Offset precision down to minutes: v1 only supports modern timezones; second-level offsets of historical timezones (<1900) are truncated

Tests

pnpm test

63 cases covering functionality/errors/attack payloads (fake clock, known timezone vectors, end-of-month clamping, strict ISO boundaries, year ranges, offsets crossing year boundaries, extreme amounts, prototype-chain timezone names, etc.). See the locally maintained design doc for the full list.

License

MIT

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →