dsh-date-wrapper 0.1.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INSTALL.md ADDED
@@ -0,0 +1,115 @@
1
+ # Installation guide (official DSH CLI)
2
+
3
+ - [English README](./README.md)
4
+ - [中文 README](./README.zh.md)
5
+ - [日本語 README](./README.ja.md)
6
+ - [한국어 README](./README.ko.md)
7
+ - [Installation guide](./INSTALL.md)
8
+ - [中文安装指南](./INSTALL.zh.md)
9
+ - [日本語インストールガイド](./INSTALL.ja.md)
10
+ - [한국어 설치 안내](./INSTALL.ko.md)
11
+ - [Changelog](./CHANGELOG.md)
12
+ - [日本語 changelog](./CHANGELOG.ja.md)
13
+ - [한국어 changelog](./CHANGELOG.ko.md)
14
+
15
+ ## 0. Prerequisites
16
+
17
+ ```powershell
18
+ echo $env:DSH_HOME # usually C:\Users\<you>\.dsh
19
+ dsh --version # this guide was verified on 0.1.1-rc.2
20
+ pnpm --version # `dsh plugin` is a pnpm forwarder, so pnpm must be on PATH
21
+ ```
22
+
23
+ ## 1. Install
24
+
25
+ From GitHub (recommended — pnpm copies the package into `node_modules`, and the lockfile pins the exact commit):
26
+
27
+ ```powershell
28
+ dsh plugin --profile web add github:drscrewdriver/dsh-date-wrapper
29
+ ```
30
+
31
+ Or from a local checkout (development):
32
+
33
+ ```powershell
34
+ dsh plugin --profile web add E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
35
+ ```
36
+
37
+ Or link mode (source edits take effect after a restart, no reinstall):
38
+
39
+ ```powershell
40
+ dsh plugin --profile web add link:E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
41
+ ```
42
+
43
+ > ⚠️ A local `file:` / `link:` install makes the profile depend on that path. Renaming or deleting the directory then breaks **every** pnpm operation in the profile with `ENOENT` until the stale dependency is removed — exactly what happened when this package was renamed from `dsh-time-wrapper`.
44
+ > ⚠️ A relative path is only anchored to **your current directory** when it starts with `.` or `..`;
45
+ > `mine-dsh-plugins\dsh-date-wrapper` is resolved inside the profile directory and will not be found. Absolute paths are safest.
46
+
47
+ Signs of a successful install:
48
+
49
+ 1. pnpm exits with code 0;
50
+ 2. `C:\Users\<you>\.dsh\profiles\web\package.json` lists `dsh-date-wrapper` under `dependencies`;
51
+ 3. the same file's `dsh.profile.bundles` gains `dsh-date-wrapper` at the end (the package declares `dsh.bundle.patch`, so it is pulled into the layer list automatically).
52
+
53
+ ## 2. Restart
54
+
55
+ ```powershell
56
+ # stop the running dsh web process, then start it again
57
+ dsh web
58
+ ```
59
+
60
+ Then refresh the browser page.
61
+
62
+ > Bundle patches are **not hot-reloaded**: only the profile / home patch layers are watched. Changing the
63
+ > plugin's own `cordis.patch.yml` or upgrading the plugin always requires a restart.
64
+
65
+ ## 3. Verify
66
+
67
+ Open a new session and send any message. The date hangs on the **runtime-context snapshot** (shown in the session as an injected context row sourced from `system-prompt`), as:
68
+
69
+ ```
70
+ Current date: 2026-09-08 Asia/Shanghai Tuesday
71
+ ```
72
+
73
+ That is `Current date: ` + `<ISO date> <IANA zone> <English weekday>`, 46 characters (~12 tokens), with **no hour, minute or second**.
74
+
75
+ You can also exercise the plugin itself from the command line:
76
+
77
+ ```powershell
78
+ cd E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
79
+ node tests/format.test.mjs
80
+ node tests/context.test.mjs
81
+ ```
82
+
83
+ ## 4. Troubleshooting
84
+
85
+ | Symptom | Cause and fix |
86
+ |---------|---------------|
87
+ | Startup fails with `date-wrapper: 非法 IANA timeZone` | `timeZone` in `cordis.patch.yml` is wrong. This is **intentional fail-fast**: better to fail at startup than to silently emit a wrong date in UTC |
88
+ | Startup fails with `duplicate loader entry id: date-wrapper` | The assembly tree already has a row with that id; delete the duplicate |
89
+ | No date after installing | ① confirm `dsh-date-wrapper` is in `dsh.profile.bundles`; ② confirm you restarted and refreshed the page; ③ **confirm the current preset is not a fixed-prompt one** (next row) |
90
+ | No date under some presets | That preset's persona sets `includeRuntimeContext: false` (the official `minimal` and the local `simple-reply` both do). Such presets explicitly forbid later listeners from adding prompt content, so this plugin's runtime context is dropped — expected behaviour |
91
+ | The date is off by one day | `timeZone` does not match your actual zone; across a zone boundary (e.g. 00:30 Beijing = 16:30 UTC the previous day) that shows up as a one-day difference |
92
+ | You also see `Time sampled …` | Some preset mounts `@deepseek-ai/dsh-time-context` explicitly. This plugin neither loads nor filters it; the two should not be used together |
93
+ | Any pnpm operation in the profile fails with `ENOENT: no such file or directory, open '…'` | A `file:` / `link:` dependency points at a path that no longer exists (the package was renamed, or its tarball was deleted). Remove the stale dependency with `dsh plugin --profile web remove <name>` and install again; `github:` installs do not have this failure mode |
94
+
95
+ ## 5. On/off (no panel toggle — activation is the switch)
96
+
97
+ Disable or enable it in your own profile patch layer — `C:\Users\<you>\.dsh\profiles\web\cordis.patch.yml`:
98
+
99
+ ```yaml
100
+ - id: date-wrapper
101
+ disabled: true # disabled; set back to false to restore
102
+ ```
103
+
104
+ - **Hot, no restart**: the file is watched by Cordis HMR; `disabled: true` disposes that row's fiber and injection stops immediately.
105
+ - DSH's built-in **Settings → Plugins** page shows `enabled / disabled` (read-only).
106
+ - The file must be a top-level YAML array; malforming it makes **startup fail** (fail-loud).
107
+ - Full removal goes through `dsh plugin remove` (next section) and **requires a restart**.
108
+
109
+ ## 6. Uninstall
110
+
111
+ ```powershell
112
+ dsh plugin --profile web remove dsh-date-wrapper
113
+ ```
114
+
115
+ Restart dsh web.
package/INSTALL.zh.md ADDED
@@ -0,0 +1,115 @@
1
+ # 安装指南(官方 DSH CLI)
2
+
3
+ - [English README](./README.md)
4
+ - [中文 README](./README.zh.md)
5
+ - [日本語 README](./README.ja.md)
6
+ - [한국어 README](./README.ko.md)
7
+ - [Installation guide](./INSTALL.md)
8
+ - [中文安装指南](./INSTALL.zh.md)
9
+ - [日本語インストールガイド](./INSTALL.ja.md)
10
+ - [한국어 설치 안내](./INSTALL.ko.md)
11
+ - [Changelog](./CHANGELOG.md)
12
+ - [日本語 changelog](./CHANGELOG.ja.md)
13
+ - [한국어 changelog](./CHANGELOG.ko.md)
14
+
15
+ ## 0. 前置条件
16
+
17
+ ```powershell
18
+ echo $env:DSH_HOME # 通常是 C:\Users\<你>\.dsh
19
+ dsh --version # 本指南验证于 0.1.1-rc.2
20
+ pnpm --version # dsh plugin 是 pnpm 转发器,pnpm 必须在 PATH 上
21
+ ```
22
+
23
+ ## 1. 安装
24
+
25
+ 从 GitHub 安装(推荐:pnpm 把包拷进 `node_modules`,lockfile 钉住具体 commit):
26
+
27
+ ```powershell
28
+ dsh plugin --profile web add github:drscrewdriver/dsh-date-wrapper
29
+ ```
30
+
31
+ 或从本地目录安装(开发期):
32
+
33
+ ```powershell
34
+ dsh plugin --profile web add E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
35
+ ```
36
+
37
+ 或软链模式(改源码后重启即生效,无需重装):
38
+
39
+ ```powershell
40
+ dsh plugin --profile web add link:E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
41
+ ```
42
+
43
+ > ⚠️ 本地 `file:` / `link:` 安装会让 profile 依赖那个路径。一旦目录被改名或删除,profile 里**任何** pnpm 操作都会 `ENOENT`,直到把这条失效依赖移除 —— 本包从 `dsh-time-wrapper` 改名时就是这样炸的。
44
+ > ⚠️ 相对路径只有以 `.` 或 `..` 开头才会被锚定到**你当前所在目录**;
45
+ > 写成 `mine-dsh-plugins\dsh-date-wrapper` 会在 profile 目录里找不到。用绝对路径最稳。
46
+
47
+ 安装成功的判据:
48
+
49
+ 1. pnpm 退出码为 0;
50
+ 2. `C:\Users\<你>\.dsh\profiles\web\package.json` 的 `dependencies` 里出现 `dsh-date-wrapper`;
51
+ 3. 同一文件的 `dsh.profile.bundles` 末尾出现 `dsh-date-wrapper`(包声明了 `dsh.bundle.patch`,会被自动纳入 layer 列表)。
52
+
53
+ ## 2. 重启
54
+
55
+ ```powershell
56
+ # 停掉当前 dsh web 进程后重新启动
57
+ dsh web
58
+ ```
59
+
60
+ 然后刷新浏览器页面。
61
+
62
+ > bundle patch **不热重载**:只改 profile / home 层的 patch 才会被监听。改插件自己的
63
+ > `cordis.patch.yml` 或换版本,都必须重启。
64
+
65
+ ## 3. 验证
66
+
67
+ 新开一个会话,随便发一句话。日期会挂在**运行上下文快照**里(会话中显示为一条注入上下文行,来源是 `system-prompt`),文本是:
68
+
69
+ ```
70
+ Current date: 2026-09-08 Asia/Shanghai Tuesday
71
+ ```
72
+
73
+ 即 `Current date: ` + `<ISO 日期> <IANA 时区> <英文星期>`,46 字符(约 12 token),**不含时分秒**。
74
+
75
+ 命令行侧可自测插件本身:
76
+
77
+ ```powershell
78
+ cd E:\test\rewrite-agently\mine-dsh-plugins\dsh-date-wrapper
79
+ node tests/format.test.mjs
80
+ node tests/context.test.mjs
81
+ ```
82
+
83
+ ## 4. 排错
84
+
85
+ | 现象 | 原因与处理 |
86
+ |------|-----------|
87
+ | 启动失败,报 `date-wrapper: 非法 IANA timeZone` | `cordis.patch.yml` 里的 `timeZone` 写错了。这是**有意的 fail-fast**:宁可启动失败,也不静默按 UTC 出错误的日期 |
88
+ | 启动失败,报 `duplicate loader entry id: date-wrapper` | 装配树里已有同名行,删掉重复的一行 |
89
+ | 装完没有日期 | ① 确认 `dsh.profile.bundles` 里有 `dsh-date-wrapper`;② 确认已重启 + 刷新页面;③ **确认当前 preset 不是 fixed-prompt 类型**(见下一行) |
90
+ | 某些 preset 下没有日期 | 该 preset 的 persona 设了 `includeRuntimeContext: false`(官方 `minimal`、本地 `simple-reply` 都是)。这类 preset 明确禁止后续 listener 往提示词加内容,本插件的运行上下文会被丢掉 —— 属预期行为 |
91
+ | 日期差一天 | `timeZone` 与你的实际时区不一致;跨时区边界(如北京 00:30 = UTC 前一日 16:30)会表现为差一天 |
92
+ | 同时看到 `Time sampled …` | 某个 preset 里显式挂载了 `@deepseek-ai/dsh-time-context`。本插件不加载也不过滤它,两者不该同时使用 |
93
+ | profile 里任何 pnpm 操作都报 `ENOENT: no such file or directory, open '…'` | 有一条 `file:` / `link:` 依赖指向已不存在的路径(包被改名,或它的 tarball 被删)。先 `dsh plugin --profile web remove <名字>` 移除失效依赖,再重新安装;`github:` 安装没有这个失败模式 |
94
+
95
+ ## 5. 开关(不装面板开关,靠插件激活)
96
+
97
+ 停用/启用直接改你自己的 profile patch 层 —— `C:\Users\<你>\.dsh\profiles\web\cordis.patch.yml`:
98
+
99
+ ```yaml
100
+ - id: date-wrapper
101
+ disabled: true # 停用;改回 false 即恢复
102
+ ```
103
+
104
+ - **热生效,无需重启**:该文件被 Cordis HMR 监听,`disabled: true` 会 dispose 该行的 fiber,注入立即停止。
105
+ - DSH 自带的 **设置 → 插件** 页面会显示 `已启用 / 已停用`(只读)。
106
+ - 该文件必须是顶层 YAML 数组;写坏会导致**启动失败**(fail-loud)。
107
+ - 彻底移除走 `dsh plugin remove`(见下节),**需要重启**。
108
+
109
+ ## 6. 卸载
110
+
111
+ ```powershell
112
+ dsh plugin --profile web remove dsh-date-wrapper
113
+ ```
114
+
115
+ 重启 dsh web。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-date-wrapper contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ja.md ADDED
@@ -0,0 +1,212 @@
1
+ # dsh-date-wrapper
2
+
3
+ - [English README](./README.md)
4
+ - [中文 README](./README.zh.md)
5
+ - [日本語 README](./README.ja.md)
6
+ - [한국어 README](./README.ko.md)
7
+ - [Installation guide](./INSTALL.md)
8
+ - [中文安装指南](./INSTALL.zh.md)
9
+ - [日本語インストールガイド](./INSTALL.ja.md)
10
+ - [한국어 설치 안내](./INSTALL.ko.md)
11
+ - [Changelog](./CHANGELOG.md)
12
+ - [日本語 changelog](./CHANGELOG.ja.md)
13
+ - [한국어 changelog](./CHANGELOG.ko.md)
14
+
15
+ > 最小限の日付行です。DSH がすでに送信しているランタイムコンテキストスナップショットに `Current date: 2026-09-08 Asia/Shanghai Tuesday`(46文字、約12トークン)をぶら下げるだけです。
16
+ > `@deepseek-ai/dsh-time-context` を**読み込まず**、余分なセッションメッセージを**追加せず**、DSH のソースを**パッチせず**、PR も必要ありません。
17
+
18
+ - [動作原理: DSH のセッション、JSONL、リクエスト組み立て](./docs/dsh-session-and-context-mechanics.md)(中国語)
19
+ - [HANDOVER.md](./HANDOVER.md)(中国語)
20
+
21
+ ## このプラグインが解決する課題
22
+
23
+ DSH 自身の `@deepseek-ai/dsh-time-context` は、リクエストごとに約 **280文字** のメタデータを注入します:
24
+
25
+ ```
26
+ Time sampled while preparing turn 3, step 2: 2026-09-08T16:05:36+08:00[Asia/Shanghai]
27
+ Browser time zone for this request: Asia/Shanghai. Interpret otherwise-unqualified dates and times in this zone.
28
+ Elapsed since the preceding model-visible message: 2m 34s.
29
+ ```
30
+
31
+ このプラグインは同じ情報を **46文字** の 1 行に圧縮し、さらにその着地点を移動します — もうメッセージストリームには入りません:
32
+
33
+ ```
34
+ Current date: 2026-09-08 Asia/Shanghai Tuesday
35
+ ```
36
+
37
+ | 項目 | `dsh-time-context` | `dsh-date-wrapper` |
38
+ |-----------|--------------------|--------------------|
39
+ | 注入されるテキスト | 約280文字 | 46文字(↓84%)、約12トークン |
40
+ | 着地点 | プレステップごとに 1 メッセージ(`user/message`) | プラットフォームのランタイムコンテキストスナップショット(`systemPrompt.context`) |
41
+ | 頻度 | 対象となるステップごとに 1 イベント | テキストが変化したときにだけスナップショットと共に再送信(1 日以内は 0 イベント) |
42
+ | 依存関係 | `agents` サービス | `systemPrompt` サービス |
43
+ | 実行時依存関係 | — | なし |
44
+
45
+ ## バージョン互換性
46
+
47
+ | 項目 | 判定 |
48
+ |------|---------|
49
+ | 対象 DSH バージョン | 0.1.0-rc.7 → 0.1.3-alpha.2(コントラクトは安定。下の表を参照) |
50
+ | settings API | **該当なし**: プラグインは設定を登録せず、schemastery の `Config` もエクスポートしません |
51
+ | 使用しているコントラクトポイント | ちょうど 1 つ — `systemPrompt.context()` |
52
+ | ネイティブ機能との競合 | `@deepseek-ai/dsh-time-context` と重複します。**両方を同時に使わないでください**。デフォルトではインストールされない = デフォルトでオフ |
53
+ | ブラウザ側 | **なし**: スロットも DOM も CSS セマンティックトークンもありません |
54
+ | DSH パッケージのインポート | **ゼロ**: `@deepseek-ai/*` から何も取り込みません。これは「実行時検出 + デュアル API フォールバック」パターンよりも厳格です |
55
+
56
+ | コントラクトポイント | 0.1.0-rc.7 | 0.1.1-rc.2 | 0.1.2-rc.1 | 0.1.3-alpha.2 |
57
+ |---|---|---|---|---|
58
+ | `systemPrompt.context(ctx): () => void` | yes | yes(このホストで検証済み) | yes | yes |
59
+ | `PromptContext = { name, order, text }`、`complete` フィールドなし | yes | yes | yes | yes |
60
+ | `includeRuntimeContext` / `suppressRuntimeContext` | yes | yes | yes | yes |
61
+ | agent-loop の `project()` によるテキスト重複排除と `surfaceOp: "append"` | yes | yes | yes | 未比較 |
62
+
63
+ > 方法: `npm pack @deepseek-ai/dsh-system-prompt@<version>` で取得して展開し、`lib/types/index.d.ts` と `lib/index.js` を比較。`@deepseek-ai/dsh-agent-loop` も同様。
64
+ > このホストで**実行時に**検証済みなのは 0.1.1-rc.2 のみです。0.1.2-rc.1 / 0.1.3-alpha.2 の実行時検証はまだ保留中です(`HANDOVER.md` §7 を参照)。
65
+
66
+ ## メッセージではなくランタイムコンテキストスナップショットを使う理由
67
+
68
+ 最初の試みは `dsh-time-context` をコピーし、`agent/pre-step` で `user/message` を追加するものでした。計測したコストは高すぎました。各 JSONL イベントは **339バイト**(うちテキストは 46バイトに過ぎません。`content` と `sections` がそれぞれコピーを保持するためです)で、しかも**毎ターン**書き込まれていました。
69
+
70
+ 代わりにランタイムコンテキストを登録すると、日付はプラットフォームがすでに送信しているスナップショットメッセージに畳み込まれます:
71
+
72
+ - プラットフォームは**テキストでスナップショットを重複排除**します(`dsh-agent-loop` の `RuntimeContextProjection.project()`: `if (this.retained?.text === snapshot) return`)。したがって日付が変わらない間は**余分なイベントは 1 つも書き込まれません**。
73
+ - スナップショットはその場で書き換えるのではなく新しいメッセージを**追加**します(`surfaceOp: 'append'`)。そのためリクエスト列は増える一方です → **プレフィックスキャッシュが保持されます**。
74
+ - こちらの限界コストはこの 46バイトだけで、しかもテキストが変わったためにスナップショットが再送信されるときに限られます。
75
+
76
+ このホストで計測(実際の 1 セッション、10 ターン / 231ステップ):
77
+
78
+ | 項目 | 計測値 |
79
+ |------|----------|
80
+ | プラットフォームのランタイムコンテキストスナップショット | 2 イベント、各 1133 B、合計 2.3 KB |
81
+ | 実際のユーザーメッセージ | 10 イベント、各 396 B |
82
+ | 旧アプローチ(1 ターンに 1 メッセージ) | 10 × 339 B ≈ 3.4 KB |
83
+ | 本アプローチ | 余分なイベント 0。既存スナップショットに約 46 B を畳み込み |
84
+
85
+ ## 設定
86
+
87
+ `cordis.patch.yml` に同梱されています。変更後は再起動してください:
88
+
89
+ ```yaml
90
+ - insert:
91
+ - id: date-wrapper
92
+ name: dsh-date-wrapper
93
+ config:
94
+ timeZone: Asia/Shanghai # IANA zone; omit to use the process zone
95
+ ```
96
+
97
+ - 無効な `timeZone` は起動時に例外を投げます(UTC への暗黙のフォールバックは**ありません**)。
98
+ - テキスト内のゾーン名は解決済みの IANA 名です(`timeZone` を省略した場合はプロセスのゾーン名)。
99
+ - ランタイムコンテキストのエントリ名は `date-wrapper:date`、order は `116`(すでに使用済み: 110 sandbox、115 approval、120 subagent)。
100
+ - プラグインは schemastery の `Config` を**エクスポートしない**ため、その設定はホストのスキーマ検証をスキップします。検証はすべて `validateConfig()` で手作業で行われます。これが Settings → Plugins ページにこのプラグインの設定フォームが存在しない理由でもあります。
101
+
102
+ ## オン/オフ: プラグインの有効化そのものがスイッチで、パネルのトグルはありません
103
+
104
+ プラグインは設定パネルのトグルも `enabled` 設定フィールドも**同梱していません**。理由は次のとおりです:
105
+
106
+ - 機能スイッチは*プラグイン行が有効かどうか*そのものです。無効 → `apply()` が実行されない → ランタイムコンテキストのエントリが存在しない → 1 文字も注入されない。
107
+ - ブラウザ側(`dsh.client`)が存在しないため、UI が持つ私たちのウィジェットはありません。
108
+ - DSH 組み込みの **Settings → Plugins** ページは、各エントリをすでに `enabled / disabled` として表示します(読み取り専用)。
109
+
110
+ ### オフにする方法
111
+
112
+ **自分の**プロファイルパッチレイヤー — `C:\Users\<you>\.dsh\profiles\web\cordis.patch.yml` — で `id` によって上書きします:
113
+
114
+ ```yaml
115
+ - id: date-wrapper
116
+ disabled: true # disabled; set back to false to restore
117
+ ```
118
+
119
+ - **ホット、再起動不要**: このファイルは Cordis HMR が監視しており、`disabled: true` はその行の fiber を直接破棄します。
120
+ - `date-wrapper` 行がまだ存在しない場合(未インストール)、このパッチは `entry "date-wrapper" not found` の警告をログに出すだけで、起動は成功します。
121
+ - ⚠️ ファイルは**トップレベルの YAML 配列**でなければなりません。形式が不正だと**起動に失敗します**(DSH はユーザーパッチレイヤーに対して fail-loud です)。
122
+
123
+ ### 完全に削除する方法
124
+
125
+ ```bash
126
+ dsh plugin --profile web remove dsh-date-wrapper
127
+ ```
128
+
129
+ 削除はバンドルレイヤーを経由し、dsh web の**再起動が必要**です(バンドルパッチはホットリロードされません)。
130
+
131
+ ## インストール
132
+
133
+ ```bash
134
+ dsh plugin --profile web add github:drscrewdriver/dsh-date-wrapper
135
+ ```
136
+
137
+ dsh web を再起動し、ページを更新してください。ローカルパス / リンクモード / トラブルシューティングは [INSTALL.ja.md](./INSTALL.ja.md) を参照してください。
138
+
139
+ ## 検証
140
+
141
+ | # | 方法 | 期待結果 |
142
+ |---|-----|----------|
143
+ | A1 | 新しいセッションを開き、メッセージを 1 つ送信する | ランタイムコンテキストスナップショットに `Current date: YYYY-MM-DD <zone> <weekday>` が含まれる(`system-prompt` を出自とする注入コンテキスト行として表示される) |
144
+ | A2 | その行を確認する | 50文字以下(計測値は 46文字。PRD のしきい値 30 は、要望された形式のために緩和された) |
145
+ | A3 | プラグインを無効化する(プロファイルパッチ `disabled: true`) | 以降のセッションのスナップショットにその行が現れなくなる |
146
+ | A4 | セッションログを検索する | `Time sampled` / `Elapsed since` / `Browser time zone` が存在しない |
147
+ | A5 | `timeZone` を `UTC` に設定して再起動する | 日付が UTC に従う(ゾーン境界をまたぐと 1 日ずれることがある) |
148
+
149
+ ## 実装メモ
150
+
151
+ ```
152
+ dsh-date-wrapper/
153
+ ├── package.json # name / type: module / main / exports["."] / dsh.bundle.patch / files
154
+ ├── cordis.patch.yml # one insert row (no patch-level id → lands at the profile root = host plane)
155
+ ├── src/
156
+ │ ├── format.js # pure functions: resolveZone / renderDate / createDateContextText / validateConfig / TEXT_LABEL
157
+ │ └── index.js # apply(ctx, config) → ctx.inject(['systemPrompt'], …) → systemPrompt.context(...)
158
+ └── tests/
159
+ ├── format.test.mjs # 11 cases (zone projection, weekday, format and length, degradation, config validation)
160
+ └── context.test.mjs # 7 cases (registration contract against a fake ctx)
161
+ ```
162
+
163
+ - **ホストプレーンの行**: `ctx.inject(['systemPrompt'], …)` は子 fiber を開きます。サービスが存在しない場合、プラグインはブート全体を失敗させるのではなく、何も登録せずに静かに終了します。
164
+ - **フェイルソフトなテキストプロバイダ**: プロンプト組み立て中に例外を投げると**すべての**リクエストが失敗するため、レンダリング失敗時は空文字列を返します(プラットフォームは空テキストを除外します)。
165
+ - **`complete` なし**: 設定するとシステムプロンプト全体を覆い隠してしまいます。
166
+ - **重複排除はプラットフォームの仕事**: エージェントごとの状態は保持しません。日付をまたげば、スナップショットは単に新しい日付を運びます。
167
+ - **ライフサイクル**: 登録は `ctx.inject` の子 fiber に属し、プラグインが非アクティブ化されると回収されます。
168
+
169
+ ## 開発: TDD + lint
170
+
171
+ ```bash
172
+ npm install # devDependencies only (eslint / @eslint/js); zero runtime dependencies
173
+
174
+ npm run tdd # watch mode: rerun on src/ or tests/ changes (node --test --watch)
175
+ npm test # one full run: node --test "tests/*.test.mjs"
176
+ node tests/format.test.mjs # run a single file (most reliable under a sandbox: no child process)
177
+
178
+ npm run lint # eslint . (src + tests + eslint.config.mjs)
179
+ npm run lint:fix # auto-fix what can be fixed
180
+ npm run verify # lint + test; run this before committing
181
+ ```
182
+
183
+ ### レッド・グリーン・リファクタリング
184
+
185
+ テストケースは受け入れ基準に直接対応します。まず失敗するアサーションを書き、それから通るようにします。
186
+
187
+ | ステップ | アクション | コマンド |
188
+ |------|--------|---------|
189
+ | 1 red | 受け入れ基準にちなんで命名したアサーションを `tests/*.test.mjs` に追加し、まだ**持っていない**挙動をアサートする | `npm run tdd` |
190
+ | 2 green | 他のアサーションに触れずに通すための最小限の実装を `src/` に書く | `npm run tdd` |
191
+ | 3 refactor | グリーンを保ったままリネームと純粋関数の抽出を行う。`src/format.js` がすべての純粋ロジックを持ち、`src/index.js` は登録だけを行う | `npm run tdd` |
192
+ | 4 gate | コミット前に lint と全スイートを実行する | `npm run verify` |
193
+
194
+ 現在 18 個のアサーション: `format.test.mjs`(11)が純粋関数をカバーし、`context.test.mjs`(7)が偽の ctx に対する登録コントラクトをアサートします。
195
+
196
+ ### lint 設定のポイント
197
+
198
+ - ESLint 10 のフラット設定(`eslint.config.mjs`)で、ベースラインとして `@eslint/js` の recommended を使用。
199
+ - 厳格化したルール: `eqeqeq`、`prefer-const`、`object-shorthand`、`no-unused-vars`(`_` プレフィックスは除外)。
200
+ - Node のグローバル `crypto` / `console` / `process` を明示的に宣言。そうしないと `no-undef` が誤検出します。
201
+
202
+ ## 既知の制限
203
+
204
+ - **固定プロンプトのプリセットでは非アクティブ**: プリセットのペルソナが `includeRuntimeContext: false` を設定している場合(公式の `minimal` とローカルの `simple-reply` の両方がそう)、`assemble()` は `contexts: []` を返し、このプラグインのエントリは丸ごと破棄されます。これらのプリセットは、後続のリスナーがプロンプトに何も追加できないように設計されています。
205
+ - **古いスナップショットは履歴に残る**: 日付が変わるとプラットフォームは新しいスナップショットを追加し(古いものは保持される)、新しいものは「このスナップショットは以前のランタイムコンテキストスナップショットを上書きします」という自身の宣言によって有効になります — プラットフォームが cwd / sandbox / approval ポリシーの変更を扱うのと同じ方法です。
206
+ - **バンドルパッチはホットリロードされない**: `cordis.patch.yml` の変更やプラグインのアップグレードには dsh web の再起動が必要です(プロファイルパッチの `disabled` の変更はホットです)。
207
+ - **`dsh-time-context` は読み込まれず、フィルタもされない**: プリセットで明示的にマウントすると、その冗長なテキストが通常どおり表示されます。両方を併用しないでください。
208
+ - **コントラクトポイントの実行時プローブはなし**: `systemPrompt.context` はガードなしで呼び出されるため、将来 DSH がリネームすると、静かな劣化ではなくプラグインの読み込み失敗として表面化します(`HANDOVER.md` §7 を参照)。
209
+
210
+ ## ライセンス
211
+
212
+ MIT