Bundle
@remote-task/remote-task
HTTP session plugin for deepseek-harness: create sessions, drive LLM dialogue with a chosen model and skills, query status, and pause/stop/restore.
- Source
- yixiuzhemu
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 23 days ago
Readme
# Remote-Task
> 面向 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)(dsh,基于 [Cordis](https://cordis.js.org))的 **HTTP 会话插件**:通过 HTTP 远程创建任务会话并**立即返回 `sessionId`**,把 prompt 送入大模型对话;模型可指定、Skills 按 id 集合注入;支持状态查询与暂停 / 停止 / 恢复,并沉淀**工作区级跨会话记忆**。
完整设计见 [`DESIGN.md`](./DESIGN.md)。本文件是速览摘要。
---
## 一、核心能力
- **异步会话**:`POST /sessions` 校验并投递首个 prompt 后立即返回 `sessionId`,客户端轮询状态。
- **模型路由**:按参数指定 `provider / model / reasoningEffort / maxTokens`,缺省回落部署默认。
- **Skills by id**:传入 skill id 集合,经 `ctx.skills` 解析后注入会话;未知 id 直接失败(fails loud)。
- **生命周期**:暂停(保留待处理 inbox,可续跑)、停止(取消 → flush 持久化 → dispose)、恢复(从持久化日志重建 Agent)。
- **状态投影**:订阅 `session/event` 与 `agent/*` 事件实时维护快照,`GET status` 直接读快照,非轮询。
- **工作区记忆**:会话结束 / idle 时增量提炼结构化记忆,跨会话注入同一工作区的后续任务(见第四节)。
## 二、架构概览
Host 单面插件(无 client UI),挂载于 dsh 的 **web profile**(该 profile 才拥有 `ctx.webServer`)。
- 一个会话一个 `RemoteTaskSession` 实例,独占其 `AgentHandle`、prompt 准入槽、状态投影与幂等 teardown(对齐官方 `AcpSession` 的所有权模型)。
- HTTP 层(`src/http/*`)与会话内核(`src/session.ts`)解耦:Router 只做协议解析 / 校验 / 错误码。
- 路由注册、事件订阅、会话创建 / 恢复全部走 `ctx.effect()` / `ctx.on()`("Registrations are effects")。
```
src/
├── index.ts # 插件入口:name / inject / Config / apply
├── config.ts # Config schema(schemastery)+ 校验
├── types.ts # 对外 wire 类型 + Cordis 事件声明合并
├── http/{router,body,respond}.ts # prefix 路由 / 有界 body / 统一响应
├── registry.ts # SessionRegistry:id→session + 事件按精确所有权路由
├── session.ts # RemoteTaskSession:Agent 所有权 + 准入 + 暂停/停止/恢复 + 记忆蒸馏/注入
├── model.ts # 模型路由解析 / 校验
├── skills.ts # skill by id 解析与注入
├── workspace.ts # 工作区列举/创建/删除 + 记忆 sidecar 绑定
└── workspaceMemory.ts # 结构化记忆 sidecar:读写 / 去重合并 / 预算检索 / 原子落盘
```
## 三、HTTP API 摘要
统一 prefix 路由 `/remote-task`(默认端口 `127.0.0.1:3080`):
| 方法 | 路径 | 作用 |
|---|---|---|
| `POST` | `/sessions` | 创建会话并投递首个 prompt(异步),返回 `201 { sessionId }` |
| `GET` | `/sessions/:id/status` | 查询会话状态快照 |
| `POST` | `/sessions/:id/prompt` | 追加对话轮次(异步),返回 `202` |
| `POST` | `/sessions/:id/pause` | 暂停:中止当前轮次,保留待处理输入 |
| `POST` | `/sessions/:id/resume-turn` | 续跑:唤醒驱动处理保留的 inbox |
| `POST` | `/sessions/:id/stop` | 停止:取消 → flush → dispose(保留持久化日志)|
| `POST` | `/sessions/:id/restore` | 恢复:从持久化日志重建已停止的会话 |
| `GET` | `/sessions/:id/messages` | 拉取转录(可选)|
| `GET` | `/sessions` | 列出活跃会话(可选)|
| `GET` | `/workspaces` | 列出工作区(含记忆绑定用的 id / path)|
| `POST` | `/workspaces` | 创建 / 复用工作区,可带 `goal`(保留既有记忆)|
| `DELETE` | `/workspaces/:id` | 删除工作区注册并移除其 sidecar 记忆文件 |
| `GET` | `/health` | 健康检查 |
错误码:`400` 参数非法 · `401` 鉴权失败 · `404` 不存在 · `405` 方法不允许 · `409` 状态冲突 / prompt 在途 · `413` body 超限 · `415` 非 JSON · `503` 依赖服务不可用。日志永不输出鉴权凭据,prompt 仅必要时脱敏。
## 四、工作区记忆(结构化 · 增量 · 原生注入)
落在宿主 Workspace 实体之外的 sidecar:`~/.dsh/remote-task/workspaces/{workspaceId}.json`。
```jsonc
{
"goal": "工作区任务架构目标(长期常驻,注入用)",
"entries": [ // 去重后的原子记忆条目,按时间升序
{ "id": "内容寻址(sha1)前16位", "kind": "decision|fact|todo|insight",
"content": "独立可复用的简洁陈述", "tags": ["关键词"],
"sessionId": "溯源会话", "createdAt": "ISO-8601" }
],
"cursors": { "<sessionId>": 123 }, // 每会话已提炼的转录行数(增量 checkpoint)
"updatedAt": "ISO-8601"
}
```
- **增量总结**:`distillToWorkspaceMemory` 只处理 cursor 之后的新转录;LLM 输出结构化 JSON,畸形响应回退为单条 `insight`;`mergeEntries` 按内容寻址 id 去重,上限 400 条;慢速 LLM 调用在锁外并行,`load→merge→save` 在 `withWorkspaceSidecarLock` 内串行。
- **原生注入**:经 `agent.inject()` 走 model-facing context 通道(`source.kind !== 'user'`),因此旧记忆被转录读取自动排除、**永不进入再总结**,也不污染用户 prompt。goal 与记忆各有独立水位线,长会话能感知运行中被 `POST /workspaces` 更新的架构目标。
- **触发时机**:宿主无「会话结束」事件,故总结在 `stop()` 时必做;开启 `autoDistillOnIdle` 后每轮回到 idle 也增量总结(cursor 幂等,不与 stop 重复)。
- **落盘安全**:临时文件 + `rename` 原子替换;进程内锁仅适用单宿主进程,多进程部署需改文件锁。
- **向后兼容**:旧的 `{ memory: string }` 单块格式在加载时自动迁移为一条 `insight`。
## 五、配置项(`cordis.patch.yml` 的 `config`)
| 字段 | 默认 | 说明 |
|---|---|---|
| `routePrefix` | `/remote-task` | HTTP 路由前缀(非根、无尾斜杠),加载时校验 |
| `defaultProvider` / `defaultModel` | `deepseek` / `deepseek-chat` | 请求未指定模型时的默认路由 |
| `authTokenEnv` | `''`(禁用)| `credential-ref`,指向 Bearer token 凭据,支持轮换 |
| `maxBodyBytes` | `1048576` | 正整数 body 上限 |
| `defaultCwd` | `process.cwd()` | 未传 workspace 时的工作目录 |
| `autoDistillOnIdle` | `false` | 每轮回到 idle 即增量总结记忆(本仓库 `cordis.patch.yml` 已置 `true`)|
| `memoryInjectionBudget` | `2000` | 每轮注入记忆的字符预算,配合优先级 / 新近度检索式选择 |
安全:webServer 无内置 TLS / 鉴权,默认 loopback,生产置于 TLS 反向代理之后;插件自身通过 `authTokenEnv` 强制 Bearer 鉴权。
## 六、构建 / 打包 / 安装 / 生效
> ⚠️ **关键**:`dsh web` 从 **`~/.dsh/profiles/web/`**(自带独立 `package.json` + `node_modules`)加载插件,这与宿主 monorepo 的 `node_modules/.pnpm`、`packages/bundle/web-app`、`apps/desktop/.desktop-build` 是**完全分离的多套解析根**。让新版本生效**必须在 profile 目录操作**。
```sh
# ① 构建与打包(工作区根目录;PowerShell 用 npm.cmd / pnpm.cmd)
pnpm install
npm run build # = node scripts/clean.mjs && tsc -p tsconfig.json(出 lib + lib/types)
npm pack # prepack 自动 build + preflight;产出 remote-task-remote-task-<version>.tgz
# ② 把 web profile 的 file: 指针指向新 tgz(编辑 ~/.dsh/profiles/web/package.json)
# "@remote-task/remote-task": "file:<绝对路径>/remote-task-remote-task-<version>.tgz"
# 并确保 dsh.profile.bundles 含 "@remote-task/remote-task"
# ③ 在 profile 目录重装(只 pack 不 install 永不生效)
pnpm -C ~/.dsh/profiles/web install
# ④ 重启宿主(patchReload:live 只热更配置,不热更 node_modules)
pnpm dsh web
```
**生效判据**(反证运行的是哪份代码):
1. `~/.dsh/profiles/web/node_modules/@remote-task/remote-task/package.json` 的 `version` = 新版本;
2. 新建带 `goal` 的工作区后,`~/.dsh/remote-task/workspaces/<id>.json` 为 `{ goal, entries:[], cursors:{}, updatedAt }`(旧版是 `{ goal, memory:"", updatedAt }`);
3. 会话 `stop` / idle 后日志出现 `remote-task: distill start …` / `distill saved …`。
**头号陷阱**:只 `npm pack`、或只改宿主仓库的 junction / `.pnpm` 副本,都**不会**让 web 端生效——运行时仍跑 profile `node_modules` 里的旧副本。
## 七、快速调用示例
```sh
# 创建会话(异步,立即返回 sessionId)
curl -X POST http://127.0.0.1:3080/remote-task/sessions \
-H 'content-type: application/json' \
-d '{"prompt":"总结这个仓库","model":{"provider":"deepseek","model":"deepseek-chat"},"skills":["commit"]}'
# → 201 {"sessionId":"..."}
# 查询状态 / 暂停 / 续跑 / 停止 / 恢复
curl http://127.0.0.1:3080/remote-task/sessions/<id>/status
curl -X POST http://127.0.0.1:3080/remote-task/sessions/<id>/pause
curl -X POST http://127.0.0.1:3080/remote-task/sessions/<id>/resume-turn
curl -X POST http://127.0.0.1:3080/remote-task/sessions/<id>/stop
curl -X POST http://127.0.0.1:3080/remote-task/sessions/<id>/restore
```
## 八、环境要求
- Node.js `^22.19.0 || >=24.0.0`,包管理器 `pnpm@11.7.0`
- 宿主提供全部 `@deepseek-ai/dsh-*` 依赖(peerDependencies):agent / agent-loop / host-webserver / llm / session / session-persistence / skill / workspace / brand / credentials,以及 cordis、schemastery
- Windows / PowerShell 注意:命令连接用 `;`(不支持 `&&`);npm/pnpm 用 `npm.cmd` / `pnpm.cmd`
## 许可
MIT
Install
dsh plugin --profile web add github:yixiuzhemu/Remote-Task
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install remote-task-remote-task from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.