dsh-foreman-node 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +78 -2
- package/cordis.patch.yml +5 -0
- package/lib/index.d.ts +219 -0
- package/lib/index.js +573 -0
- package/lib/protocol.d.ts +110 -0
- package/lib/protocol.js +69 -0
- package/package.json +94 -4
- package/src/client.ts +209 -0
- package/src/index.ts +129 -0
- package/src/protocol.ts +160 -0
- package/src/report-tool.ts +93 -0
- package/src/session.ts +156 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
本包是 [`dsh-foreman`](https://www.npmjs.com/package/dsh-foreman) 的设备侧,单独发版,
|
|
4
|
+
但**协议与 hub 版本绑定**:两侧协议版本不一致时,握手就拒绝并给出可读原因。
|
|
5
|
+
|
|
6
|
+
## 0.1.0 — 2026-11-07
|
|
7
|
+
|
|
8
|
+
首个公开发布。
|
|
9
|
+
|
|
10
|
+
### 新增:连出去
|
|
11
|
+
|
|
12
|
+
- `NodeClient`:主动 wss 连到主 loop 的稳定域名,断线指数退避重连(1 s 起,封顶 60 s)。
|
|
13
|
+
方向是出站,所以公司 NAT 后面也能用。
|
|
14
|
+
- 每 15 s 一次心跳,报「我在跑几件」;`hello` 报完整的在跑 assignment id 列表,
|
|
15
|
+
让主 loop 对齐而不是猜测。
|
|
16
|
+
|
|
17
|
+
### 新增:接单
|
|
18
|
+
|
|
19
|
+
- `JobSessions`:收到 `assign` 就在帧里给的**设备侧目录**建(或复用)会话
|
|
20
|
+
`job-<assignmentId>`,把渲染好的任务文本投进去。会话出现在你自己的侧栏里,你能看、能插话。
|
|
21
|
+
- 会话权限固定为 **`workspace-write`**,不是 `danger-full-access`:笔记本前没人,
|
|
22
|
+
弹审批等于卡死。需要越界时不弹窗,而是报 `blocked` 让人决定。
|
|
23
|
+
- 投递按 `requestId` 幂等:重投不会产生第二条消息。
|
|
24
|
+
|
|
25
|
+
### 新增:回报
|
|
26
|
+
|
|
27
|
+
- 每个 job 会话自动挂上 `foreman_report` 工具:`progress` / `ready` / `blocked` / `failed`,
|
|
28
|
+
可选一句提问。工具调用直接转成 `report` 帧。
|
|
29
|
+
- **不向模型提供 `dropped`**:那是判断,设备没有判断权。想表达同样意思只能 `blocked`。
|
|
30
|
+
- 投递成功回一帧 `accepted`,主 loop 才把这张 assignment 记为 started。
|
|
31
|
+
|
|
32
|
+
### 协议
|
|
33
|
+
|
|
34
|
+
- 帧定义在 `src/protocol.ts`,**这是协议的唯一定义**,hub 依赖本包并从这里导入。
|
|
35
|
+
- 单帧上限 256 KiB,超限拒绝而不是截断;带副作用的帧带去重 id;帧里不传凭据。
|
|
36
|
+
|
|
37
|
+
### 依赖
|
|
38
|
+
|
|
39
|
+
- 运行时零第三方依赖。`@deepseek-ai/*` 是 `peerDependencies`,由装了本插件的 DSH 实例提供。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek Harness Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-foreman-node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Not an official DeepSeek product.** 第三方项目,与 DeepSeek 官方无关。
|
|
4
|
+
|
|
5
|
+
**设备侧。** 装在每台要被主 loop 派活的机器上:公司笔记本、宿舍电脑、任何一台你愿意接单的机器。
|
|
6
|
+
|
|
7
|
+
它只做三件事:
|
|
8
|
+
|
|
9
|
+
1. **连出去**——主动 wss 连到主 loop 的稳定域名,断线指数退避重连。方向是出站,所以公司 NAT 后面也能用。
|
|
10
|
+
2. **接单**——收到 `assign` 就在指定目录建(或复用)一个 `job-<assignmentId>` 会话,把渲染好的任务文本投进去。这个会话出现在你自己的侧栏里,你能看、能插话。
|
|
11
|
+
3. **回报**——在 job 会话上注册 `foreman_report` 工具,`progress` / `ready` / `blocked` / `failed` 加可选提问,直接转成 `report` 帧发回去。
|
|
12
|
+
|
|
13
|
+
**它没有的**:工单、评估、决策、策略、数据库、SQLite。判断这件事该不该做,是主 loop 的事,不是设备的。
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌──────────── 主 loop(另一台机器)────────────┐
|
|
17
|
+
│ dsh-foreman:唯一账本、选设备、下发、收回报 │
|
|
18
|
+
└────────────────────┬─────────────────────────┘
|
|
19
|
+
│ 设备主动外连(wss)
|
|
20
|
+
▼
|
|
21
|
+
┌───────────────────────────────┐
|
|
22
|
+
│ dsh-foreman-node(本机) │
|
|
23
|
+
│ job-<id> 会话,权限受限 │
|
|
24
|
+
│ 你自己的 DSH 一切照常 │
|
|
25
|
+
└───────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 状态
|
|
29
|
+
|
|
30
|
+
**早期开发中。** 协议、连接、接单与回报工具都已落地并有测试(14 项)。
|
|
31
|
+
|
|
32
|
+
## 装上去
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
# 本机 profile 的 cordis.patch.yml
|
|
36
|
+
- id: dsh-foreman-node
|
|
37
|
+
config:
|
|
38
|
+
url: wss://foreman.example.com # 主 loop 的稳定域名(隧道后面)
|
|
39
|
+
deviceId: laptop # 主 loop 上 scripts/add-device.mjs 登记的那个 id
|
|
40
|
+
token: <登记时打印的那一枚>
|
|
41
|
+
roots:
|
|
42
|
+
- project: shop
|
|
43
|
+
path: 'D:\工作\shop' # 本机自己的绝对路径,原样写
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
重启本机的 DSH 即可。主 loop 不用重启——设备表在主 loop 的数据库里,不在它的插件配置里。
|
|
47
|
+
|
|
48
|
+
## 接单时发生什么
|
|
49
|
+
|
|
50
|
+
1. 收到 `assign` → 在帧里给的目录建(或复用)会话 `job-<assignmentId>`。
|
|
51
|
+
2. 任务文本作为一条用户消息投进去,`requestId` 幂等:重投不会产生第二条。
|
|
52
|
+
3. 会话挂上 `foreman_report` 工具,模型用它报 `progress` / `ready` / `blocked` / `failed`。
|
|
53
|
+
4. 每次投递成功回一帧 `accepted`,主 loop 才把这张 assignment 记为 started。
|
|
54
|
+
|
|
55
|
+
**权限是 `workspace-write`**,不是主 loop 自己用的 `danger-full-access`:笔记本前没人,弹审批等于卡死。
|
|
56
|
+
需要越界时设备不弹窗,而是报 `blocked`,由人决定放宽 root 还是换设备。
|
|
57
|
+
|
|
58
|
+
## 协议
|
|
59
|
+
|
|
60
|
+
帧定义在 [`src/protocol.ts`](src/protocol.ts)。**这个文件是该协议的唯一权威定义**:
|
|
61
|
+
主 loop 那个包 `dsh-foreman` 依赖本包并从 `dsh-foreman-node/protocol` 导入它,所以两侧
|
|
62
|
+
不可能漂移——这也是为什么协议放在设备侧而不是主 loop 侧:反过来会把整个领域内核装进每台笔记本。
|
|
63
|
+
|
|
64
|
+
约定:
|
|
65
|
+
- 一条 WebSocket 文本消息一个 JSON 帧,`v` 是协议版本,不同版本在握手时就拒绝。
|
|
66
|
+
- 单帧上限 256 KiB,超限拒绝而不是截断。
|
|
67
|
+
- 带副作用的帧带 `frameId`,接收方去重。
|
|
68
|
+
- `report` 的 `state` **不含 `dropped`**:那是判断,设备没有判断权。
|
|
69
|
+
|
|
70
|
+
## 由来
|
|
71
|
+
|
|
72
|
+
代码结构参考了 [dsh-superagent-local](https://github.com/mengzhongjie/dsh-superagent-local)
|
|
73
|
+
与 [dsh-superagent-prototype](https://github.com/deepseek-ai/dsh-superagent-prototype)(均为 MIT,
|
|
74
|
+
Copyright (c) 2026 DeepSeek Harness Team)。架构思想来自 DeepSeek Harness 团队的
|
|
75
|
+
《Super-agent 设计探索》,本包对应其中「计算与执行环境分离」那一层。
|
|
76
|
+
|
|
77
|
+
## 许可
|
|
78
|
+
|
|
79
|
+
MIT,见 [LICENSE](LICENSE)。
|
package/cordis.patch.yml
ADDED
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Frame, FrameAttachment, FrameDedupe, Root } from "./protocol.js";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
4
|
+
/** What a socket has to offer; the global `WebSocket` satisfies it, and tests supply an object that does too. */
|
|
5
|
+
interface SocketLike {
|
|
6
|
+
readyState: number;
|
|
7
|
+
send(data: string): void;
|
|
8
|
+
close(): void;
|
|
9
|
+
addEventListener(type: 'open' | 'close' | 'error', listener: () => void): void;
|
|
10
|
+
addEventListener(type: 'message', listener: (event: {
|
|
11
|
+
data: unknown;
|
|
12
|
+
}) => void): void;
|
|
13
|
+
}
|
|
14
|
+
/** Opens a socket; injectable so tests never touch the network. */
|
|
15
|
+
type Connect = (url: string) => SocketLike;
|
|
16
|
+
interface NodeClientInput {
|
|
17
|
+
/** The hub's stable address, such as `wss://foreman.example.com`. */
|
|
18
|
+
url: string;
|
|
19
|
+
deviceId: string;
|
|
20
|
+
/** The device token; sent in the hello and nowhere else. */
|
|
21
|
+
token: string;
|
|
22
|
+
version: string;
|
|
23
|
+
/** The projects this device can work on, as its own absolute paths. Declared once and re-declared on every hello. */
|
|
24
|
+
roots: () => Root[];
|
|
25
|
+
/** The assignments this device currently holds. Reported on every hello so the hub aligns instead of guessing. */
|
|
26
|
+
running: () => string[];
|
|
27
|
+
/** Handles frames the hub sends. */
|
|
28
|
+
onFrame: (frame: Frame) => void;
|
|
29
|
+
/** Milliseconds before the first reconnect; doubles up to `maxReconnectMs`. */
|
|
30
|
+
reconnectMs?: number;
|
|
31
|
+
maxReconnectMs?: number;
|
|
32
|
+
connect?: Connect;
|
|
33
|
+
/** The heartbeat interval; a device that says nothing looks offline to the hub, which turns its work blocked. */
|
|
34
|
+
heartbeatMs?: number;
|
|
35
|
+
log?: (text: string) => void;
|
|
36
|
+
/** Frame ids the device has already acted on, so a redelivered frame does nothing twice. */
|
|
37
|
+
dedupe?: FrameDedupe;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Keeps one connection to the hub open, reconnecting with backoff.
|
|
41
|
+
*
|
|
42
|
+
* The class owns the socket's lifecycle and nothing else: what a frame means is the plugin's business, and what
|
|
43
|
+
* happens when the connection is refused is the hub's. `start` and `stop` are idempotent.
|
|
44
|
+
*/
|
|
45
|
+
declare class NodeClient {
|
|
46
|
+
private readonly input;
|
|
47
|
+
private socket?;
|
|
48
|
+
private stopped;
|
|
49
|
+
private reconnectTimer?;
|
|
50
|
+
private heartbeatTimer?;
|
|
51
|
+
private delay;
|
|
52
|
+
private readonly dedupe;
|
|
53
|
+
private readonly connect;
|
|
54
|
+
private readonly log;
|
|
55
|
+
constructor(input: NodeClientInput);
|
|
56
|
+
/** Open the connection, or do nothing if it is already open or opening. */
|
|
57
|
+
start(): void;
|
|
58
|
+
/** Close the connection and stop reconnecting; a late socket event does nothing. */
|
|
59
|
+
stop(): void;
|
|
60
|
+
/** Send a frame now if the connection is open. Returns whether it went out, so a caller can retry later. */
|
|
61
|
+
send(frame: Frame): boolean;
|
|
62
|
+
/** Report an outcome on an assignment, which is the only way a hub learns what a job did. */
|
|
63
|
+
report(assignmentId: string, report: {
|
|
64
|
+
state: 'progress' | 'ready' | 'blocked' | 'failed';
|
|
65
|
+
summary: string;
|
|
66
|
+
question?: string;
|
|
67
|
+
}): void;
|
|
68
|
+
/** Whether the connection is open right now. */
|
|
69
|
+
connected(): boolean;
|
|
70
|
+
private open;
|
|
71
|
+
private hello;
|
|
72
|
+
private startHeartbeat;
|
|
73
|
+
private receive;
|
|
74
|
+
/** Acknowledge a frame once its side effect is done, so the hub can stop resending it. */
|
|
75
|
+
ack(frame: Frame): void;
|
|
76
|
+
/** Tell the hub a frame's work reached its Session, which is what lets it mark the assignment started. */
|
|
77
|
+
accepted(assignmentId: string): void;
|
|
78
|
+
private scheduleReconnect;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The `foreman_report` tool a device registers on a job Session.
|
|
82
|
+
*
|
|
83
|
+
* Reporting is a hard requirement rather than a convenience: in the domain, "waiting for a person" is a structured
|
|
84
|
+
* fact that a report carries, and inferring it from a turn ending is an explicitly rejected approach. So a device
|
|
85
|
+
* states outcomes through this tool and nothing else.
|
|
86
|
+
*
|
|
87
|
+
* `dropped` is not in its enum. Deciding that work should not be done is a judgement, a device has no judgement, and
|
|
88
|
+
* the hub's ledger already refuses that state from a device (see the protocol). A device that believes the same thing
|
|
89
|
+
* must report `blocked` and let a person decide.
|
|
90
|
+
*
|
|
91
|
+
* The schema is written as JSON Schema and validated here rather than through `zod`, so this package stays free of
|
|
92
|
+
* runtime dependencies beyond the DSH services it is a peer plugin of.
|
|
93
|
+
*/
|
|
94
|
+
/** What a device may report; the same set the protocol's `NodeReportState` names. */
|
|
95
|
+
declare const REPORT_STATES: readonly ["progress", "ready", "blocked", "failed"];
|
|
96
|
+
type ReportState = typeof REPORT_STATES[number];
|
|
97
|
+
/** A report as the tool's caller gives it. */
|
|
98
|
+
interface DeviceReport {
|
|
99
|
+
state: ReportState;
|
|
100
|
+
summary: string;
|
|
101
|
+
/** One question a person must answer; paired with `state: 'blocked'` in practice. */
|
|
102
|
+
question?: string;
|
|
103
|
+
}
|
|
104
|
+
/** The host a tool reports through: the client that owns the connection, or a test. */
|
|
105
|
+
interface ReportHost {
|
|
106
|
+
report(assignmentId: string, report: DeviceReport): void;
|
|
107
|
+
}
|
|
108
|
+
/** The parts of a tool definition DSH registers: name, description, JSON Schema, and the executor. */
|
|
109
|
+
interface DeviceTool {
|
|
110
|
+
name: string;
|
|
111
|
+
description: string;
|
|
112
|
+
parameters: Record<string, unknown>;
|
|
113
|
+
execute(args: Record<string, unknown>, execution: {
|
|
114
|
+
signal: AbortSignal;
|
|
115
|
+
}): Promise<unknown>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build the `foreman_report` tool for one assignment.
|
|
119
|
+
*
|
|
120
|
+
* The assignment id is bound here rather than passed by the model: a job Session reports on the assignment it was
|
|
121
|
+
* started for, and a model should not be able to name a different one.
|
|
122
|
+
*/
|
|
123
|
+
declare function foremanReportTool(host: ReportHost, assignmentId: string): DeviceTool;
|
|
124
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
125
|
+
interface MessageSourceMap {
|
|
126
|
+
'dsh-foreman-node': {
|
|
127
|
+
kind: 'dsh-foreman-node';
|
|
128
|
+
form: 'relay';
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
interface EnsureInput {
|
|
133
|
+
assignmentId: string;
|
|
134
|
+
/** The absolute directory on this device; the hub mapped it through the device's declared roots. */
|
|
135
|
+
directory: string;
|
|
136
|
+
title: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Keeps the device's job Sessions and delivers work into them.
|
|
140
|
+
*
|
|
141
|
+
* A Session is created only when it does not exist yet, so a redelivered `assign` reuses the Session and the work
|
|
142
|
+
* already in it instead of starting a second one. Delivery is idempotent by request id: the hub retrying a frame
|
|
143
|
+
* that it never saw acknowledged cannot append the task twice.
|
|
144
|
+
*/
|
|
145
|
+
declare class JobSessions {
|
|
146
|
+
private readonly ctx;
|
|
147
|
+
/** Jobs by assignment id, so `running()` reports what this device actually holds rather than what it was told. */
|
|
148
|
+
private readonly jobs;
|
|
149
|
+
/** Assignments whose Session already carries the report tool, so a redelivered assign registers it only once. */
|
|
150
|
+
private readonly reported;
|
|
151
|
+
constructor(ctx: Context);
|
|
152
|
+
/**
|
|
153
|
+
* Resolve or create the `job-<assignmentId>` Session in the given directory, attach its `foreman_report` tool, and
|
|
154
|
+
* register it so a heartbeat counts it.
|
|
155
|
+
*
|
|
156
|
+
* The tool is attached here rather than left to the caller because it is the only way a device can say what
|
|
157
|
+
* happened: without it a job Session's model has no `progress`/`ready`/`blocked`/`failed` channel, the hub waits
|
|
158
|
+
* forever, and the whole design's "never infer state from session events" requirement is moot. Registration is
|
|
159
|
+
* idempotent per assignment, so a redelivered `assign` does not stack a second tool on the same Session.
|
|
160
|
+
*
|
|
161
|
+
* @returns the Session id.
|
|
162
|
+
*/
|
|
163
|
+
ensure(input: EnsureInput, onReport?: (assignmentId: string, report: DeviceReport) => void): Promise<string>;
|
|
164
|
+
/**
|
|
165
|
+
* Deliver a task into its job Session, once per request id.
|
|
166
|
+
*
|
|
167
|
+
* `queue` rather than `steer` on the first delivery: the job's task should start a turn, not interrupt one that is
|
|
168
|
+
* already running. A redelivery finds the message already recorded and adds nothing.
|
|
169
|
+
*/
|
|
170
|
+
deliver(sessionId: string, text: string, requestId: string): Promise<void>;
|
|
171
|
+
/** Whether the Session already holds the delivery for `requestId`, in its live inbox or its stored history. */
|
|
172
|
+
holds(agent: Agent, requestId: string): Promise<boolean>;
|
|
173
|
+
/** The assignment ids this device currently holds a job for: what a heartbeat and a reconnected hello report. */
|
|
174
|
+
running(): string[];
|
|
175
|
+
/** Forget a job whose assignment the hub stopped, so this device stops reporting it as running. */
|
|
176
|
+
forget(assignmentId: string): void;
|
|
177
|
+
/** Attach the `foreman_report` tool to a job Session, disposed with it. */
|
|
178
|
+
reportTools(agent: Agent, assignmentId: string, onReport: (assignmentId: string, report: DeviceReport) => void): void;
|
|
179
|
+
}
|
|
180
|
+
/** Version reported in the hello, so the hub can tell which device build is talking. */
|
|
181
|
+
declare const VERSION = "0.1.0";
|
|
182
|
+
/** One project this device can work on: the hub's logical name and this machine's own absolute path. */
|
|
183
|
+
interface NodeRoot {
|
|
184
|
+
project: string;
|
|
185
|
+
path: string;
|
|
186
|
+
}
|
|
187
|
+
/** What the `dsh-foreman-node` entry in a profile's patch configures. */
|
|
188
|
+
interface NodeConfig {
|
|
189
|
+
/** The hub's stable address; the device dials out to it. */
|
|
190
|
+
url: string;
|
|
191
|
+
/** This device's id, as registered on the hub with `Devices.add`. */
|
|
192
|
+
deviceId: string;
|
|
193
|
+
/** This device's token; sent in the hello and nowhere else. */
|
|
194
|
+
token: string;
|
|
195
|
+
/** The projects this device can work on. */
|
|
196
|
+
roots: NodeRoot[];
|
|
197
|
+
/** How often to tell the hub this device is alive, in milliseconds. */
|
|
198
|
+
heartbeatMs: number;
|
|
199
|
+
/** Delay before the first reconnect attempt, in milliseconds. */
|
|
200
|
+
reconnectMs: number;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Configuration of one device, from the `dsh-foreman-node` entry in the profile's patch.
|
|
204
|
+
*
|
|
205
|
+
* The annotation names the schema type through the global `Schemastery` namespace rather than letting it be inferred:
|
|
206
|
+
* an inferred type here names a `cosmokit` symbol the emitted declaration cannot reference portably.
|
|
207
|
+
*/
|
|
208
|
+
declare const Config: Schemastery<NodeConfig>;
|
|
209
|
+
declare const name = "dsh-foreman-node";
|
|
210
|
+
/** DSH services a job needs; all are the host's own, and none is a foreman one. */
|
|
211
|
+
declare const inject: string[];
|
|
212
|
+
/**
|
|
213
|
+
* Start the device.
|
|
214
|
+
*
|
|
215
|
+
* The plugin returns once the connection is attempted, not once it succeeds: a device must boot and stay usable
|
|
216
|
+
* while its hub is unreachable, and the client keeps retrying in the background.
|
|
217
|
+
*/
|
|
218
|
+
declare function apply(ctx: Context, config: NodeConfig): Promise<void>;
|
|
219
|
+
export { Config, type Frame, type FrameAttachment, JobSessions, NodeClient, NodeConfig, NodeRoot, REPORT_STATES, type Root, VERSION, apply, foremanReportTool, inject, name };
|