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:
2026-08-16 12:36:04 +08:00
parent 73cb4cec60
commit d6f5359873
19 changed files with 16836 additions and 160 deletions

View File

@@ -1,3 +1,2 @@
node_modules/
lib/
package-lock.json

View File

@@ -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.
**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.
Copy the workspace MCP URL from AgentRQ **Settings → Setup → DeepSeek Harness** (it already includes `?token=`), then either:
```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
- id: agentrq
name: '@agentrq/dsh-plugin-agentrq'
name: agentrq
config:
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
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.
**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.
## 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 |
`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.
## 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.
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.
## Config
| 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=` |
| `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 |
@@ -142,59 +63,19 @@ Two consequences worth knowing:
| `guidance` | `true` | Contribute the AgentRQ working-agreement system-prompt section |
| `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:
```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.
A profile patch replaces a row's whole `config` rather than merging into it. Every key except `url` has a schema default.
## Development
```sh
npm install --legacy-peer-deps # harness packages declare peers pnpm resolves from the profile
npm install --legacy-peer-deps
npm run typecheck
npm test
npm run build
```
`make plugin-deepseek` from the repository root runs all four.
## 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.
`lib/` is the loadable entry (`package.json` `main`) and is committed so the desktop shell can copy the plugin without a runtime build.
## License
[Apache-2.0](./LICENSE), matching the rest of the AgentRQ repository.
[Apache-2.0](./LICENSE), matching [agentrq/agentrq](https://github.com/agentrq/agentrq).

View File

@@ -1,15 +1,13 @@
# The @agentrq/dsh-plugin-agentrq bundle patch. Applied when a profile lists
# this bundle, over whatever earlier layers (normally @deepseek-ai/dsh-base)
# already contributed.
# The agentrq bundle patch. Applied when a profile lists this bundle.
#
# 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
# this row's lifetime. Everything else has a schema default.
#
# `url` falls back to AGENTRQ_WORKSPACE_MCP_URL, which suits a container or CI
# job. For an interactive install, pin the endpoint in the profile's own
# cordis.patch.yml instead: that layer is applied after this one, and dsh
# watches it, so an edit takes effect without a restart.
# `url` falls back to AGENTRQ_WORKSPACE_MCP_URL. An empty/missing value leaves
# the plugin idle so the default web profile can ship the bundle without a
# workspace. Pin the endpoint in the profile's own cordis.patch.yml to enable
# it: that layer is applied after this one, and dsh watches it.
- insert:
- id: agentrq

286
plugins/agentrq/lib/index.d.ts vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
{
"name": "agentrq",
"version": "0.2.1",
"private": true,
"description": "AgentRQ task manager for DeepSeek Harness: create, manage, and auto-pull AgentRQ tasks without leaving the harness",
"keywords": [
"dsh-plugin",
@@ -12,7 +13,7 @@
"homepage": "https://github.com/TommyFang2077/dsh-desktop/tree/main/plugins/agentrq",
"repository": {
"type": "git",
"url": "git+https://github.com/agentrq/agentrq.git",
"url": "git+https://github.com/TommyFang2077/dsh-desktop.git",
"directory": "plugins/agentrq"
},
"license": "Apache-2.0",

View File

@@ -25,7 +25,8 @@ export 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.
* authenticates a headless client. Empty keeps the plugin loaded but idle
* so a desktop profile can ship the bundle without an endpoint.
*/
url: string
/**
@@ -81,7 +82,7 @@ export interface Config {
}
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.'),
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.'),

View File

@@ -53,6 +53,13 @@ type AgentCleanup = () => void | Promise<void>
* @param config - validated plugin configuration.
*/
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
// endpoint is configured once and the two halves share one lifetime: our
// disposal and HMR reload take the bridge with them.

Binary file not shown.

View File

@@ -12,5 +12,8 @@ export default defineConfig({
target: 'node20',
dts: 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\//],
})