dsh-trellis 0.1.4 → 0.1.6
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/README.md +98 -85
- package/cordis.patch.yml +1 -1
- package/lib/breadcrumb-projection.js +78 -0
- package/lib/breadcrumb.js +1 -41
- package/lib/index.js +55 -56
- package/lib/node-fs.js +1 -8
- package/lib/workflow.js +35 -52
- package/package.json +107 -100
package/README.md
CHANGED
|
@@ -1,54 +1,58 @@
|
|
|
1
|
-
# dsh-trellis
|
|
2
|
-
|
|
3
|
-
DeepSeek Harness (dsh) host 插件:把 [Trellis](https://github.com/mindfold-ai/Trellis) 工作流带进 dsh,并尽量沿用 DSH 原生能力补齐 Codex / Claude Code 级体验:
|
|
4
|
-
|
|
5
|
-
1. **每轮 workflow-state 面包屑注入** — 在每个 `agent/pre-step`,解析项目 `.trellis/workflow.md` 的 `[workflow-state:*]` 块 + 活跃任务状态(no_task / planning / in_progress),把对应面包屑注入会话(与 `dsh-agent-instructions` 的注入管道相同)。未变化不重复注入;被压缩后自动重注入;提示词里出现独立单词 `no-trellis` 可跳过当轮。
|
|
6
|
-
2. **隔离的原生会话上下文身份** — 从当前 agent 的 DSH 原生 session header 生成受管的 `DSH_TRELLIS_CONTEXT_ID`,让 `task.py start/create/current` 解析到会话级 active-task 指针,并在子代理身份不同于 shell 自身 session 时优先使用被转发的身份;插件命令启动的子进程也显式使用同一个 DSH 身份。
|
|
7
|
-
3. **原生子代理同步** — 安装插件后,Trellis 角色可用 DSH continuable 后台子代理;主会话先继续独立工作,耗尽后可调用事件驱动的 `trellis_wait`。它监听 DSH 的 `subagent/end`,利用 lifecycle 自带的 run/provider/output-block 元数据返回 `completed / failed / aborted / unknown` 的 fail-closed 结论。`error`、`max-tokens`、`refusal` 和未来未知失败原因都不会被误报成通过。rc.8 在父会话 idle 时也会用原生 settlement notice 唤醒,因此已经结束当前轮时无需额外调用 wait。
|
|
8
|
-
4. **`/trellis` 命令** — `/trellis-status`(活跃任务 + git 状态)、`/trellis-finish`(只读检查 + 安全收尾清单,不提前清 active-task 指针)。命令输出不进模型历史;真正的会话收尾走技能面 `/trellis-finish-work`,由技能先归档再写 journal。rc.8 下两条零输入命令使用 `recordInput: false`,并显式拒绝参数和图片附件,避免静默忽略输入。
|
|
9
|
-
5. **Web 配置菜单** — DSH rc.8 的“设置 → 插件 → 插件配置”会显示 Trellis 工作流卡片,可分阶段编辑全部六个配置项、恢复 profile 配置层并保存。写入经过 namespace revision fencing 和保存后读回确认;配置实时生效,禁用插件后卡片仍保留,可直接重新启用。
|
|
10
|
-
|
|
11
|
-
非 Trellis 项目不注入面包屑;命令只有被用户显式调用时才会检查并返回“未初始化 Trellis”。
|
|
12
|
-
|
|
13
|
-
这是 Trellis DSH 适配的**可选伴侣插件**,不是使用 `trellis init --dsh` 的前置条件。workflow 会按能力选择路径:存在 `trellis_wait` 时才使用 continuable 后台派发和事件汇合;未安装插件时,依赖结果的子代理从一开始就用 `run_in_background: false` 前台派发。两条路径都禁止 sleep 或轮询,Trellis CLI 也不会自动安装任何 DSH profile 插件。
|
|
14
|
-
|
|
15
|
-
## 安装
|
|
16
|
-
|
|
17
|
-
```powershell
|
|
18
|
-
# 装进实际使用的 profile(均在该 profile 下次启动时生效)
|
|
19
|
-
dsh plugin --profile web add dsh-trellis
|
|
20
|
-
dsh plugin --profile headless add dsh-trellis
|
|
21
|
-
|
|
22
|
-
# 如果你维护独立的 tui profile,也需要装进该 profile
|
|
23
|
-
dsh plugin --profile tui add dsh-trellis
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`dsh plugin` 会把插件安装到指定 profile;每个实际使用的 profile 都需要单独安装。升级到 npm 上的最新版本:
|
|
27
|
-
|
|
28
|
-
```powershell
|
|
29
|
-
dsh plugin --profile web update dsh-trellis
|
|
30
|
-
dsh plugin --profile headless update dsh-trellis
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
对 `tui` profile 使用同样的 update,并在该 profile 下次启动时生效。
|
|
34
|
-
|
|
35
|
-
如果要从源码开发或验证尚未发布的版本,可以改用本地 `file:` 安装:
|
|
36
|
-
|
|
37
|
-
```powershell
|
|
38
|
-
git clone https://github.com/SajoLuo/dsh-trellis.git
|
|
39
|
-
cd dsh-trellis
|
|
40
|
-
pnpm install --frozen-lockfile
|
|
41
|
-
pnpm run build:client
|
|
42
|
-
dsh plugin --profile headless add file:C:/path/to/dsh-trellis
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
`file:` 插件会作为 profile 内的 pnpm 快照安装;拉取源码更新后,尤其是版本新增文件时,需要先 remove 再 add 刷新该 profile。
|
|
46
|
-
|
|
47
|
-
配套要求:项目的 Trellis 平台需包含 dsh(`trellis init --dsh`,见 Trellis-DeepSeekHarness 适配分支),且 `.trellis/scripts` 需包含读取原生 `DSH_SESSION_ID` 的适配(已含在同一分支)。Host peer 范围显式覆盖 DSH `0.1.0-rc.6+`、`0.1.1-rc.1+`、`0.1.2-alpha.1+`、`0.1.3-alpha.1+` 与 `0.1.5-alpha.1+` 五条已知预发布线,避免 npm 的 prerelease 语义把新版 Host 误判为不兼容;本仓开发与回归基线固定在 `0.1.5-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
# dsh-trellis
|
|
2
|
+
|
|
3
|
+
DeepSeek Harness (dsh) host 插件:把 [Trellis](https://github.com/mindfold-ai/Trellis) 工作流带进 dsh,并尽量沿用 DSH 原生能力补齐 Codex / Claude Code 级体验:
|
|
4
|
+
|
|
5
|
+
1. **每轮 workflow-state 面包屑注入** — 在每个 `agent/pre-step`,解析项目 `.trellis/workflow.md` 的 `[workflow-state:*]` 块 + 活跃任务状态(no_task / task_error / planning / in_progress),把对应面包屑注入会话(与 `dsh-agent-instructions` 的注入管道相同)。未变化不重复注入;被压缩后自动重注入;提示词里出现独立单词 `no-trellis` 可跳过当轮。
|
|
6
|
+
2. **隔离的原生会话上下文身份** — 从当前 agent 的 DSH 原生 session header 生成受管的 `DSH_TRELLIS_CONTEXT_ID`,让 `task.py start/create/current` 解析到会话级 active-task 指针,并在子代理身份不同于 shell 自身 session 时优先使用被转发的身份;插件命令启动的子进程也显式使用同一个 DSH 身份。
|
|
7
|
+
3. **原生子代理同步** — 安装插件后,Trellis 角色可用 DSH continuable 后台子代理;主会话先继续独立工作,耗尽后可调用事件驱动的 `trellis_wait`。它监听 DSH 的 `subagent/end`,利用 lifecycle 自带的 run/provider/output-block 元数据返回 `completed / failed / aborted / unknown` 的 fail-closed 结论。`error`、`max-tokens`、`refusal` 和未来未知失败原因都不会被误报成通过。rc.8 在父会话 idle 时也会用原生 settlement notice 唤醒,因此已经结束当前轮时无需额外调用 wait。
|
|
8
|
+
4. **`/trellis` 命令** — `/trellis-status`(活跃任务 + git 状态)、`/trellis-finish`(只读检查 + 安全收尾清单,不提前清 active-task 指针)。命令输出不进模型历史;真正的会话收尾走技能面 `/trellis-finish-work`,由技能先归档再写 journal。rc.8 下两条零输入命令使用 `recordInput: false`,并显式拒绝参数和图片附件,避免静默忽略输入。
|
|
9
|
+
5. **Web 配置菜单** — DSH rc.8 的“设置 → 插件 → 插件配置”会显示 Trellis 工作流卡片,可分阶段编辑全部六个配置项、恢复 profile 配置层并保存。写入经过 namespace revision fencing 和保存后读回确认;配置实时生效,禁用插件后卡片仍保留,可直接重新启用。
|
|
10
|
+
|
|
11
|
+
非 Trellis 项目不注入面包屑;命令只有被用户显式调用时才会检查并返回“未初始化 Trellis”。
|
|
12
|
+
|
|
13
|
+
这是 Trellis DSH 适配的**可选伴侣插件**,不是使用 `trellis init --dsh` 的前置条件。workflow 会按能力选择路径:存在 `trellis_wait` 时才使用 continuable 后台派发和事件汇合;未安装插件时,依赖结果的子代理从一开始就用 `run_in_background: false` 前台派发。两条路径都禁止 sleep 或轮询,Trellis CLI 也不会自动安装任何 DSH profile 插件。
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
# 装进实际使用的 profile(均在该 profile 下次启动时生效)
|
|
19
|
+
dsh plugin --profile web add dsh-trellis
|
|
20
|
+
dsh plugin --profile headless add dsh-trellis
|
|
21
|
+
|
|
22
|
+
# 如果你维护独立的 tui profile,也需要装进该 profile
|
|
23
|
+
dsh plugin --profile tui add dsh-trellis
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`dsh plugin` 会把插件安装到指定 profile;每个实际使用的 profile 都需要单独安装。升级到 npm 上的最新版本:
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
dsh plugin --profile web update dsh-trellis
|
|
30
|
+
dsh plugin --profile headless update dsh-trellis
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
对 `tui` profile 使用同样的 update,并在该 profile 下次启动时生效。
|
|
34
|
+
|
|
35
|
+
如果要从源码开发或验证尚未发布的版本,可以改用本地 `file:` 安装:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
git clone https://github.com/SajoLuo/dsh-trellis.git
|
|
39
|
+
cd dsh-trellis
|
|
40
|
+
pnpm install --frozen-lockfile
|
|
41
|
+
pnpm run build:client
|
|
42
|
+
dsh plugin --profile headless add file:C:/path/to/dsh-trellis
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`file:` 插件会作为 profile 内的 pnpm 快照安装;拉取源码更新后,尤其是版本新增文件时,需要先 remove 再 add 刷新该 profile。
|
|
46
|
+
|
|
47
|
+
配套要求:项目的 Trellis 平台需包含 dsh(`trellis init --dsh`,见 Trellis-DeepSeekHarness 适配分支),且 `.trellis/scripts` 需包含读取原生 `DSH_SESSION_ID` 的适配(已含在同一分支)。Host peer 范围显式覆盖 DSH `0.1.0-rc.6+`、`0.1.1-rc.1+`、`0.1.2-alpha.1+`、`0.1.3-alpha.1+` 与 `0.1.5-alpha.1+` 五条已知预发布线,避免 npm 的 prerelease 语义把新版 Host 误判为不兼容;本仓开发与回归基线固定在 `0.1.5-rc.2`。Host 侧 Settings 桥同时兼容旧版包级 helper 与当前 provider 方法,Web 配置卡片只在具备 settings/client surface 的 profile 中加载。
|
|
48
|
+
|
|
49
|
+
运行时需要 profile 提供 `sessionProjections` 服务;该依赖由插件的 `inject` 声明。自定义或精简 profile 若未组装此服务,需先加载 `@deepseek-ai/dsh-session-projection`,否则插件会等待依赖,不会退回直接扫描历史。
|
|
50
|
+
|
|
51
|
+
### DSH 0.1.5-rc.2 对齐说明
|
|
52
|
+
|
|
53
|
+
- 已迁移:面包屑使用 `sessionProjections` 的纯 fold,在恢复或分叉时重建状态、按已提交事件增量更新,不再调用 Session 的 `eventAt()` / `snapshotEvents()` / `ownEvents()` 或访问旧事件集合。当前 DSH 注册 Host-only projection;旧版 registry 通过 projection snapshot 兼容,仅包含序号和 payload 哈希,不传输工作流正文。
|
|
54
|
+
- 已修复:恢复会话、插件或 Settings 重载后的首轮也会去重;以最后一条仍可见的面包屑判断,避免 A → B → A 状态切换被旧记录误抑制。压缩移除后会重新注入,字节预算变化导致的正文变化也会重新注入。
|
|
55
|
+
- 前瞻适配:[上游同步历史读取弃用决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md) 已进入主线,但 RC.2 仍提供这些方法;本次提前解除依赖,不代表 RC.2 已删除接口。插件不负责迁移磁盘会话格式。
|
|
52
56
|
- 已核对:插件不依赖 0.1.5 移除的 `ctx.agent` 或可构造 `Inbox`;pre-step 从事件载荷取得 agent,工具从 `exec.agent` 取得调用方,待处理消息只使用 `agent.inbox` 当前公开的 `nextStep`、`prepend`、`replace` 与 `remove`。
|
|
53
57
|
- 已采用:command lifecycle 的 `recordInput`、command attachment envelope 的 fail-closed 输入检查、`subagent/end` 的 run/provider/final-output 元数据,以及 report-before-settlement 与 idle-parent 原生唤醒语义。
|
|
54
58
|
- 已接入:Host `dsh-trellis` settings namespace 与 `dsh.client` 浏览器卡片。保存值写入 DSH 的 `settings.yaml` 用户层,并实时重挂插件 runtime;settings provider 单独重载时退回 loader 配置,插件自身卸载时则不会错误重挂 runtime。没有 settings 服务的 profile 继续使用原有 loader 配置。当前可选 Settings 生命周期由 `settings.installSection()` 承担;插件仍在运行时兼容旧版 helper。
|
|
@@ -59,37 +63,46 @@ dsh plugin --profile headless add file:C:/path/to/dsh-trellis
|
|
|
59
63
|
## 配置
|
|
60
64
|
|
|
61
65
|
支持 Settings provider 的 DSH Web profile 可直接打开“设置 → 插件 → 插件配置 → Trellis 工作流”。保存内容进入该 DSH_HOME 的 `settings.yaml`,优先级高于 profile 组合层并立即生效。
|
|
62
|
-
|
|
63
|
-
Headless、rc.6 或需要声明部署默认值时,仍可在 profile 的 `cordis.patch.yml` 里覆盖(整行替换);Web 卡片的“恢复配置文件值”会清除用户层字段并重新继承这里的值:
|
|
64
|
-
|
|
65
|
-
```yaml
|
|
66
|
-
- id: dsh-trellis
|
|
67
|
-
config:
|
|
68
|
-
maxBytes: 4096 # 面包屑注入的字节预算(0 = 关闭注入)
|
|
69
|
-
skipKeyword: no-trellis # 提示词中的独立单词可跳过当轮注入(空 = 禁用)
|
|
70
|
-
pythonCmd: "" # 空 = 自动选择:Windows 先 py -3 再 python;其他系统先 python3 再 python
|
|
71
|
-
commandsEnabled: true
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
66
|
+
|
|
67
|
+
Headless、rc.6 或需要声明部署默认值时,仍可在 profile 的 `cordis.patch.yml` 里覆盖(整行替换);Web 卡片的“恢复配置文件值”会清除用户层字段并重新继承这里的值:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
- id: dsh-trellis
|
|
71
|
+
config:
|
|
72
|
+
maxBytes: 4096 # 面包屑注入的字节预算(0 = 关闭注入)
|
|
73
|
+
skipKeyword: no-trellis # 提示词中的独立单词可跳过当轮注入(空 = 禁用)
|
|
74
|
+
pythonCmd: "" # 空 = 自动选择:Windows 先 py -3 再 python;其他系统先 python3 再 python
|
|
75
|
+
commandsEnabled: true
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 0.1.6:Trellis 会话隔离与任务错误兼容
|
|
79
|
+
|
|
80
|
+
- 跟随 [Trellis #608](https://github.com/mindfold-ai/Trellis/pull/608):只读取当前 DSH 会话的精确指针。缺少身份或匹配指针时返回 `no_task`,不再借用唯一的其他会话;也不从派发提示词反向建立会话绑定。子代理继续通过明确的 `Active task:` 派发上下文读取任务。
|
|
81
|
+
- 对齐 [官方 `task_error` 工作流](https://github.com/mindfold-ai/marketplace/commit/62a77c9b57fb8bb081c110394bce08b6ebc6e09a):已绑定任务的 `task.json` 缺失、不可读、JSON 损坏或状态为空时,保留任务路径,提示先修复记录,不把它当成“没有任务”。不修改或删除任何任务与指针。
|
|
82
|
+
- 旧项目没有 `[workflow-state:task_error]` 块时,插件提供保守的修复提示;恢复有效记录后,下一轮自动恢复正常状态。因此无需先更新 Trellis CLI 才能使用本插件的修复。
|
|
83
|
+
- 插件修复不替代 Trellis Python 脚本升级:旧版 `task.py current/finish` 的回退策略、归档恢复和上下文清单检查仍由项目中的 `.trellis/scripts` 实现。
|
|
84
|
+
|
|
85
|
+
## 工作原理
|
|
86
|
+
|
|
87
|
+
- **状态解析**(`lib/workflow.js`):向上找项目根 → 读 `.trellis/workflow.md` 解析状态块 → 只读当前会话指针 `.trellis/.runtime/sessions/dsh_<id>.json`,不枚举其他会话。指针缺失时返回 `no_task`;指向的任务记录不可读时返回 `task_error` 并保留路径。修复记录或重新绑定任务后会在下一轮重新解析。
|
|
88
|
+
- **注入去重**(`lib/breadcrumb-projection.js`):投影仅保存面包屑的事件序号和 source/content 指纹,与 DSH 已维护的 `session.surface.nodes` 一起确定最后可见面包屑。恢复、分叉和重载均从 projection 重建;不缓存“本进程最后注入”的临时判断,也不扫描完整事件历史。
|
|
89
|
+
- **会话身份**:DSH 原生提供 `DSH_SESSION_ID = agent.session.header.id`,并先丢弃环境中已有的 `DSH_*` 再重建受管命名空间。Trellis beta 因此会在同时看到 `DSH_SHELL=1` 与 `DSH_SESSION_ID` 时优先解析当前 DSH 身份,即使没有插件也不会被外层 host 继承的 `TRELLIS_CONTEXT_ID` 串任务。插件通过 `shellEnv` 为每次执行额外生成 `DSH_TRELLIS_CONTEXT_ID = dsh_<session-id>`,用于转发可能不同于 shell 自身 session 的子代理身份;主会话与子代理仍各自保留 DSH 身份,子代理通过派发 prompt 首行的 `Active task:` 和角色 prelude 取得父任务上下文。
|
|
90
|
+
- **Headless 会话**:每次 `dsh --profile headless` 调用都是新的 DSH session。需要跨轮保留 active-task 指针时,应保持同一会话或显式 resume 返回的 session id,不能把多个独立 headless 调用当成同一 session。
|
|
91
|
+
- **取消与生命周期**:命令和 `trellis_wait` 都继承 DSH invocation 的 `AbortSignal`;取消后命令不会继续尝试另一个 Python 启动器,等待工具也会立即注销临时事件监听器。`trellis_wait` 只在收到配对的结算事件时声明 `settlementNoticeQueued=true`;仅从 catalog 看到 inactive 时保持 `unknown`,不猜任务通过。插件卸载时只注销自己的命令、工具和监听器。
|
|
92
|
+
- **子代理并发**:workflow 只有在发现 `trellis_wait` 时才使用 DSH 原生 continuable 后台 `subagent`。主会话并行做独立工作;需要汇合时调用一次 `trellis_wait <subagent_id>` 等待原生结算事件。没有该工具时,首次派发直接设置 `run_in_background: false`,绝不留下无法事件汇合的后台子代理。
|
|
93
|
+
|
|
94
|
+
## 开发
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
pnpm install
|
|
98
|
+
pnpm run build:client
|
|
99
|
+
pnpm test # node --test test/*.test.js
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
GitHub Actions 在 Windows / Linux 的 Node 24 环境执行锁定安装、客户端构建、测试和打包检查;其中包含真实 Session/projection 服务的增量驱动、恢复、分叉、压缩和 checkpoint 回归,以及真实临时项目中的精确身份绑定、跨会话隔离、任务记录损坏/修复、解绑与 Settings 重载回归。
|
|
103
|
+
|
|
104
|
+
Host half 是直接由 `main` 加载的 ESM JavaScript;Web half 通过 tsdown 生成 DSH lazy-CJS factory 到 `lib/client.js`。`pnpm pack` 会在 prepack 阶段自动重建客户端 bundle。
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# dsh-trellis bundle patch: one insert over the composed profile tree.
|
|
2
|
-
# Service injection (
|
|
2
|
+
# Service injection (including sessionProjections) is declared by the plugin module.
|
|
3
3
|
- insert:
|
|
4
4
|
- id: dsh-trellis
|
|
5
5
|
name: dsh-trellis
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replayable breadcrumb identity, driven by DSH's projection registry.
|
|
3
|
+
* Only fingerprints are retained, never message bodies or a second event log.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { isBreadcrumbMessage } from "./breadcrumb.js";
|
|
8
|
+
|
|
9
|
+
export const BREADCRUMB_PROJECTION_KEY = "dsh-trellis.breadcrumbs";
|
|
10
|
+
|
|
11
|
+
/** Stable JSON payload identity, excluding the per-injection message id. */
|
|
12
|
+
export function breadcrumbFingerprint(message) {
|
|
13
|
+
const json = JSON.stringify(
|
|
14
|
+
{ content: message.content, source: message.source },
|
|
15
|
+
(_key, value) =>
|
|
16
|
+
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
17
|
+
? Object.fromEntries(Object.entries(value).sort(([a], [b]) =>
|
|
18
|
+
a < b ? -1 : a > b ? 1 : 0,
|
|
19
|
+
))
|
|
20
|
+
: value,
|
|
21
|
+
);
|
|
22
|
+
return createHash("sha256").update(json).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const breadcrumbProjection = {
|
|
26
|
+
key: BREADCRUMB_PROJECTION_KEY,
|
|
27
|
+
stateVersion: 1,
|
|
28
|
+
stateSchema: z.record(
|
|
29
|
+
z.string().regex(/^(0|[1-9]\d*)$/),
|
|
30
|
+
z.string().regex(/^[a-f0-9]{64}$/),
|
|
31
|
+
),
|
|
32
|
+
init: () => ({}),
|
|
33
|
+
apply(state, event) {
|
|
34
|
+
if (event.type !== "user/message" || !isBreadcrumbMessage(event.data)) {
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
return { ...state, [event.seq]: breadcrumbFingerprint(event.data) };
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Register/read on the owning runtime scope; unload removes our key only. */
|
|
42
|
+
export function registerBreadcrumbProjection(ctx) {
|
|
43
|
+
const registry = ctx.sessionProjections;
|
|
44
|
+
if (typeof registry?.register !== "function") {
|
|
45
|
+
throw new Error("dsh-trellis requires the DSH sessionProjections service");
|
|
46
|
+
}
|
|
47
|
+
const hostOnly = typeof registry.stateOf === "function";
|
|
48
|
+
// rc.6 predates host-only units. Its projection snapshot contains only our
|
|
49
|
+
// seq -> hash index, not workflow text. This is NOT a Session log snapshot.
|
|
50
|
+
const definition = hostOnly ? breadcrumbProjection : {
|
|
51
|
+
...breadcrumbProjection,
|
|
52
|
+
schema: breadcrumbProjection.stateSchema,
|
|
53
|
+
view: (state) => state,
|
|
54
|
+
};
|
|
55
|
+
const dispose = registry.register(definition);
|
|
56
|
+
return {
|
|
57
|
+
dispose,
|
|
58
|
+
isVisible(session, message) {
|
|
59
|
+
const state = hostOnly
|
|
60
|
+
? registry.stateOf(session, BREADCRUMB_PROJECTION_KEY)
|
|
61
|
+
: registry.snapshot(session).values[BREADCRUMB_PROJECTION_KEY];
|
|
62
|
+
if (state === undefined) {
|
|
63
|
+
throw new Error("dsh-trellis breadcrumb projection is not registered");
|
|
64
|
+
}
|
|
65
|
+
// Surface order, not maximum event seq: replacements can reorder seqs.
|
|
66
|
+
// Only the LAST visible breadcrumb may suppress a new state (A -> B -> A).
|
|
67
|
+
// Provenance can cite still-visible nodes, so do not prune by sourceEventSeqs.
|
|
68
|
+
const nodes = session.surface.nodes;
|
|
69
|
+
for (let i = nodes.length - 1; i >= 0; i -= 1) {
|
|
70
|
+
const fingerprint = state[nodes[i]];
|
|
71
|
+
if (fingerprint !== undefined) {
|
|
72
|
+
return fingerprint === breadcrumbFingerprint(message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
package/lib/breadcrumb.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @module dsh-trellis/breadcrumb
|
|
5
5
|
*/
|
|
6
6
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
-
import { join } from "node:path";
|
|
8
7
|
import { resolveTrellisState, sanitizeContextKey } from "./workflow.js";
|
|
9
8
|
import { nodeFs } from "./node-fs.js";
|
|
10
9
|
|
|
@@ -61,39 +60,6 @@ export function isBreadcrumbMessage(message) {
|
|
|
61
60
|
);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
/** Read one session event across the current and legacy DSH session APIs. */
|
|
65
|
-
export function sessionEventAt(session, seq) {
|
|
66
|
-
if (typeof session.eventAt === "function") return session.eventAt(seq);
|
|
67
|
-
if (typeof session.events?.get === "function") return session.events.get(seq);
|
|
68
|
-
return session.events?.[seq];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Whether the same breadcrumb payload is already visible in the session surface. */
|
|
72
|
-
export function isBreadcrumbVisible(agent, digest) {
|
|
73
|
-
const { session } = agent;
|
|
74
|
-
const visibleSeqs = new Set(session.surface.nodes);
|
|
75
|
-
// Current DSH sessions, including Session V3, expose an immutable on-demand
|
|
76
|
-
// snapshot. Keep the legacy collection fallback for older versions covered
|
|
77
|
-
// by our peer range.
|
|
78
|
-
const events =
|
|
79
|
-
typeof session.snapshotEvents === "function"
|
|
80
|
-
? session.snapshotEvents()
|
|
81
|
-
: session.events;
|
|
82
|
-
for (const [fallbackSeq, event] of events.entries()) {
|
|
83
|
-
const seq = event?.seq ?? fallbackSeq;
|
|
84
|
-
if (!visibleSeqs.has(seq) || event.type !== "user/message") continue;
|
|
85
|
-
const source = event.data?.source;
|
|
86
|
-
if (
|
|
87
|
-
source?.kind === SOURCE_KIND &&
|
|
88
|
-
source?.form === SOURCE_FORM &&
|
|
89
|
-
source.digest === digest
|
|
90
|
-
) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
63
|
/**
|
|
98
64
|
* Does the claimed prompt batch contain the skip keyword as a standalone word?
|
|
99
65
|
* @param {Array<{content?: unknown}>} messages
|
|
@@ -127,7 +93,6 @@ export function hasSkipKeyword(messages, keyword) {
|
|
|
127
93
|
export function createBreadcrumbComposer(resolved, ctx, dependencies = {}) {
|
|
128
94
|
const fs = dependencies.fs ?? nodeFs;
|
|
129
95
|
const cache = new Map();
|
|
130
|
-
const lastInjected = new WeakMap();
|
|
131
96
|
|
|
132
97
|
const compose = async (agent, messages, signal) => {
|
|
133
98
|
if (resolved.maxBytes <= 0 || !Number.isFinite(resolved.maxBytes))
|
|
@@ -136,7 +101,7 @@ export function createBreadcrumbComposer(resolved, ctx, dependencies = {}) {
|
|
|
136
101
|
const cwd = agent.session.header.cwd ?? process.cwd();
|
|
137
102
|
const sessionId = agent.session.header.id ?? "";
|
|
138
103
|
const contextKey =
|
|
139
|
-
sessionId === "" ? "
|
|
104
|
+
sessionId === "" ? "" : `dsh_${sanitizeContextKey(sessionId)}`;
|
|
140
105
|
signal?.throwIfAborted();
|
|
141
106
|
const state = await resolveTrellisState({
|
|
142
107
|
cwd,
|
|
@@ -147,11 +112,6 @@ export function createBreadcrumbComposer(resolved, ctx, dependencies = {}) {
|
|
|
147
112
|
});
|
|
148
113
|
signal?.throwIfAborted();
|
|
149
114
|
if (state === null) return undefined;
|
|
150
|
-
const previous = lastInjected.get(agent.session);
|
|
151
|
-
if (previous !== undefined && previous.digest === state.digest) {
|
|
152
|
-
if (isBreadcrumbVisible(agent, state.digest)) return undefined;
|
|
153
|
-
}
|
|
154
|
-
lastInjected.set(agent.session, { digest: state.digest });
|
|
155
115
|
return breadcrumbMessage(state, resolved.maxBytes);
|
|
156
116
|
};
|
|
157
117
|
|
package/lib/index.js
CHANGED
|
@@ -25,8 +25,8 @@ import { Config, resolveConfig } from "./config.js";
|
|
|
25
25
|
import {
|
|
26
26
|
createBreadcrumbComposer,
|
|
27
27
|
isBreadcrumbMessage,
|
|
28
|
-
sessionEventAt,
|
|
29
28
|
} from "./breadcrumb.js";
|
|
29
|
+
import { registerBreadcrumbProjection } from "./breadcrumb-projection.js";
|
|
30
30
|
import { registerCommands } from "./commands.js";
|
|
31
31
|
import { registerSessionEnv } from "./session-env.js";
|
|
32
32
|
import { installSettingsSectionCompat } from "./settings-compat.js";
|
|
@@ -36,7 +36,7 @@ export const name = "dsh-trellis";
|
|
|
36
36
|
export const SETTINGS_NAMESPACE = "dsh-trellis";
|
|
37
37
|
|
|
38
38
|
/** @type {string[]} */
|
|
39
|
-
export const inject = ["commands", "shellEnv", "subagents", "tools"];
|
|
39
|
+
export const inject = ["commands", "shellEnv", "subagents", "tools", "sessionProjections"];
|
|
40
40
|
|
|
41
41
|
export { Config };
|
|
42
42
|
|
|
@@ -131,26 +131,12 @@ export function insertBreadcrumbDecision(decision, claimedMessages, desired) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
function registerBreadcrumb(ctx, resolved) {
|
|
135
|
-
const { compose } = createBreadcrumbComposer(resolved, ctx);
|
|
134
|
+
function registerBreadcrumb(ctx, resolved) {
|
|
135
|
+
const { compose } = createBreadcrumbComposer(resolved, ctx);
|
|
136
|
+
const projection = registerBreadcrumbProjection(ctx);
|
|
136
137
|
|
|
137
138
|
const syncInbox = (agent, desired) => {
|
|
138
139
|
const pending = agent.inbox.nextStep.filter(isBreadcrumbMessage);
|
|
139
|
-
const alreadySupplied =
|
|
140
|
-
desired === undefined ||
|
|
141
|
-
agent.session.surface.nodes.some((seq) => {
|
|
142
|
-
const event = sessionEventAt(agent.session, seq);
|
|
143
|
-
return (
|
|
144
|
-
event?.type === "user/message" &&
|
|
145
|
-
event.data !== undefined &&
|
|
146
|
-
isBreadcrumbMessage(event.data) &&
|
|
147
|
-
sameBreadcrumbPayload(event.data, desired)
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
if (alreadySupplied) {
|
|
151
|
-
for (const message of pending) agent.inbox.remove(message.id);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
140
|
const reusable = pending.find((message) =>
|
|
155
141
|
sameBreadcrumbPayload(message, desired),
|
|
156
142
|
);
|
|
@@ -165,41 +151,54 @@ function registerBreadcrumb(ctx, resolved) {
|
|
|
165
151
|
for (const message of pending.slice(1)) agent.inbox.remove(message.id);
|
|
166
152
|
};
|
|
167
153
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
desired
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
154
|
+
let disposeStep;
|
|
155
|
+
try {
|
|
156
|
+
disposeStep = ctx.on("agent/pre-step", async ({ agent, messages, step, signal }, next) => {
|
|
157
|
+
const decision = await next();
|
|
158
|
+
let desired;
|
|
159
|
+
try {
|
|
160
|
+
desired = await compose(agent, messages, signal);
|
|
161
|
+
if (desired !== undefined && projection.isVisible(agent.session, desired)) {
|
|
162
|
+
desired = undefined;
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (signal !== undefined && signal.aborted) throw error;
|
|
166
|
+
ctx.logger?.warn?.(
|
|
167
|
+
"dsh-trellis breadcrumb composition failed: %o",
|
|
168
|
+
error,
|
|
169
|
+
);
|
|
170
|
+
return decision;
|
|
171
|
+
}
|
|
172
|
+
signal?.throwIfAborted();
|
|
173
|
+
if (desired === undefined) {
|
|
174
|
+
const pending = agent.inbox.nextStep.filter(isBreadcrumbMessage);
|
|
175
|
+
for (const message of pending) agent.inbox.remove(message.id);
|
|
176
|
+
return decision;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
decision.kind === "reject" ||
|
|
180
|
+
(step === 1 && decision.messages.length === 0)
|
|
181
|
+
) {
|
|
182
|
+
syncInbox(agent, desired);
|
|
183
|
+
return decision;
|
|
184
|
+
}
|
|
185
|
+
const pending = agent.inbox.nextStep.filter(isBreadcrumbMessage);
|
|
186
|
+
for (const message of pending) agent.inbox.remove(message.id);
|
|
187
|
+
if (
|
|
188
|
+
decision.messages.some((message) =>
|
|
189
|
+
sameBreadcrumbPayload(message, desired),
|
|
190
|
+
)
|
|
191
|
+
) {
|
|
192
|
+
return decision;
|
|
193
|
+
}
|
|
194
|
+
return insertBreadcrumbDecision(decision, messages, desired);
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
projection.dispose();
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
return () => {
|
|
201
|
+
disposeStep();
|
|
202
|
+
projection.dispose();
|
|
203
|
+
};
|
|
205
204
|
}
|
package/lib/node-fs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module dsh-trellis/node-fs
|
|
5
5
|
*/
|
|
6
|
-
import { stat as nodeStat, readFile as nodeReadFile
|
|
6
|
+
import { stat as nodeStat, readFile as nodeReadFile } from "node:fs/promises";
|
|
7
7
|
|
|
8
8
|
/** @type {import("./workflow.js").FsShim} */
|
|
9
9
|
export const nodeFs = {
|
|
@@ -30,11 +30,4 @@ export const nodeFs = {
|
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
|
-
async listDir(path) {
|
|
34
|
-
try {
|
|
35
|
-
return await nodeReaddir(path);
|
|
36
|
-
} catch {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
33
|
};
|
package/lib/workflow.js
CHANGED
|
@@ -25,6 +25,12 @@ export function sanitizeContextKey(value) {
|
|
|
25
25
|
export const FALLBACK_BREADCRUMB =
|
|
26
26
|
"Refer to `.trellis/workflow.md` for the current workflow step (no matching [workflow-state] block found).";
|
|
27
27
|
|
|
28
|
+
/** Old workflows may not yet define task_error; never turn a bad record into no_task. */
|
|
29
|
+
export const TASK_ERROR_BREADCRUMB =
|
|
30
|
+
"The active task record could not be read. Do not create or activate another task. " +
|
|
31
|
+
"Inspect the active task directory and repair its task.json (a JSON object with a non-empty status). " +
|
|
32
|
+
"Preserve existing fields and artifacts; ask the user if the correct status is uncertain.";
|
|
33
|
+
|
|
28
34
|
/**
|
|
29
35
|
* Parse every `[workflow-state:STATUS] ... [/workflow-state:STATUS]` block.
|
|
30
36
|
* @param {string} content - workflow.md content.
|
|
@@ -82,71 +88,42 @@ export async function readCached(path, fs, cache) {
|
|
|
82
88
|
/**
|
|
83
89
|
* Resolve the active task for a dsh session.
|
|
84
90
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* refuses to guess even when one task happens to be newer.
|
|
91
|
+
* Read only this session's pointer. A sole foreign session is not proof of
|
|
92
|
+
* ownership (Trellis #608). Child agents receive an explicit Active task:
|
|
93
|
+
* dispatch prompt; they must not borrow arbitrary session bindings here.
|
|
94
|
+
* Once bound, an unreadable task record is task_error, not an absent task.
|
|
90
95
|
*
|
|
91
96
|
* @param {string} root - project root.
|
|
92
97
|
* @param {string} contextKey - sanitized `dsh_<session-id>` key for this agent.
|
|
93
|
-
* @param {{
|
|
98
|
+
* @param {{readFile(path: string): Promise<string | null>}} fs
|
|
94
99
|
* @returns {Promise<{path: string, status: string} | null>} task dir-relative path + status.
|
|
95
100
|
*/
|
|
96
101
|
export async function resolveActiveTask(root, contextKey, fs) {
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
102
|
+
if (!contextKey) return null;
|
|
103
|
+
const pointer = join(
|
|
104
|
+
root, ".trellis", ".runtime", "sessions", `${contextKey}.json`,
|
|
105
|
+
);
|
|
106
|
+
let data = null;
|
|
100
107
|
try {
|
|
101
|
-
|
|
102
|
-
} catch {
|
|
103
|
-
names = [];
|
|
104
|
-
}
|
|
105
|
-
for (const name of names) {
|
|
106
|
-
if (name !== `${contextKey}.json`) continue;
|
|
107
|
-
let data = null;
|
|
108
|
-
try {
|
|
109
|
-
data = JSON.parse((await fs.readFile(join(sessionsDir, name))) ?? "null");
|
|
110
|
-
} catch {
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (
|
|
114
|
-
data === null ||
|
|
115
|
-
typeof data !== "object" ||
|
|
116
|
-
typeof data.current_task !== "string"
|
|
117
|
-
)
|
|
118
|
-
continue;
|
|
119
|
-
const status = await readTaskStatus(root, data.current_task, fs);
|
|
120
|
-
if (status !== null) return { path: data.current_task, status };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 2. Official single-session fallback. Count files before parsing so one
|
|
124
|
-
// malformed pointer cannot make a multi-window runtime look unambiguous.
|
|
125
|
-
const sessionFiles = names.filter((name) => name.endsWith(".json"));
|
|
126
|
-
if (sessionFiles.length !== 1) return null;
|
|
127
|
-
|
|
128
|
-
let fallback = null;
|
|
129
|
-
try {
|
|
130
|
-
fallback = JSON.parse(
|
|
131
|
-
(await fs.readFile(join(sessionsDir, sessionFiles[0]))) ?? "null",
|
|
132
|
-
);
|
|
108
|
+
data = JSON.parse((await fs.readFile(pointer)) ?? "null");
|
|
133
109
|
} catch {
|
|
134
110
|
return null;
|
|
135
111
|
}
|
|
136
112
|
if (
|
|
137
|
-
|
|
138
|
-
typeof
|
|
139
|
-
|
|
140
|
-
|
|
113
|
+
data === null ||
|
|
114
|
+
typeof data !== "object" ||
|
|
115
|
+
Array.isArray(data) ||
|
|
116
|
+
typeof data.current_task !== "string" ||
|
|
117
|
+
data.current_task.trim() === ""
|
|
141
118
|
) {
|
|
142
119
|
return null;
|
|
143
120
|
}
|
|
144
|
-
const status = await readTaskStatus(root,
|
|
145
|
-
return
|
|
121
|
+
const status = await readTaskStatus(root, data.current_task, fs);
|
|
122
|
+
return { path: data.current_task, status: status ?? "task_error" };
|
|
146
123
|
}
|
|
147
124
|
|
|
148
125
|
/**
|
|
149
|
-
* Read
|
|
126
|
+
* Read a non-empty task.json status, or null for an unreadable/invalid record.
|
|
150
127
|
* @param {string} root
|
|
151
128
|
* @param {string} taskPath - repo-root relative task dir (e.g. `.trellis/tasks/04-17-foo`).
|
|
152
129
|
* @param {{readFile(path: string): Promise<string | null>}} fs
|
|
@@ -160,7 +137,9 @@ export async function readTaskStatus(root, taskPath, fs) {
|
|
|
160
137
|
if (
|
|
161
138
|
data !== null &&
|
|
162
139
|
typeof data === "object" &&
|
|
163
|
-
|
|
140
|
+
!Array.isArray(data) &&
|
|
141
|
+
typeof data.status === "string" &&
|
|
142
|
+
data.status.trim() !== ""
|
|
164
143
|
) {
|
|
165
144
|
return data.status;
|
|
166
145
|
}
|
|
@@ -173,11 +152,12 @@ export async function readTaskStatus(root, taskPath, fs) {
|
|
|
173
152
|
/**
|
|
174
153
|
* Map a task.json status to a workflow-state breadcrumb status.
|
|
175
154
|
* @param {string | null} status
|
|
176
|
-
* @returns {"planning" | "in_progress" | "no_task"}
|
|
155
|
+
* @returns {"planning" | "in_progress" | "task_error" | "no_task"}
|
|
177
156
|
*/
|
|
178
157
|
export function mapStatus(status) {
|
|
179
158
|
if (status === "planning") return "planning";
|
|
180
159
|
if (status === "in_progress") return "in_progress";
|
|
160
|
+
if (status === "task_error") return "task_error";
|
|
181
161
|
return "no_task";
|
|
182
162
|
}
|
|
183
163
|
|
|
@@ -187,7 +167,7 @@ export function mapStatus(status) {
|
|
|
187
167
|
* @param {string} options.cwd
|
|
188
168
|
* @param {string[]} options.markers
|
|
189
169
|
* @param {string} options.contextKey - sanitized session context key.
|
|
190
|
-
* @param {object} options.fs - filesystem shim ({exists,
|
|
170
|
+
* @param {object} options.fs - filesystem shim ({exists, readFile, stat}).
|
|
191
171
|
* @param {Map<string, {mtimeMs: number, content: string | null}>} options.cache
|
|
192
172
|
* @returns {Promise<{root: string, status: string, taskPath: string | null, body: string, digest: string} | null>}
|
|
193
173
|
*/
|
|
@@ -207,7 +187,10 @@ export async function resolveTrellisState({
|
|
|
207
187
|
if (blocks.size === 0) return null;
|
|
208
188
|
const task = await resolveActiveTask(root, contextKey, fs);
|
|
209
189
|
const status = mapStatus(task?.status ?? null);
|
|
210
|
-
const body =
|
|
190
|
+
const body =
|
|
191
|
+
status === "task_error"
|
|
192
|
+
? blocks.get(status) || TASK_ERROR_BREADCRUMB
|
|
193
|
+
: blocks.get(status) ?? FALLBACK_BREADCRUMB;
|
|
211
194
|
const digest = createHash("sha1")
|
|
212
195
|
.update(`${status}\n${task?.path ?? ""}\n${body}`)
|
|
213
196
|
.digest("hex");
|
package/package.json
CHANGED
|
@@ -1,121 +1,128 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-trellis",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Trellis workflow integration for DeepSeek Harness: per-turn workflow-state breadcrumbs, utility commands, and native session identity.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./lib/index.js",
|
|
9
|
-
"./client": "./lib/client.js",
|
|
10
|
-
"./package.json": "./package.json"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"lib",
|
|
14
|
-
"cordis.patch.yml"
|
|
15
|
-
],
|
|
16
|
-
"author": "SajoLuo",
|
|
17
|
-
"license": "MIT",
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/SajoLuo/dsh-trellis.git"
|
|
21
|
-
},
|
|
22
|
-
"homepage": "https://github.com/SajoLuo/dsh-trellis#readme",
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/SajoLuo/dsh-trellis/issues"
|
|
25
|
-
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"deepseek-harness",
|
|
28
|
-
"dsh",
|
|
29
|
-
"trellis",
|
|
30
|
-
"workflow",
|
|
31
|
-
"ai-agent"
|
|
32
|
-
],
|
|
33
|
-
"publishConfig": {
|
|
34
|
-
"access": "public"
|
|
35
|
-
},
|
|
36
|
-
"packageManager": "pnpm@10.33.0",
|
|
37
|
-
"dsh": {
|
|
38
|
-
"bundle": {
|
|
39
|
-
"patch": "cordis.patch.yml"
|
|
40
|
-
},
|
|
41
|
-
"client": {
|
|
42
|
-
"inject": [
|
|
43
|
-
"@deepseek-ai/dsh-client-locale",
|
|
44
|
-
"@deepseek-ai/dsh-client-ui-settings",
|
|
45
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
46
|
-
],
|
|
47
|
-
"platform": "web"
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build:client": "tsdown",
|
|
52
|
-
"test": "node --test \"test/*.test.js\"",
|
|
53
|
-
"prepack": "pnpm run build:client"
|
|
54
|
-
},
|
|
55
|
-
"peerDependencies": {
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-trellis",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "Trellis workflow integration for DeepSeek Harness: per-turn workflow-state breadcrumbs, utility commands, and native session identity.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"cordis.patch.yml"
|
|
15
|
+
],
|
|
16
|
+
"author": "SajoLuo",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/SajoLuo/dsh-trellis.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/SajoLuo/dsh-trellis#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/SajoLuo/dsh-trellis/issues"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"deepseek-harness",
|
|
28
|
+
"dsh",
|
|
29
|
+
"trellis",
|
|
30
|
+
"workflow",
|
|
31
|
+
"ai-agent"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"packageManager": "pnpm@10.33.0",
|
|
37
|
+
"dsh": {
|
|
38
|
+
"bundle": {
|
|
39
|
+
"patch": "cordis.patch.yml"
|
|
40
|
+
},
|
|
41
|
+
"client": {
|
|
42
|
+
"inject": [
|
|
43
|
+
"@deepseek-ai/dsh-client-locale",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
46
|
+
],
|
|
47
|
+
"platform": "web"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build:client": "tsdown",
|
|
52
|
+
"test": "node --test \"test/*.test.js\"",
|
|
53
|
+
"prepack": "pnpm run build:client"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
57
57
|
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
58
58
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
59
60
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
60
61
|
"@deepseek-ai/dsh-shell-env": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
61
62
|
"@deepseek-ai/dsh-subagent": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
62
63
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-alpha.1 || ^0.1.3-alpha.1 || ^0.1.5-alpha.1",
|
|
63
64
|
"@deepseek-ai/schemastery": "^3.18.2"
|
|
64
|
-
},
|
|
65
|
-
"peerDependenciesMeta": {
|
|
66
|
-
"@deepseek-ai/cordis": {
|
|
67
|
-
"optional": true
|
|
68
|
-
},
|
|
69
|
-
"@deepseek-ai/dsh-commands": {
|
|
70
|
-
"optional": true
|
|
71
|
-
},
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@deepseek-ai/cordis": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@deepseek-ai/dsh-commands": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
72
73
|
"@deepseek-ai/dsh-llm": {
|
|
73
74
|
"optional": true
|
|
74
75
|
},
|
|
76
|
+
"@deepseek-ai/dsh-session-projection": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
75
79
|
"@deepseek-ai/dsh-settings": {
|
|
76
80
|
"optional": true
|
|
77
81
|
},
|
|
78
|
-
"@deepseek-ai/dsh-shell-env": {
|
|
79
|
-
"optional": true
|
|
80
|
-
},
|
|
81
|
-
"@deepseek-ai/dsh-subagent": {
|
|
82
|
-
"optional": true
|
|
83
|
-
},
|
|
84
|
-
"@deepseek-ai/dsh-tools": {
|
|
85
|
-
"optional": true
|
|
86
|
-
},
|
|
82
|
+
"@deepseek-ai/dsh-shell-env": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"@deepseek-ai/dsh-subagent": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@deepseek-ai/dsh-tools": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
87
91
|
"@deepseek-ai/schemastery": {
|
|
88
92
|
"optional": true
|
|
89
93
|
}
|
|
90
|
-
},
|
|
94
|
+
},
|
|
95
|
+
"dependencies": {
|
|
96
|
+
"zod": "^4.4.3"
|
|
97
|
+
},
|
|
91
98
|
"devDependencies": {
|
|
92
99
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
93
|
-
"@deepseek-ai/dsh-
|
|
94
|
-
"@deepseek-ai/dsh-
|
|
95
|
-
"@deepseek-ai/dsh-client-locale": "0.1.5-
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-
|
|
98
|
-
"@deepseek-ai/dsh-code-runtime": "0.1.5-
|
|
99
|
-
"@deepseek-ai/dsh-commands": "0.1.5-
|
|
100
|
-
"@deepseek-ai/dsh-home-paths": "0.1.5-
|
|
101
|
-
"@deepseek-ai/dsh-invariants": "0.1.5-
|
|
102
|
-
"@deepseek-ai/dsh-llm": "0.1.5-
|
|
103
|
-
"@deepseek-ai/dsh-sandbox": "0.1.5-
|
|
104
|
-
"@deepseek-ai/dsh-scope": "0.1.5-
|
|
105
|
-
"@deepseek-ai/dsh-session": "0.1.5-
|
|
106
|
-
"@deepseek-ai/dsh-session-persistence": "0.1.5-
|
|
107
|
-
"@deepseek-ai/dsh-session-projection": "0.1.5-
|
|
108
|
-
"@deepseek-ai/dsh-
|
|
109
|
-
"@deepseek-ai/dsh-
|
|
110
|
-
"@deepseek-ai/dsh-shell-env": "0.1.5-
|
|
111
|
-
"@deepseek-ai/dsh-
|
|
112
|
-
"@deepseek-ai/dsh-
|
|
113
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.5-
|
|
114
|
-
"@deepseek-ai/dsh-tools": "0.1.5-
|
|
115
|
-
"@deepseek-ai/dsh-user-approval": "0.1.5-
|
|
116
|
-
"@deepseek-ai/dsh-util-time": "0.1.5-
|
|
100
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
104
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
|
|
105
|
+
"@deepseek-ai/dsh-code-runtime": "0.1.5-rc.2",
|
|
106
|
+
"@deepseek-ai/dsh-commands": "0.1.5-rc.2",
|
|
107
|
+
"@deepseek-ai/dsh-home-paths": "0.1.5-rc.2",
|
|
108
|
+
"@deepseek-ai/dsh-invariants": "0.1.5-rc.2",
|
|
109
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
110
|
+
"@deepseek-ai/dsh-sandbox": "0.1.5-rc.2",
|
|
111
|
+
"@deepseek-ai/dsh-scope": "0.1.5-rc.2",
|
|
112
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
113
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.5-rc.2",
|
|
114
|
+
"@deepseek-ai/dsh-session-projection": "0.1.5-rc.2",
|
|
115
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
116
|
+
"@deepseek-ai/dsh-shell": "0.1.5-rc.2",
|
|
117
|
+
"@deepseek-ai/dsh-shell-env": "0.1.5-rc.2",
|
|
118
|
+
"@deepseek-ai/dsh-subagent": "0.1.5-rc.2",
|
|
119
|
+
"@deepseek-ai/dsh-subprocess": "0.1.5-rc.2",
|
|
120
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
121
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
122
|
+
"@deepseek-ai/dsh-user-approval": "0.1.5-rc.2",
|
|
123
|
+
"@deepseek-ai/dsh-util-time": "0.1.5-rc.2",
|
|
117
124
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
118
125
|
"react": "19.2.8",
|
|
119
|
-
"tsdown": "^0.22.2"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
126
|
+
"tsdown": "^0.22.2"
|
|
127
|
+
}
|
|
128
|
+
}
|