mirror of
https://github.com/TommyFang2077/dsh-desktop.git
synced 2026-08-17 09:06:36 +08:00
fix: ship a loadable AgentRQ bundle that stays idle without a workspace URL
The first integration copied sources without lib/, skipped packaging paths, and could fail the default web profile when url was unset. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
7
Makefile
7
Makefile
@@ -4,6 +4,7 @@ PREFIX ?= $(HOME)/.local
|
|||||||
APP_ID := io.github.tommyfang.DshDesktop
|
APP_ID := io.github.tommyfang.DshDesktop
|
||||||
DSH_VERSION := 0.1.0-rc.6
|
DSH_VERSION := 0.1.0-rc.6
|
||||||
MODLENS_VERSION := 3.16.6
|
MODLENS_VERSION := 3.16.6
|
||||||
|
AGENTRQ_VERSION := 0.2.1
|
||||||
ANCHORED_COMMIT := ffb845c5480adc953392a6db6f8a98ede621174b
|
ANCHORED_COMMIT := ffb845c5480adc953392a6db6f8a98ede621174b
|
||||||
ANCHORED_REPO := https://github.com/xiaobright/dsh-anchored-standard.git
|
ANCHORED_REPO := https://github.com/xiaobright/dsh-anchored-standard.git
|
||||||
VENDOR_DIR := vendor/dsh-prefix
|
VENDOR_DIR := vendor/dsh-prefix
|
||||||
@@ -84,6 +85,12 @@ install: build
|
|||||||
mkdir -p $(DESTDIR)$(PREFIX)/share/dsh-desktop; \
|
mkdir -p $(DESTDIR)$(PREFIX)/share/dsh-desktop; \
|
||||||
cp -R plugins/dsh-desktop-vision $(DESTDIR)$(PREFIX)/share/dsh-desktop/vision; \
|
cp -R plugins/dsh-desktop-vision $(DESTDIR)$(PREFIX)/share/dsh-desktop/vision; \
|
||||||
fi
|
fi
|
||||||
|
if [ -f plugins/agentrq/lib/index.js ]; then \
|
||||||
|
rm -rf $(DESTDIR)$(PREFIX)/share/dsh-desktop/agentrq; \
|
||||||
|
mkdir -p $(DESTDIR)$(PREFIX)/share/dsh-desktop/agentrq/lib; \
|
||||||
|
cp plugins/agentrq/package.json plugins/agentrq/cordis.patch.yml plugins/agentrq/LICENSE plugins/agentrq/README.md $(DESTDIR)$(PREFIX)/share/dsh-desktop/agentrq/; \
|
||||||
|
cp -R plugins/agentrq/lib/. $(DESTDIR)$(PREFIX)/share/dsh-desktop/agentrq/lib/; \
|
||||||
|
fi
|
||||||
if [ -f $(ANCHORED_DIR)/preset.yml ]; then \
|
if [ -f $(ANCHORED_DIR)/preset.yml ]; then \
|
||||||
rm -rf $(DESTDIR)$(PREFIX)/share/dsh-desktop/anchored-standard; \
|
rm -rf $(DESTDIR)$(PREFIX)/share/dsh-desktop/anchored-standard; \
|
||||||
mkdir -p $(DESTDIR)$(PREFIX)/share/dsh-desktop; \
|
mkdir -p $(DESTDIR)$(PREFIX)/share/dsh-desktop; \
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -147,21 +147,26 @@ npx -y @deepseek-ai/dsh plugin --profile web add @liustack/modlens@3.16.6
|
|||||||
| Claude CLI | 本机 CLI,无需填 URL | [code.claude.com](https://code.claude.com) |
|
| Claude CLI | 本机 CLI,无需填 URL | [code.claude.com](https://code.claude.com) |
|
||||||
|
|
||||||
外链在系统浏览器中打开(Tauri `on_navigation`),密钥只写在本机 ModLens 配置里。
|
外链在系统浏览器中打开(Tauri `on_navigation`),密钥只写在本机 ModLens 配置里。
|
||||||
|
|
||||||
### 4. AgentRQ 任务管理器插件(`agentrq`)
|
### 4. AgentRQ 任务管理器插件(`agentrq`)
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| 路径 | [`plugins/agentrq/`](plugins/agentrq/) |
|
| 路径 | [`plugins/agentrq/`](plugins/agentrq/) |
|
||||||
| 上游 | [agentrq/agentrq](https://github.com/agentrq/agentrq) |
|
| 上游 | [agentrq/agentrq](https://github.com/agentrq/agentrq) |
|
||||||
| 版本 | `0.2.1` |
|
| 版本 | `0.2.1`(钉在 [Makefile](Makefile) 的 `AGENTRQ_VERSION`) |
|
||||||
| 许可证 | Apache 2.0(AgentRQ)· 与本仓库相同(MIT) |
|
| 许可证 | Apache-2.0 · [docs/licenses/agentrq.LICENSE](docs/licenses/agentrq.LICENSE) |
|
||||||
| 作用 | 让 DeepSeek Harness 直接管理 AgentRQ 任务:创建、获取、更新状态、回复、获取工作区信息等。支持实时推送任务,无需离开 Harness |
|
| 作用 | 让 DeepSeek Harness 直接管理 AgentRQ 任务:创建、获取、更新状态、回复、获取工作区信息等。支持实时推送任务,无需离开 Harness |
|
||||||
|
| 安装位置 | 启动时复制到 `~/.dsh/profiles/web/node_modules/agentrq` |
|
||||||
|
|
||||||
**AgentRQ** 是一个人类在环的任务管理器——你可以在 AgentRQ 工作区中给 Agent 分配任务,这个插件让 Harness 直接接收任务并执行,完成任务后更新状态。
|
**AgentRQ** 是一个人类在环的任务管理器——你可以在 AgentRQ 工作区中给 Agent 分配任务,这个插件让 Harness 直接接收任务并执行,完成任务后更新状态。
|
||||||
|
|
||||||
安装后在 profile 的 `cordis.patch.yml` 中配置 AgentRQ workspace endpoint 即可使用。
|
未配置 endpoint 时插件保持空闲,不影响 ModLens 和其它内置插件。配置任一即可启用:
|
||||||
|
|
||||||
### 6. 零工具锚定式标准预设
|
- 环境变量 `AGENTRQ_WORKSPACE_MCP_URL`(含 `?token=`)
|
||||||
|
- 或在 profile 的 `cordis.patch.yml` 里写 `url`
|
||||||
|
|
||||||
|
详见 [`plugins/agentrq/README.md`](plugins/agentrq/README.md)。
|
||||||
|
|
||||||
### 5. Anchored Standard 预设
|
### 5. Anchored Standard 预设
|
||||||
|
|
||||||
@@ -238,6 +243,7 @@ dsh-desktop/
|
|||||||
├── src-tauri/ # Tauri 窗口、命令、deb/rpm/nsis/dmg
|
├── src-tauri/ # Tauri 窗口、命令、deb/rpm/nsis/dmg
|
||||||
├── crates/dsh-core/ # 启动 / 更新 / ModLens / 预设 / 剪贴板
|
├── crates/dsh-core/ # 启动 / 更新 / ModLens / 预设 / 剪贴板
|
||||||
├── plugins/dsh-desktop-vision/ # 设置 → 视觉模型
|
├── plugins/dsh-desktop-vision/ # 设置 → 视觉模型
|
||||||
|
├── plugins/agentrq/ # AgentRQ 任务管理器
|
||||||
├── data/ # .desktop、图标、AppStream
|
├── data/ # .desktop、图标、AppStream
|
||||||
├── flatpak/
|
├── flatpak/
|
||||||
├── docs/screenshots/ # README 截图
|
├── docs/screenshots/ # README 截图
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ shell around other projects. Those projects keep their own copyright and
|
|||||||
license. Copies of the relevant texts live in `docs/licenses/`.
|
license. Copies of the relevant texts live in `docs/licenses/`.
|
||||||
|
|
||||||
This project is **not** affiliated with, endorsed by, or maintained by
|
This project is **not** affiliated with, endorsed by, or maintained by
|
||||||
DeepSeek, liustack, or xiaobright.
|
DeepSeek, liustack, xiaobright, or AgentRQ.
|
||||||
|
|
||||||
## Bundled or launched at runtime
|
## Bundled or launched at runtime
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ DeepSeek, liustack, or xiaobright.
|
|||||||
| ModLens (`@liustack/modlens`) | [liustack/modlens](https://github.com/liustack/modlens) | `3.16.6` | MIT, © 2026 Leon Liu (liustack) | Copied into `~/.dsh/profiles/web` so text-only models can read images. See `docs/licenses/modlens.LICENSE`. |
|
| ModLens (`@liustack/modlens`) | [liustack/modlens](https://github.com/liustack/modlens) | `3.16.6` | MIT, © 2026 Leon Liu (liustack) | Copied into `~/.dsh/profiles/web` so text-only models can read images. See `docs/licenses/modlens.LICENSE`. |
|
||||||
| Anchored Standard | [xiaobright/dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard) | commit `ffb845c5480adc953392a6db6f8a98ede621174b` | MIT, © 2026 xiaobright; portions © 2026 DeepSeek | Localized as **锚定式标准(实验)** and **零工具锚定式标准(实验)**, written to `~/.dsh/.agent-presets/`. See `docs/licenses/dsh-anchored-standard.LICENSE` and `.NOTICE`. |
|
| Anchored Standard | [xiaobright/dsh-anchored-standard](https://github.com/xiaobright/dsh-anchored-standard) | commit `ffb845c5480adc953392a6db6f8a98ede621174b` | MIT, © 2026 xiaobright; portions © 2026 DeepSeek | Localized as **锚定式标准(实验)** and **零工具锚定式标准(实验)**, written to `~/.dsh/.agent-presets/`. See `docs/licenses/dsh-anchored-standard.LICENSE` and `.NOTICE`. |
|
||||||
| `dsh-desktop-vision` | this repo `plugins/dsh-desktop-vision/` | `0.1.4` | MIT, © 2026 TommyFang2077 | Settings page **设置 → 视觉模型**; writes `~/.modlens/config.json`. |
|
| `dsh-desktop-vision` | this repo `plugins/dsh-desktop-vision/` | `0.1.4` | MIT, © 2026 TommyFang2077 | Settings page **设置 → 视觉模型**; writes `~/.modlens/config.json`. |
|
||||||
|
| AgentRQ (`agentrq`) | [agentrq/agentrq](https://github.com/agentrq/agentrq) | `0.2.1` | Apache-2.0 | Copied into `~/.dsh/profiles/web` so Harness can manage AgentRQ tasks. Idle until `AGENTRQ_WORKSPACE_MCP_URL` or the profile `url` is set. See `docs/licenses/agentrq.LICENSE`. |
|
||||||
|
|
||||||
The Anchored Standard NOTICE records that the presets adapt the DeepSeek
|
The Anchored Standard NOTICE records that the presets adapt the DeepSeek
|
||||||
Harness Standard agent preset from
|
Harness Standard agent preset from
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ use crate::paths::{copy_tree, dsh_home, replace_symlink, BundledPaths};
|
|||||||
pub const PACKAGE: &str = "@liustack/modlens";
|
pub const PACKAGE: &str = "@liustack/modlens";
|
||||||
pub const VISION_PACKAGE: &str = "dsh-desktop-vision";
|
pub const VISION_PACKAGE: &str = "dsh-desktop-vision";
|
||||||
pub const MODLENS_VERSION: &str = "3.16.6";
|
pub const MODLENS_VERSION: &str = "3.16.6";
|
||||||
|
pub const HIDE_PLAIN_TWINS_JS: &str = include_str!("../../../ui/inject/hide-twins.js");
|
||||||
pub const AGENTRQ_PACKAGE: &str = "agentrq";
|
pub const AGENTRQ_PACKAGE: &str = "agentrq";
|
||||||
|
pub const AGENTRQ_VERSION: &str = "0.2.1";
|
||||||
|
|
||||||
|
const AGENTRQ_PACKAGE_FILES: &[&str] =
|
||||||
|
&["package.json", "cordis.patch.yml", "LICENSE", "README.md"];
|
||||||
|
|
||||||
pub const MANAGED_OVERLAY: &str = "\
|
pub const MANAGED_OVERLAY: &str = "\
|
||||||
# dsh-desktop manages this modlens overlay (wrap every text-only model).
|
# dsh-desktop manages this modlens overlay (wrap every text-only model).
|
||||||
@@ -55,9 +60,12 @@ pub fn bundled_vision_plugin(paths: &BundledPaths) -> Option<PathBuf> {
|
|||||||
.or_else(|| paths.find_dir("plugins/dsh-desktop-vision", "package.json"))
|
.or_else(|| paths.find_dir("plugins/dsh-desktop-vision", "package.json"))
|
||||||
.filter(|p| p.join("client.js").is_file())
|
.filter(|p| p.join("client.js").is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bundled_agentrq_plugin(paths: &BundledPaths) -> Option<PathBuf> {
|
pub fn bundled_agentrq_plugin(paths: &BundledPaths) -> Option<PathBuf> {
|
||||||
paths.find_dir("agentrq", "package.json")
|
paths
|
||||||
|
.find_dir("agentrq", "package.json")
|
||||||
.or_else(|| paths.find_dir("plugins/agentrq", "package.json"))
|
.or_else(|| paths.find_dir("plugins/agentrq", "package.json"))
|
||||||
|
.filter(|p| p.join("lib/index.js").is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bundled_modlens_prefix(paths: &BundledPaths) -> Option<PathBuf> {
|
pub fn bundled_modlens_prefix(paths: &BundledPaths) -> Option<PathBuf> {
|
||||||
@@ -65,18 +73,38 @@ pub fn bundled_modlens_prefix(paths: &BundledPaths) -> Option<PathBuf> {
|
|||||||
.find_dir("modlens", "node_modules/@liustack/modlens")
|
.find_dir("modlens", "node_modules/@liustack/modlens")
|
||||||
.or_else(|| paths.find_dir("vendor/modlens", "node_modules/@liustack/modlens"))
|
.or_else(|| paths.find_dir("vendor/modlens", "node_modules/@liustack/modlens"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_agentrq_plugin(paths: &BundledPaths, profile: &Path) -> bool {
|
fn install_agentrq_plugin(paths: &BundledPaths, profile: &Path) -> bool {
|
||||||
let Some(src) = bundled_agentrq_plugin(paths) else {
|
let Some(src) = bundled_agentrq_plugin(paths) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let dest = profile.join("node_modules").join(AGENTRQ_PACKAGE);
|
let dest = profile.join("node_modules").join(AGENTRQ_PACKAGE);
|
||||||
if let Err(e) = copy_tree(&src, &dest, true) {
|
let up_to_date =
|
||||||
eprintln!("Failed to install agentrq plugin: {e}");
|
dest.join("lib/index.js").is_file() && read_pkg_version(&dest) == read_pkg_version(&src);
|
||||||
|
if !up_to_date && copy_agentrq_package(&src, &dest).is_err() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
let fallback = dsh_home()
|
||||||
|
.join("profiles/node_modules")
|
||||||
|
.join(AGENTRQ_PACKAGE);
|
||||||
|
let _ = replace_symlink(&fallback, &dest);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_agentrq_package(src: &Path, dest: &Path) -> std::io::Result<()> {
|
||||||
|
if dest.exists() {
|
||||||
|
std::fs::remove_dir_all(dest)?;
|
||||||
|
}
|
||||||
|
std::fs::create_dir_all(dest)?;
|
||||||
|
for name in AGENTRQ_PACKAGE_FILES {
|
||||||
|
let from = src.join(name);
|
||||||
|
if from.is_file() {
|
||||||
|
std::fs::copy(&from, dest.join(name))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy_tree(&src.join("lib"), &dest.join("lib"), true)
|
||||||
|
}
|
||||||
|
|
||||||
fn install_into_profile(src_prefix: &Path, profile: &Path) -> std::io::Result<()> {
|
fn install_into_profile(src_prefix: &Path, profile: &Path) -> std::io::Result<()> {
|
||||||
let dest_pkg = package_dir(profile);
|
let dest_pkg = package_dir(profile);
|
||||||
copy_tree(&package_dir(src_prefix), &dest_pkg, true)?;
|
copy_tree(&package_dir(src_prefix), &dest_pkg, true)?;
|
||||||
@@ -112,7 +140,9 @@ fn install_vision_plugin(paths: &BundledPaths, profile: &Path) -> bool {
|
|||||||
if !up_to_date && copy_tree(&src, &dest, true).is_err() {
|
if !up_to_date && copy_tree(&src, &dest, true).is_err() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let fallback = dsh_home().join("profiles/node_modules").join(VISION_PACKAGE);
|
let fallback = dsh_home()
|
||||||
|
.join("profiles/node_modules")
|
||||||
|
.join(VISION_PACKAGE);
|
||||||
let _ = replace_symlink(&fallback, &dest);
|
let _ = replace_symlink(&fallback, &dest);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -334,6 +364,7 @@ fn ensure_modlens_inner(
|
|||||||
installed: Option<String>,
|
installed: Option<String>,
|
||||||
version: &str,
|
version: &str,
|
||||||
) -> std::io::Result<ModlensEnsureResult> {
|
) -> std::io::Result<ModlensEnsureResult> {
|
||||||
|
std::fs::create_dir_all(profile)?;
|
||||||
let vision_ok = install_vision_plugin(paths, profile);
|
let vision_ok = install_vision_plugin(paths, profile);
|
||||||
let agentrq_ok = install_agentrq_plugin(paths, profile);
|
let agentrq_ok = install_agentrq_plugin(paths, profile);
|
||||||
let mut packages = BTreeMap::new();
|
let mut packages = BTreeMap::new();
|
||||||
@@ -341,7 +372,10 @@ fn ensure_modlens_inner(
|
|||||||
packages.insert(VISION_PACKAGE.to_string(), "0.1.0".into());
|
packages.insert(VISION_PACKAGE.to_string(), "0.1.0".into());
|
||||||
}
|
}
|
||||||
if agentrq_ok {
|
if agentrq_ok {
|
||||||
packages.insert(AGENTRQ_PACKAGE.to_string(), "0.2.1".into());
|
let version = bundled_agentrq_plugin(paths)
|
||||||
|
.and_then(|p| read_pkg_version(&p))
|
||||||
|
.unwrap_or_else(|| AGENTRQ_VERSION.to_string());
|
||||||
|
packages.insert(AGENTRQ_PACKAGE.to_string(), version);
|
||||||
}
|
}
|
||||||
if src.is_none() && installed.is_none() {
|
if src.is_none() && installed.is_none() {
|
||||||
if !packages.is_empty() {
|
if !packages.is_empty() {
|
||||||
@@ -449,7 +483,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn keeps_other_entries() {
|
fn keeps_other_entries() {
|
||||||
let original = "- id: other\n config:\n x: 1\n- id: modlens\n config:\n autoRead: false\n";
|
let original =
|
||||||
|
"- id: other\n config:\n x: 1\n- id: modlens\n config:\n autoRead: false\n";
|
||||||
let text = ensure_modlens_overlay(original);
|
let text = ensure_modlens_overlay(original);
|
||||||
assert!(text.contains("id: other"));
|
assert!(text.contains("id: other"));
|
||||||
assert!(text.contains("autoRead: true"));
|
assert!(text.contains("autoRead: true"));
|
||||||
@@ -479,7 +514,8 @@ ui-theme:
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn already_wrapped_is_left_alone() {
|
fn already_wrapped_is_left_alone() {
|
||||||
let original = "agent-default-model:\n provider: deepseek-modlens\n model: deepseek-v4-pro\n";
|
let original =
|
||||||
|
"agent-default-model:\n provider: deepseek-modlens\n model: deepseek-v4-pro\n";
|
||||||
assert_eq!(remap_default_text_model(original), original);
|
assert_eq!(remap_default_text_model(original), original);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,4 +524,44 @@ ui-theme:
|
|||||||
assert!(HIDE_PLAIN_TWINS_JS.contains("(modlens vision)"));
|
assert!(HIDE_PLAIN_TWINS_JS.contains("(modlens vision)"));
|
||||||
assert!(HIDE_PLAIN_TWINS_JS.contains("MutationObserver"));
|
assert!(HIDE_PLAIN_TWINS_JS.contains("MutationObserver"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn agentrq_bundle_requires_built_entry() {
|
||||||
|
let root = tempfile::tempdir().unwrap();
|
||||||
|
let src = root.path().join("agentrq");
|
||||||
|
std::fs::create_dir_all(&src).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
src.join("package.json"),
|
||||||
|
r#"{"name":"agentrq","version":"0.2.1"}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let paths = BundledPaths::default().with_resource_dir(root.path().to_path_buf());
|
||||||
|
assert!(bundled_agentrq_plugin(&paths).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn agentrq_install_copies_package_not_sources() {
|
||||||
|
let root = tempfile::tempdir().unwrap();
|
||||||
|
let src = root.path().join("agentrq");
|
||||||
|
std::fs::create_dir_all(src.join("lib")).unwrap();
|
||||||
|
std::fs::create_dir_all(src.join("src")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
src.join("package.json"),
|
||||||
|
r#"{"name":"agentrq","version":"0.2.1"}"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
std::fs::write(src.join("lib/index.js"), "export const name = 'agentrq'\n").unwrap();
|
||||||
|
std::fs::write(src.join("src/index.ts"), "should not be copied\n").unwrap();
|
||||||
|
std::fs::write(src.join("cordis.patch.yml"), "- insert: []\n").unwrap();
|
||||||
|
|
||||||
|
let profile = tempfile::tempdir().unwrap();
|
||||||
|
let paths = BundledPaths::default().with_resource_dir(root.path().to_path_buf());
|
||||||
|
assert!(install_agentrq_plugin(&paths, profile.path()));
|
||||||
|
|
||||||
|
let dest = profile.path().join("node_modules/agentrq");
|
||||||
|
assert!(dest.join("lib/index.js").is_file());
|
||||||
|
assert!(dest.join("package.json").is_file());
|
||||||
|
assert!(dest.join("cordis.patch.yml").is_file());
|
||||||
|
assert!(!dest.join("src").exists());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pub fn is_flatpak() -> bool {
|
|||||||
Path::new("/.flatpak-info").exists()
|
Path::new("/.flatpak-info").exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Roots that may contain bundled ModLens / presets / vision plugin.
|
/// Roots that may contain bundled ModLens / presets / vision / AgentRQ plugins.
|
||||||
///
|
///
|
||||||
/// Search order: extra roots (Tauri resource dir), `$XDG_DATA_HOME/dsh-desktop`,
|
/// Search order: extra roots (Tauri resource dir), `$XDG_DATA_HOME/dsh-desktop`,
|
||||||
/// `/app/share/dsh-desktop`, `/usr/share/dsh-desktop`, `~/.local/share/dsh-desktop`,
|
/// `/app/share/dsh-desktop`, `/usr/share/dsh-desktop`, `~/.local/share/dsh-desktop`,
|
||||||
|
|||||||
201
docs/licenses/agentrq.LICENSE
Normal file
201
docs/licenses/agentrq.LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -55,6 +55,9 @@ modules:
|
|||||||
- install -Dm755 target/release/dsh-desktop ${FLATPAK_DEST}/bin/dsh-desktop
|
- install -Dm755 target/release/dsh-desktop ${FLATPAK_DEST}/bin/dsh-desktop
|
||||||
- mkdir -p ${FLATPAK_DEST}/share/dsh-desktop/vision
|
- mkdir -p ${FLATPAK_DEST}/share/dsh-desktop/vision
|
||||||
- cp -a plugins/dsh-desktop-vision/. ${FLATPAK_DEST}/share/dsh-desktop/vision
|
- cp -a plugins/dsh-desktop-vision/. ${FLATPAK_DEST}/share/dsh-desktop/vision
|
||||||
|
- mkdir -p ${FLATPAK_DEST}/share/dsh-desktop/agentrq/lib
|
||||||
|
- cp -a plugins/agentrq/package.json plugins/agentrq/cordis.patch.yml plugins/agentrq/LICENSE plugins/agentrq/README.md ${FLATPAK_DEST}/share/dsh-desktop/agentrq/
|
||||||
|
- cp -a plugins/agentrq/lib/. ${FLATPAK_DEST}/share/dsh-desktop/agentrq/lib
|
||||||
- install -Dm644 data/applications/io.github.tommyfang.DshDesktop.desktop ${FLATPAK_DEST}/share/applications/io.github.tommyfang.DshDesktop.desktop
|
- install -Dm644 data/applications/io.github.tommyfang.DshDesktop.desktop ${FLATPAK_DEST}/share/applications/io.github.tommyfang.DshDesktop.desktop
|
||||||
- install -Dm644 data/metainfo/io.github.tommyfang.DshDesktop.metainfo.xml ${FLATPAK_DEST}/share/metainfo/io.github.tommyfang.DshDesktop.metainfo.xml
|
- install -Dm644 data/metainfo/io.github.tommyfang.DshDesktop.metainfo.xml ${FLATPAK_DEST}/share/metainfo/io.github.tommyfang.DshDesktop.metainfo.xml
|
||||||
- for size in 16 24 32 48 64 128 256 512; do install -Dm644 data/icons/hicolor/${size}x${size}/apps/io.github.tommyfang.DshDesktop.png ${FLATPAK_DEST}/share/icons/hicolor/${size}x${size}/apps/io.github.tommyfang.DshDesktop.png; done
|
- for size in 16 24 32 48 64 128 256 512; do install -Dm644 data/icons/hicolor/${size}x${size}/apps/io.github.tommyfang.DshDesktop.png ${FLATPAK_DEST}/share/icons/hicolor/${size}x${size}/apps/io.github.tommyfang.DshDesktop.png; done
|
||||||
|
|||||||
1
plugins/agentrq/.gitignore
vendored
1
plugins/agentrq/.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
lib/
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
@@ -1,56 +1,29 @@
|
|||||||
# @agentrq/dsh-plugin-agentrq
|
# agentrq
|
||||||
|
|
||||||
AgentRQ task manager for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
AgentRQ task manager bundled with [DeepSeek Harness Desktop](https://github.com/TommyFang2077/dsh-desktop). Upstream: [agentrq/agentrq](https://github.com/agentrq/agentrq).
|
||||||
|
|
||||||
Create, manage, and automatically receive [AgentRQ](https://agentrq.com) tasks without leaving the harness. The bundle ships two rows: the workspace's tools bridged to the model, and the harness-side behavior a tool bridge cannot provide on its own — a supervised workspace session that delivers AgentRQ's pushes into the live agent, and the AgentRQ working agreement as a system-prompt section.
|
The desktop app copies this package into `~/.dsh/profiles/web/node_modules/agentrq` on startup. Without a workspace endpoint the plugin stays idle and does not affect other built-in plugins.
|
||||||
|
|
||||||
## Install
|
## Enable
|
||||||
|
|
||||||
**Requires pnpm.** `dsh plugin` is a thin forwarder to `pnpm` for every profile — installing any plugin, this one included, fails with `pnpm not found on PATH` unless pnpm is already installed (`npm install -g pnpm`, `corepack enable pnpm`, or `brew install pnpm`). This is a DeepSeek Harness CLI requirement, not something this plugin can opt out of.
|
Copy the workspace MCP URL from AgentRQ **Settings → Setup → DeepSeek Harness** (it already includes `?token=`), then either:
|
||||||
|
|
||||||
**One profile per workspace.** A profile serves one AgentRQ workspace and carries its own endpoint, so name it after the workspace rather than using `default` — that is what makes [several workspaces](#multiple-workspaces) work. Your workspace's **Settings → Setup → DeepSeek Harness** page prints every command and config block below already filled in.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsh plugin --profile agentrq-<workspace> add @agentrq/dsh-plugin-agentrq
|
export AGENTRQ_WORKSPACE_MCP_URL='https://<workspace>.mcp.agentrq.com/mcp?token=<token>'
|
||||||
```
|
```
|
||||||
|
|
||||||
Then pin this workspace's endpoint in the profile's own patch layer, `~/.dsh/profiles/agentrq-<workspace>/cordis.patch.yml`. Copy the URL from the Settings page — it already carries the `?token=` credential that authenticates a headless client:
|
or pin it in the profile patch, `~/.dsh/profiles/web/cordis.patch.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- id: agentrq
|
- id: agentrq
|
||||||
name: '@agentrq/dsh-plugin-agentrq'
|
name: agentrq
|
||||||
config:
|
config:
|
||||||
url: "https://<workspace>.mcp.agentrq.com/mcp?token=<token>"
|
url: "https://<workspace>.mcp.agentrq.com/mcp?token=<token>"
|
||||||
```
|
```
|
||||||
|
|
||||||
One row, one URL: the plugin mounts `@deepseek-ai/dsh-mcp-client` itself as a child fiber, so the endpoint is configured in exactly one place and the bridge shares this row's lifetime — disposal and HMR take it along.
|
dsh watches the profile patch, so an edit takes effect without a restart. Prefer the profile patch for interactive use: the environment variable is process-global.
|
||||||
|
|
||||||
```sh
|
**One profile per workspace.** A second workspace needs its own profile and its own `url`. Mounting this bundle twice in one profile collides on the `agentrq:protocol` section and the `agentrq_autopull` tool.
|
||||||
dsh --profile agentrq-<workspace> --dump-config # shows the bundle layer and your override
|
|
||||||
dsh --profile agentrq-<workspace>
|
|
||||||
```
|
|
||||||
|
|
||||||
The profile's patch is applied after every bundle layer, so those two rows win. dsh watches both `cordis.patch.yml` layers and reapplies valid edits transactionally, so changing the URL takes effect without a restart.
|
|
||||||
|
|
||||||
### Configuring without a file edit
|
|
||||||
|
|
||||||
The bundle's own patch defaults both rows to `!!js process.env.AGENTRQ_WORKSPACE_MCP_URL`, so a container or CI job can export the endpoint instead of writing a profile patch:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export AGENTRQ_WORKSPACE_MCP_URL='https://<workspace>.mcp.agentrq.com/mcp?token=<token>'
|
|
||||||
dsh --profile agentrq-<workspace>
|
|
||||||
```
|
|
||||||
|
|
||||||
Prefer the profile patch for an interactive install: an environment variable is process-global, so with one profile per workspace you have to remember the right `export` before each start, and the wrong one connects the wrong workspace without complaint. Supply neither and the row fails to load with the `url` field named — it is a required field, not a silent default.
|
|
||||||
|
|
||||||
Installing from a git checkout instead of the registry fetches sources rather than built artifacts, so pnpm must be allowed to run this package's `prepare` build. Add the allowance to your profile's `pnpm-workspace.yaml` and re-run the `add`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
allowBuilds:
|
|
||||||
'@agentrq/dsh-plugin-agentrq': true
|
|
||||||
```
|
|
||||||
|
|
||||||
That allowance is permission to execute this package's code on your machine at install time. Pin a commit (`github:agentrq/agentrq#<sha>`) if you take that route. Publishing to npm or shipping a `pnpm pack` tarball avoids the allowance entirely.
|
|
||||||
|
|
||||||
## What the model gets
|
## What the model gets
|
||||||
|
|
||||||
@@ -72,65 +45,13 @@ Plus one tool this package owns:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `agentrq_autopull` | `status`, `pause`, `resume`, or `pull_now` for this session's AgentRQ delivery |
|
| `agentrq_autopull` | `status`, `pause`, `resume`, or `pull_now` for this session's AgentRQ delivery |
|
||||||
|
|
||||||
`pull_now` returns the dequeued task as its own tool result rather than queuing a turn, because a tool body runs mid-turn by definition.
|
The plugin does not poll. AgentRQ pushes work over `notifications/claude/channel`. Repeats of the same `(task, content)` pair are dropped. `agentrq_autopull pause` stops delivery; the session stays open.
|
||||||
|
|
||||||
## How work arrives
|
|
||||||
|
|
||||||
**The plugin does not poll.** AgentRQ already decides when there is work and pushes it over `notifications/claude/channel`:
|
|
||||||
|
|
||||||
- creating a task assigned to the agent pushes it immediately (`backend/internal/handler/api/task.go`), provided nothing else is ongoing;
|
|
||||||
- `WorkspaceServer.StartPoller` re-pushes the next unclaimed task — or a status check for the ongoing one — every 60 seconds.
|
|
||||||
|
|
||||||
The plugin's own workspace session subscribes to that channel, exactly as [`acp-gateway`](https://github.com/agentrq/agentrq-acp-gateway) does for Gemini and other ACP agents. Polling the queue from the client would duplicate the server's own ticker and deliver every task twice.
|
|
||||||
|
|
||||||
Each push is forwarded **as written**. A new task, the periodic reminder, a status check, and a human's reply all arrive on the same channel; the plugin adds a framing line naming the `chat_id` and the tools to answer with, then hands over the content. It does not try to classify what kind of push it is, because that would only add a way to be wrong. The content is JSON-escaped into the framing, so pushed content cannot forge a framing field.
|
|
||||||
|
|
||||||
Delivery route depends on the agent's state: `inject()` while a turn is running, so it lands at the next step boundary, and `followup()` while idle, since nothing else would wake it. Neither interrupts a turn in flight.
|
|
||||||
|
|
||||||
`SendChannelNotification` puts the task id in `meta.chat_id` on every push, so the id never has to be recovered from the content.
|
|
||||||
|
|
||||||
**Repeats are dropped.** The workspace re-pushes an unclaimed task verbatim every minute; the runtime remembers recent `(task, content)` pairs, so the agent is handed it once and not woken every sixty seconds for work it already has. A genuinely new message on the same task still gets through.
|
|
||||||
|
|
||||||
**Staying connected is the load-bearing part.** No session, no pushes — so a closed transport or an unrecoverable transport error triggers a reconnect with exponential backoff (`reconnect.initialDelayMs` doubling to `reconnect.maxDelayMs`), on top of the SDK's own SSE resumption. Because the server re-pushes on its own schedule, a recovered session catches up on the next tick without any client-side replay.
|
|
||||||
|
|
||||||
`catchUpOnStart` dequeues one task when the session opens, so work that predates the connection does not wait for the server's next tick. A failed startup check costs latency, not work.
|
|
||||||
|
|
||||||
`agentrq_autopull pause` stops pushes from reaching the session; the session itself stays open.
|
|
||||||
|
|
||||||
One AgentRQ queue serves one worker, the harness Web UI creates a root agent per chat session, and pushes are broadcast to **every** connected session. Under the default `scope: single-agent`, exactly one live root agent holds the workspace session, so a second chat session does not get every task delivered a second time; a later session inherits the connection only after the owning agent is gone. Set `every-agent` when your agents work disjoint queues or you want deliberate fan-out.
|
|
||||||
|
|
||||||
## Multiple workspaces
|
|
||||||
|
|
||||||
AgentRQ users normally have several workspaces, each with its own queue, mission, and agent identity. **Run one profile per workspace.**
|
|
||||||
|
|
||||||
Install once per profile, and let each profile's `cordis.patch.yml` carry its own endpoint:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
dsh plugin --profile agentrq-acme add @agentrq/dsh-plugin-agentrq
|
|
||||||
dsh plugin --profile agentrq-beta add @agentrq/dsh-plugin-agentrq
|
|
||||||
# …then pin acme's URL in ~/.dsh/profiles/agentrq-acme/cordis.patch.yml
|
|
||||||
# and beta's URL in ~/.dsh/profiles/agentrq-beta/cordis.patch.yml
|
|
||||||
|
|
||||||
dsh --profile agentrq-acme # terminal 1
|
|
||||||
dsh --profile agentrq-beta # terminal 2
|
|
||||||
```
|
|
||||||
|
|
||||||
Because the endpoint lives in the profile rather than the environment, switching workspaces is switching profiles — nothing to re-export, and no way to start one workspace's profile pointed at another's queue.
|
|
||||||
|
|
||||||
Each profile gets its own process, sessions, working directory, and workspace connection, which matches how AgentRQ already models a workspace: one workspace, one agent, one mission. It also matches the usual case where workspaces track different repositories.
|
|
||||||
|
|
||||||
Two consequences worth knowing:
|
|
||||||
|
|
||||||
- **A single profile cannot serve two workspaces.** Mounting the bundle twice in one profile registers the `agentrq:protocol` prompt section and the `agentrq_autopull` tool twice in the same layer, and both registrations throw on a duplicate name. Namespacing them per instance is deferred until someone needs it.
|
|
||||||
- **No cross-workspace view.** AgentRQ's CoreMCP supervisor (`https://mcp.agentrq.com/mcp`) does expose `listWorkspaces` and `listAllTasks`, so a deployment that wants "what is outstanding everywhere" can mount it as an extra `@deepseek-ai/dsh-mcp-client` row. It sends no channel notifications, so it complements per-workspace delivery rather than replacing it.
|
|
||||||
|
|
||||||
`serverName` is safe to change: the guidance section and every framing derive their tool names from it, so the namespace the model sees and the namespace the prose describes cannot drift apart.
|
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
| Key | Default | Meaning |
|
| Key | Default | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `url` | — (required) | Workspace MCP endpoint, including its `?token=` credential |
|
| `url` | `''` (idle) | Workspace MCP endpoint, including its `?token=` credential |
|
||||||
| `token` | `''` | Bearer token, for deployments that prefer an `Authorization` header over `?token=` |
|
| `token` | `''` | Bearer token, for deployments that prefer an `Authorization` header over `?token=` |
|
||||||
| `mountBridge` | `true` | Mount the `@deepseek-ai/dsh-mcp-client` child that gives the model AgentRQ's tools |
|
| `mountBridge` | `true` | Mount the `@deepseek-ai/dsh-mcp-client` child that gives the model AgentRQ's tools |
|
||||||
| `serverName` | `agentrq` | Namespace for the bridged tools; the guidance section and framings follow it |
|
| `serverName` | `agentrq` | Namespace for the bridged tools; the guidance section and framings follow it |
|
||||||
@@ -142,59 +63,19 @@ Two consequences worth knowing:
|
|||||||
| `guidance` | `true` | Contribute the AgentRQ working-agreement system-prompt section |
|
| `guidance` | `true` | Contribute the AgentRQ working-agreement system-prompt section |
|
||||||
| `requestTimeoutMs` | `30000` | Timeout for one AgentRQ tool call |
|
| `requestTimeoutMs` | `30000` | Timeout for one AgentRQ tool call |
|
||||||
|
|
||||||
Set any of these in the same profile patch. A patch replaces a row's whole `config` rather than merging into it, but every key except `url` has a schema default, so a row only restates what it changes:
|
A profile patch replaces a row's whole `config` rather than merging into it. Every key except `url` has a schema default.
|
||||||
|
|
||||||
```yaml
|
|
||||||
- id: agentrq
|
|
||||||
name: '@agentrq/dsh-plugin-agentrq'
|
|
||||||
config:
|
|
||||||
url: "https://<workspace>.mcp.agentrq.com/mcp?token=<token>"
|
|
||||||
catchUpOnStart: false
|
|
||||||
reconnect:
|
|
||||||
initialDelayMs: 2000
|
|
||||||
maxDelayMs: 60000
|
|
||||||
```
|
|
||||||
|
|
||||||
## The prompt section
|
|
||||||
|
|
||||||
AgentRQ's MCP server ships its collaboration rules as server `Instructions`, and the harness does not surface an MCP server's instructions to the model. Without them the model has the tools but not the contract — that the human is remote, sees only what `reply` sends, and needs the task claimed before work starts. This package contributes those rules as the `agentrq:protocol` section in the tool-guidance band (order 150), so behavior in dsh matches behavior in the Claude Code and Gemini extensions. Turn it off with `guidance: false` when a deployment states the same protocol in its own persona.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install --legacy-peer-deps # harness packages declare peers pnpm resolves from the profile
|
npm install --legacy-peer-deps
|
||||||
npm run typecheck
|
npm run typecheck
|
||||||
npm test
|
npm test
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
`make plugin-deepseek` from the repository root runs all four.
|
`lib/` is the loadable entry (`package.json` `main`) and is committed so the desktop shell can copy the plugin without a runtime build.
|
||||||
|
|
||||||
## Releasing
|
|
||||||
|
|
||||||
`.github/workflows/plugin-deepseek-harness.yml` typechecks, tests, and builds this package on every pull request that touches `plugins/deepseek-harness/**`, and publishes it to npm when such a change lands on `main`.
|
|
||||||
|
|
||||||
**Bumping `version` in `package.json` is what releases.** npm refuses to republish an existing version, so the workflow checks first and skips the publish when the current version is already on the registry — an ordinary fix that touches this path does not need a version bump to merge.
|
|
||||||
|
|
||||||
Authentication is [npm trusted publishing](https://docs.npmjs.com/trusted-publishers): the package names `agentrq/agentrq` and this workflow file as its trusted publisher, the job requests an OIDC token with `id-token: write`, and npm exchanges it for a short-lived publish credential. There is no `NPM_TOKEN` secret to store, rotate, or leak, and npm attaches build provenance automatically.
|
|
||||||
|
|
||||||
Two things that break it, both non-obvious:
|
|
||||||
|
|
||||||
- **Renaming the workflow file.** The trusted-publisher record names `plugin-deepseek-harness.yml` exactly; a rename must be made on npm's side too or every publish is rejected.
|
|
||||||
- **npm older than 11.5.1.** `setup-node` with Node 22 installs npm 10.x, which has no OIDC support and silently falls back to looking for a token. The workflow upgrades npm explicitly for this reason — do not remove that step.
|
|
||||||
|
|
||||||
## Known limitations and deferred work
|
|
||||||
|
|
||||||
- **One workspace per profile** — each row carries one `url`, and mounting the bundle twice in one profile collides on the prompt-section and tool names. [Several workspaces means several profiles](#multiple-workspaces).
|
|
||||||
- **The endpoint is configured, not discovered** — there is no in-harness command to switch workspaces; the profile's `cordis.patch.yml` (watched, so no restart needed) or `AGENTRQ_WORKSPACE_MCP_URL` is the switch. A `ctx.settings` namespace would give a schema-driven editor with `role('secret')` redaction for the token, but its document is `$DSH_HOME`-global by default and so does not carry per-profile values without extra plumbing.
|
|
||||||
- **The bridge is mounted, not injectable** — the plugin mounts one `@deepseek-ai/dsh-mcp-client` child with the settings it derives from its own config. A deployment that needs the bridge's other knobs sets `mountBridge: false` and mounts its own row, and is then responsible for keeping `serverName` aligned.
|
|
||||||
- **Load-order boundary** — the plugin attaches only to root agents published after it loads; an agent that was already live when the plugin loaded gets no workspace session and no `agentrq_autopull` tool.
|
|
||||||
- **Ownership does not migrate to a live agent** — under `single-agent`, when the owning agent is disposed the connection stops until the *next* root agent is created; an already-open second session does not adopt it.
|
|
||||||
- **Session-lifetime repeat memory** — the delivered-set is process-local and bounded, so a restarted harness may be handed a task it saw before if that task is still unclaimed. Claiming a task with `updateTaskStatus` is what stops the workspace re-pushing it.
|
|
||||||
- **Auth is the URL's credential** — the plugin does not run the AgentRQ OAuth authorization-code flow; it uses the long-lived token from Workspace Settings, as a bearer header or a `?token=` query parameter.
|
|
||||||
- **Attachments travel through the model** — the plugin's own session only receives pushes and dequeues on request; `downloadAttachment` remains a model-facing tool call on the bridged server.
|
|
||||||
- **Permission verdicts are not bridged** — `acp-gateway` also consumes `notifications/claude/channel/permission` to answer AgentRQ's allow/deny prompts. The harness has its own `tools/pre-execute` approval axis, and wiring the two together is deferred.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[Apache-2.0](./LICENSE), matching the rest of the AgentRQ repository.
|
[Apache-2.0](./LICENSE), matching [agentrq/agentrq](https://github.com/agentrq/agentrq).
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
# The @agentrq/dsh-plugin-agentrq bundle patch. Applied when a profile lists
|
# The agentrq bundle patch. Applied when a profile lists this bundle.
|
||||||
# this bundle, over whatever earlier layers (normally @deepseek-ai/dsh-base)
|
|
||||||
# already contributed.
|
|
||||||
#
|
#
|
||||||
# One row, one endpoint. The plugin mounts @deepseek-ai/dsh-mcp-client itself as
|
# One row, one endpoint. The plugin mounts @deepseek-ai/dsh-mcp-client itself as
|
||||||
# a child fiber, so the workspace URL is configured once and the bridge shares
|
# a child fiber, so the workspace URL is configured once and the bridge shares
|
||||||
# this row's lifetime. Everything else has a schema default.
|
# this row's lifetime. Everything else has a schema default.
|
||||||
#
|
#
|
||||||
# `url` falls back to AGENTRQ_WORKSPACE_MCP_URL, which suits a container or CI
|
# `url` falls back to AGENTRQ_WORKSPACE_MCP_URL. An empty/missing value leaves
|
||||||
# job. For an interactive install, pin the endpoint in the profile's own
|
# the plugin idle so the default web profile can ship the bundle without a
|
||||||
# cordis.patch.yml instead: that layer is applied after this one, and dsh
|
# workspace. Pin the endpoint in the profile's own cordis.patch.yml to enable
|
||||||
# watches it, so an edit takes effect without a restart.
|
# it: that layer is applied after this one, and dsh watches it.
|
||||||
|
|
||||||
- insert:
|
- insert:
|
||||||
- id: agentrq
|
- id: agentrq
|
||||||
|
|||||||
286
plugins/agentrq/lib/index.d.ts
vendored
Normal file
286
plugins/agentrq/lib/index.d.ts
vendored
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import Schema from "@deepseek-ai/schemastery";
|
||||||
|
import { Context } from "@deepseek-ai/cordis";
|
||||||
|
import "@deepseek-ai/dsh-agent";
|
||||||
|
|
||||||
|
//#region src/config.d.ts
|
||||||
|
|
||||||
|
/** How many of a process's agents may take work from the same workspace. */
|
||||||
|
type DeliveryScope = 'single-agent' | 'every-agent';
|
||||||
|
/** Reconnection backoff for a dropped workspace session. */
|
||||||
|
interface ReconnectConfig {
|
||||||
|
/** Delay before the first retry, in milliseconds. */
|
||||||
|
initialDelayMs: number;
|
||||||
|
/** Ceiling for the exponential backoff, in milliseconds. */
|
||||||
|
maxDelayMs: number;
|
||||||
|
}
|
||||||
|
/** Resolved plugin configuration. */
|
||||||
|
interface Config {
|
||||||
|
/**
|
||||||
|
* The workspace's AgentRQ MCP endpoint. Copy it from Workspace Settings —
|
||||||
|
* the URL there already carries `?token=…`, which is how AgentRQ
|
||||||
|
* authenticates a headless client. Empty keeps the plugin loaded but idle
|
||||||
|
* so a desktop profile can ship the bundle without an endpoint.
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/**
|
||||||
|
* Optional bearer token, for deployments that prefer an `Authorization`
|
||||||
|
* header over the `?token=` query parameter. Empty means "the URL carries
|
||||||
|
* its own credential".
|
||||||
|
*/
|
||||||
|
token: string;
|
||||||
|
/**
|
||||||
|
* Whether to mount the MCP bridge that gives the model AgentRQ's tools.
|
||||||
|
*
|
||||||
|
* The plugin mounts one `@deepseek-ai/dsh-mcp-client` instance itself, so a
|
||||||
|
* deployment configures the workspace endpoint once. Set false only to mount
|
||||||
|
* that bridge as your own row — a second instance on the same `serverName`
|
||||||
|
* fails at load.
|
||||||
|
*/
|
||||||
|
mountBridge: boolean;
|
||||||
|
/**
|
||||||
|
* Namespace the bridged AgentRQ tools are registered under: the model sees
|
||||||
|
* `mcp__<serverName>__reply` and friends. The working-agreement section and
|
||||||
|
* every framing derive their tool names from this, so the two can never drift.
|
||||||
|
*/
|
||||||
|
serverName: string;
|
||||||
|
/**
|
||||||
|
* Whether the workspace's pushes — new tasks, the periodic next-task
|
||||||
|
* reminder, status checks, and the human's messages — are delivered into the
|
||||||
|
* session as they arrive.
|
||||||
|
*/
|
||||||
|
deliverPushes: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to dequeue one task at startup. The workspace re-pushes an
|
||||||
|
* unclaimed task on its own schedule, so this only shortens the wait for
|
||||||
|
* work that predates the connection.
|
||||||
|
*/
|
||||||
|
catchUpOnStart: boolean;
|
||||||
|
/**
|
||||||
|
* One AgentRQ workspace queue serves one worker, and pushes are broadcast to
|
||||||
|
* every connected session. Under `single-agent` (the default) exactly one
|
||||||
|
* live root agent holds the workspace session, so opening a second chat
|
||||||
|
* session does not get every task delivered twice. `every-agent` suits a
|
||||||
|
* deployment that wants deliberate fan-out.
|
||||||
|
*/
|
||||||
|
scope: DeliveryScope;
|
||||||
|
/** Reconnection backoff for a dropped workspace session. */
|
||||||
|
reconnect: ReconnectConfig;
|
||||||
|
/**
|
||||||
|
* Whether to contribute the AgentRQ working-agreement system-prompt section.
|
||||||
|
* Turn it off when a deployment states the same protocol in its own persona.
|
||||||
|
*/
|
||||||
|
guidance: boolean;
|
||||||
|
/** Per-request timeout for AgentRQ tool calls, in milliseconds. */
|
||||||
|
requestTimeoutMs: number;
|
||||||
|
}
|
||||||
|
declare const Config: Schema<Schemastery.ObjectS<{
|
||||||
|
url: Schema<string, string>;
|
||||||
|
token: Schema<string, string>;
|
||||||
|
mountBridge: Schema<boolean, boolean>;
|
||||||
|
serverName: Schema<string, string>;
|
||||||
|
deliverPushes: Schema<boolean, boolean>;
|
||||||
|
catchUpOnStart: Schema<boolean, boolean>;
|
||||||
|
scope: Schema<"single-agent" | "every-agent", "single-agent" | "every-agent">;
|
||||||
|
reconnect: Schema<Schemastery.ObjectS<{
|
||||||
|
initialDelayMs: Schema<number, number>;
|
||||||
|
maxDelayMs: Schema<number, number>;
|
||||||
|
}>, Schemastery.ObjectT<{
|
||||||
|
initialDelayMs: Schema<number, number>;
|
||||||
|
maxDelayMs: Schema<number, number>;
|
||||||
|
}>>;
|
||||||
|
guidance: Schema<boolean, boolean>;
|
||||||
|
requestTimeoutMs: Schema<number, number>;
|
||||||
|
}>, Schemastery.ObjectT<{
|
||||||
|
url: Schema<string, string>;
|
||||||
|
token: Schema<string, string>;
|
||||||
|
mountBridge: Schema<boolean, boolean>;
|
||||||
|
serverName: Schema<string, string>;
|
||||||
|
deliverPushes: Schema<boolean, boolean>;
|
||||||
|
catchUpOnStart: Schema<boolean, boolean>;
|
||||||
|
scope: Schema<"single-agent" | "every-agent", "single-agent" | "every-agent">;
|
||||||
|
reconnect: Schema<Schemastery.ObjectS<{
|
||||||
|
initialDelayMs: Schema<number, number>;
|
||||||
|
maxDelayMs: Schema<number, number>;
|
||||||
|
}>, Schemastery.ObjectT<{
|
||||||
|
initialDelayMs: Schema<number, number>;
|
||||||
|
maxDelayMs: Schema<number, number>;
|
||||||
|
}>>;
|
||||||
|
guidance: Schema<boolean, boolean>;
|
||||||
|
requestTimeoutMs: Schema<number, number>;
|
||||||
|
}>>;
|
||||||
|
//#endregion
|
||||||
|
//#region src/client.d.ts
|
||||||
|
/** One task dequeued from the workspace queue by an explicit `getTask`. */
|
||||||
|
interface AgentRqTask {
|
||||||
|
/** Base62 task id, as AgentRQ reports it. */
|
||||||
|
readonly id: string;
|
||||||
|
/** Task title, empty when the server omitted the line. */
|
||||||
|
readonly title: string;
|
||||||
|
/** Task status at fetch time, empty when the server omitted the line. */
|
||||||
|
readonly status: string;
|
||||||
|
/**
|
||||||
|
* The server's own rendering of the task, verbatim. The plugin hands this to
|
||||||
|
* the model rather than a reassembled copy, so nothing is lost in parsing.
|
||||||
|
*/
|
||||||
|
readonly text: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* One push from the workspace.
|
||||||
|
*
|
||||||
|
* The channel carries new task assignments, the periodic "next assigned task"
|
||||||
|
* reminder, status-check prompts, and messages a human typed into a thread.
|
||||||
|
* The plugin does not try to tell them apart: like the gateway, it forwards the
|
||||||
|
* content as written and lets the model read it.
|
||||||
|
*/
|
||||||
|
interface ChannelMessage {
|
||||||
|
/** Task id the push belongs to; also the `chat_id` the `reply` tool wants. */
|
||||||
|
readonly chatId: string;
|
||||||
|
/** Content as the workspace wrote it. */
|
||||||
|
readonly text: string;
|
||||||
|
/** Sender label supplied by AgentRQ. */
|
||||||
|
readonly user: string;
|
||||||
|
}
|
||||||
|
/** Reconnection behavior for the workspace session. */
|
||||||
|
interface ReconnectOptions {
|
||||||
|
/** Delay before the first retry, in milliseconds. */
|
||||||
|
readonly initialDelayMs: number;
|
||||||
|
/** Ceiling for the exponential backoff, in milliseconds. */
|
||||||
|
readonly maxDelayMs: number;
|
||||||
|
}
|
||||||
|
/** Options for constructing an {@link AgentRqClient}. */
|
||||||
|
interface AgentRqClientOptions {
|
||||||
|
/** Workspace MCP endpoint, including any `?token=` credential. */
|
||||||
|
readonly url: string;
|
||||||
|
/** Bearer token, or empty when the URL carries its own credential. */
|
||||||
|
readonly token: string;
|
||||||
|
/** Timeout for a single tool call, in milliseconds. */
|
||||||
|
readonly requestTimeoutMs: number;
|
||||||
|
/** Reconnection backoff for a dropped session. */
|
||||||
|
readonly reconnect: ReconnectOptions;
|
||||||
|
/** Called for every push the workspace delivers. */
|
||||||
|
readonly onChannelMessage: (message: ChannelMessage) => void;
|
||||||
|
/** Called when a connection attempt fails, for process-local diagnostics. */
|
||||||
|
readonly onConnectionError: (error: unknown) => void;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Interpret a `getTask` reply.
|
||||||
|
*
|
||||||
|
* @param text - joined text content of the tool result.
|
||||||
|
* @returns the task, or undefined when the queue is empty or unparseable.
|
||||||
|
*/
|
||||||
|
declare function parseTaskReply(text: string): AgentRqTask | undefined;
|
||||||
|
/**
|
||||||
|
* Interpret a `notifications/claude/channel` payload.
|
||||||
|
*
|
||||||
|
* `SendChannelNotification` puts the task id in `meta.chat_id` for every push,
|
||||||
|
* so the id never has to be recovered from the content.
|
||||||
|
*/
|
||||||
|
declare function parseChannelNotification(params: unknown): ChannelMessage | undefined;
|
||||||
|
/**
|
||||||
|
* One supervised AgentRQ workspace session.
|
||||||
|
*
|
||||||
|
* `start()` opens it and keeps it open: a closed transport or an unrecoverable
|
||||||
|
* transport error schedules a reconnect with exponential backoff, because a
|
||||||
|
* session that stays down silently stops delivering work.
|
||||||
|
*/
|
||||||
|
declare class AgentRqClient {
|
||||||
|
private readonly options;
|
||||||
|
private client;
|
||||||
|
private transport;
|
||||||
|
private opening;
|
||||||
|
private retryTimer;
|
||||||
|
private attempt;
|
||||||
|
private closed;
|
||||||
|
constructor(options: AgentRqClientOptions);
|
||||||
|
/** Whether a session is currently established. */
|
||||||
|
get connected(): boolean;
|
||||||
|
/**
|
||||||
|
* Open the session, and keep reopening it for as long as the client lives.
|
||||||
|
*
|
||||||
|
* @returns once the first attempt settles; a failure is reported through
|
||||||
|
* `onConnectionError` and retried, not thrown.
|
||||||
|
*/
|
||||||
|
start(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Open the session if it is not already open.
|
||||||
|
*
|
||||||
|
* @throws when this attempt fails; a retry is scheduled either way.
|
||||||
|
*/
|
||||||
|
ensureConnected(): Promise<void>;
|
||||||
|
/** Dequeue the next task assigned to this agent, if any. */
|
||||||
|
fetchNextTask(signal: AbortSignal): Promise<AgentRqTask | undefined>;
|
||||||
|
/**
|
||||||
|
* Call one AgentRQ tool and return its joined text content.
|
||||||
|
*
|
||||||
|
* @param name - raw AgentRQ tool name.
|
||||||
|
* @param args - JSON arguments for the tool.
|
||||||
|
* @param signal - caller cancellation.
|
||||||
|
* @returns the joined text blocks of the result.
|
||||||
|
* @throws when the connection or the call fails.
|
||||||
|
*/
|
||||||
|
callTool(name: string, args: Record<string, unknown>, signal: AbortSignal): Promise<string>;
|
||||||
|
/** Close the session and stop reconnecting. */
|
||||||
|
dispose(): Promise<void>;
|
||||||
|
private open;
|
||||||
|
/** Drop the current session and schedule a fresh one. */
|
||||||
|
private handleLost;
|
||||||
|
private scheduleRetry;
|
||||||
|
private createTransport;
|
||||||
|
private teardown;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
//#region src/runtime.d.ts
|
||||||
|
/** What `agentrq_autopull` reports about the current runtime. */
|
||||||
|
interface DeliveryStatus {
|
||||||
|
/** Whether the workspace session is established right now. */
|
||||||
|
readonly connected: boolean;
|
||||||
|
/** Whether pushes are configured to reach the session. */
|
||||||
|
readonly configured: boolean;
|
||||||
|
/** Whether pushes are reaching the session (configured and not paused). */
|
||||||
|
readonly active: boolean;
|
||||||
|
/** Task id most recently delivered to this agent, or null when none has been. */
|
||||||
|
readonly lastDeliveredTaskId: string | null;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
//#region src/prompt.d.ts
|
||||||
|
/** The public name the MCP bridge registers for one AgentRQ tool. */
|
||||||
|
declare function toolName(serverName: string, rawName: string): string;
|
||||||
|
/**
|
||||||
|
* The AgentRQ working agreement.
|
||||||
|
*
|
||||||
|
* It restates the protocol AgentRQ's MCP server sends as server `Instructions`,
|
||||||
|
* because the harness does not surface an MCP server's instructions to the
|
||||||
|
* model. Without it the model has the tools but not the collaboration rules,
|
||||||
|
* and the human — who is remote and sees only what `reply` sends — goes dark.
|
||||||
|
*
|
||||||
|
* @param serverName - the bridge namespace the AgentRQ tools are registered under.
|
||||||
|
* @returns the section text naming that namespace's tools.
|
||||||
|
*/
|
||||||
|
declare function renderGuidanceSection(serverName: string): string;
|
||||||
|
/** Frame one task the plugin dequeued itself as a user-role turn. */
|
||||||
|
declare function renderTaskFraming(task: AgentRqTask, serverName: string): string;
|
||||||
|
/**
|
||||||
|
* Frame one workspace push as model-facing context.
|
||||||
|
*
|
||||||
|
* The same channel carries a new task assignment, the periodic next-task
|
||||||
|
* reminder, a status check, and a human's reply. The framing says where the
|
||||||
|
* content came from and how to answer it, then hands over the content as
|
||||||
|
* written — classifying it here would only add a way to be wrong. The content
|
||||||
|
* is JSON-escaped so a crafted message cannot forge a framing field.
|
||||||
|
*/
|
||||||
|
declare function renderPushFraming(message: ChannelMessage, serverName: string): string;
|
||||||
|
//#endregion
|
||||||
|
//#region src/index.d.ts
|
||||||
|
/** Cordis function-plugin name used by loader diagnostics. */
|
||||||
|
declare const name = "agentrq";
|
||||||
|
/** Services required before this plugin loads. */
|
||||||
|
declare const inject: string[];
|
||||||
|
/**
|
||||||
|
* Attach AgentRQ to root agents published after this plugin loads.
|
||||||
|
*
|
||||||
|
* @param ctx - the plugin's context.
|
||||||
|
* @param config - validated plugin configuration.
|
||||||
|
*/
|
||||||
|
declare function apply(ctx: Context, config: Config): void;
|
||||||
|
//#endregion
|
||||||
|
export { AgentRqClient, type AgentRqTask, type ChannelMessage, Config, type DeliveryScope, type DeliveryStatus, type ReconnectConfig, type ReconnectOptions, apply, inject, name, parseChannelNotification, parseTaskReply, renderGuidanceSection, renderPushFraming, renderTaskFraming, toolName };
|
||||||
16198
plugins/agentrq/lib/index.js
Normal file
16198
plugins/agentrq/lib/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "agentrq",
|
"name": "agentrq",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
|
"private": true,
|
||||||
"description": "AgentRQ task manager for DeepSeek Harness: create, manage, and auto-pull AgentRQ tasks without leaving the harness",
|
"description": "AgentRQ task manager for DeepSeek Harness: create, manage, and auto-pull AgentRQ tasks without leaving the harness",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"dsh-plugin",
|
"dsh-plugin",
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
"homepage": "https://github.com/TommyFang2077/dsh-desktop/tree/main/plugins/agentrq",
|
"homepage": "https://github.com/TommyFang2077/dsh-desktop/tree/main/plugins/agentrq",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/agentrq/agentrq.git",
|
"url": "git+https://github.com/TommyFang2077/dsh-desktop.git",
|
||||||
"directory": "plugins/agentrq"
|
"directory": "plugins/agentrq"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export interface Config {
|
|||||||
/**
|
/**
|
||||||
* The workspace's AgentRQ MCP endpoint. Copy it from Workspace Settings —
|
* The workspace's AgentRQ MCP endpoint. Copy it from Workspace Settings —
|
||||||
* the URL there already carries `?token=…`, which is how AgentRQ
|
* the URL there already carries `?token=…`, which is how AgentRQ
|
||||||
* authenticates a headless client.
|
* authenticates a headless client. Empty keeps the plugin loaded but idle
|
||||||
|
* so a desktop profile can ship the bundle without an endpoint.
|
||||||
*/
|
*/
|
||||||
url: string
|
url: string
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +82,7 @@ export interface Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Config = Schema.object({
|
export const Config = Schema.object({
|
||||||
url: Schema.string().required().description('AgentRQ workspace MCP endpoint, including its ?token= credential.'),
|
url: Schema.string().default('').description('AgentRQ workspace MCP endpoint, including its ?token= credential. Empty keeps the plugin idle.'),
|
||||||
token: Schema.string().default('').description('Optional bearer token, when the URL carries no ?token= credential.'),
|
token: Schema.string().default('').description('Optional bearer token, when the URL carries no ?token= credential.'),
|
||||||
mountBridge: Schema.boolean().default(true).description('Mount the MCP bridge that gives the model AgentRQ\'s tools.'),
|
mountBridge: Schema.boolean().default(true).description('Mount the MCP bridge that gives the model AgentRQ\'s tools.'),
|
||||||
serverName: Schema.string().default('agentrq').description('Namespace for the bridged tools: mcp__<serverName>__reply, and so on.'),
|
serverName: Schema.string().default('agentrq').description('Namespace for the bridged tools: mcp__<serverName>__reply, and so on.'),
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ type AgentCleanup = () => void | Promise<void>
|
|||||||
* @param config - validated plugin configuration.
|
* @param config - validated plugin configuration.
|
||||||
*/
|
*/
|
||||||
export function apply(ctx: Context, config: Config): void {
|
export function apply(ctx: Context, config: Config): void {
|
||||||
|
if (config.url.trim() === '') {
|
||||||
|
ctx.logger.info(
|
||||||
|
'agentrq: no workspace url; set AGENTRQ_WORKSPACE_MCP_URL or config.url to enable',
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// The bridge is a child fiber rather than a sibling row, so the workspace
|
// The bridge is a child fiber rather than a sibling row, so the workspace
|
||||||
// endpoint is configured once and the two halves share one lifetime: our
|
// endpoint is configured once and the two halves share one lifetime: our
|
||||||
// disposal and HMR reload take the bridge with them.
|
// disposal and HMR reload take the bridge with them.
|
||||||
|
|||||||
Binary file not shown.
@@ -12,5 +12,8 @@ export default defineConfig({
|
|||||||
target: 'node20',
|
target: 'node20',
|
||||||
dts: true,
|
dts: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
external: [/^@deepseek-ai\//, /^@modelcontextprotocol\//],
|
// Harness packages stay external. The MCP SDK is inlined so a copied
|
||||||
|
// `lib/index.js` loads without a profile-local node_modules install.
|
||||||
|
external: [/^@deepseek-ai\//],
|
||||||
|
noExternal: [/^@modelcontextprotocol\//],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
],
|
],
|
||||||
"resources": [
|
"resources": [
|
||||||
"../plugins/dsh-desktop-vision/",
|
"../plugins/dsh-desktop-vision/",
|
||||||
|
"../plugins/agentrq/",
|
||||||
"../vendor/modlens/",
|
"../vendor/modlens/",
|
||||||
"../vendor/anchored-standard/",
|
"../vendor/anchored-standard/",
|
||||||
"../vendor/zero-anchored-standard/"
|
"../vendor/zero-anchored-standard/"
|
||||||
|
|||||||
@@ -44,14 +44,21 @@ class BundledAttributionTests(unittest.TestCase):
|
|||||||
self.assertIn("https://github.com/deepseek-ai/deepseek-harness", text)
|
self.assertIn("https://github.com/deepseek-ai/deepseek-harness", text)
|
||||||
self.assertIn("https://github.com/liustack/modlens", text)
|
self.assertIn("https://github.com/liustack/modlens", text)
|
||||||
self.assertIn("https://github.com/xiaobright/dsh-anchored-standard", text)
|
self.assertIn("https://github.com/xiaobright/dsh-anchored-standard", text)
|
||||||
|
self.assertIn("https://github.com/agentrq/agentrq", text)
|
||||||
self.assertIn("0.1.0-rc.6", text)
|
self.assertIn("0.1.0-rc.6", text)
|
||||||
self.assertIn("3.16.6", text)
|
self.assertIn("3.16.6", text)
|
||||||
|
self.assertIn("0.2.1", text)
|
||||||
self.assertIn("ffb845c5480adc953392a6db6f8a98ede621174b", text)
|
self.assertIn("ffb845c5480adc953392a6db6f8a98ede621174b", text)
|
||||||
self.assertIn("dsh-desktop-vision", text)
|
self.assertIn("dsh-desktop-vision", text)
|
||||||
self.assertIn("dsh-plugin", readme)
|
self.assertIn("dsh-plugin", readme)
|
||||||
self.assertIn("带上眼睛", readme)
|
self.assertIn("带上眼睛", readme)
|
||||||
self.assertIn("+8%", readme)
|
self.assertIn("+8%", readme)
|
||||||
|
self.assertIn("AGENTRQ_VERSION", (ROOT / "Makefile").read_text(encoding="utf-8"))
|
||||||
|
self.assertIn("../plugins/agentrq/", (ROOT / "src-tauri" / "tauri.conf.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertNotIn("### 6. 零工具锚定式标准预设", readme)
|
||||||
self.assertTrue((ROOT / "docs" / "licenses" / "modlens.LICENSE").is_file())
|
self.assertTrue((ROOT / "docs" / "licenses" / "modlens.LICENSE").is_file())
|
||||||
|
self.assertTrue((ROOT / "docs" / "licenses" / "agentrq.LICENSE").is_file())
|
||||||
|
self.assertTrue((ROOT / "plugins" / "agentrq" / "lib" / "index.js").is_file())
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
(ROOT / "docs" / "licenses" / "dsh-anchored-standard.NOTICE").is_file()
|
(ROOT / "docs" / "licenses" / "dsh-anchored-standard.NOTICE").is_file()
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user