dsh-foreman 0.3.0 → 0.3.2
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 +164 -2
- package/lib/client.js +197 -31
- package/lib/feishu-watch.d.ts +2 -2
- package/lib/github-watch.d.ts +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +118 -4
- package/lib/{intake-Di3PgVj2.d.ts → intake-B1UsoHuw.d.ts} +8 -0
- package/lib/{kind-DGutnJ2b.d.ts → kind-BYtP14oM.d.ts} +1 -1
- package/lib/local-watch.d.ts +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,170 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本仓库的历史从本项目自己的初始提交开始,**不携带上游 git 历史**(见 README 的「由来与致谢」)。
|
|
4
|
+
下面 `0.2.0` 一节是从代码基线 `dsh-superagent-local` 沿袭下来的、那一份代码相对上游
|
|
5
|
+
`dsh-superagent-prototype` 的改动记录,保留它是为了说明基线里已经有什么;**它不是本项目的发布版本**。
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
本项目自己的版本从 `0.1.0` 开始。
|
|
8
|
+
|
|
9
|
+
## 0.3.2 — 2026-11-08
|
|
10
|
+
|
|
11
|
+
### 修复:派单文本没有说要回报
|
|
12
|
+
|
|
13
|
+
真机上一次派单,设备做了 30 步、写完整答案,**一次都没调用 `foreman_report`**——因为派单文本里
|
|
14
|
+
根本没提这件事。设备收到的只有标题、请求原文和附件说明,模型自然当成普通任务做完就停,
|
|
15
|
+
hub 于是永远停在 `running`。
|
|
16
|
+
|
|
17
|
+
现在每张派单的末尾都有一段:`foreman_report` 是结果到达问话人的唯一途径,答在对话里
|
|
18
|
+
不算完成,**没回报就不算做完**。
|
|
19
|
+
|
|
20
|
+
配套地,设备侧 `dsh-foreman-node@0.2.1` 加了一层兜底:一轮结束而还没回报时自动补一条
|
|
21
|
+
`ready`。两层一起,模型照做时走正路,不照做也不会让工单挂死。
|
|
22
|
+
|
|
23
|
+
## 0.3.1 — 2026-11-08
|
|
24
|
+
|
|
25
|
+
### 修复:同一 id 的两个连接互相踢,设备永远上不了线
|
|
26
|
+
|
|
27
|
+
`Hub.attach` 在新连接到达时,立刻关闭同 id 的旧连接。设备重连时旧 socket 往往还没超时,
|
|
28
|
+
于是**两边互相踢**:谁都完不成握手,hub 一直认为设备离线,双方不停重试。
|
|
29
|
+
|
|
30
|
+
现场证据:本机 89 个 TIME_WAIT,hub 上一个在线设备都没有;一张要查看宿舍电脑的工单,
|
|
31
|
+
因为选不到设备,回退到服务器本地,在空目录里跑完并报告成功。
|
|
32
|
+
|
|
33
|
+
现在新连接先排队,**令牌验证通过后才接管 id**。半成品连接打扰不到正在工作的那条,
|
|
34
|
+
真正的重连仍然能接管。
|
|
35
|
+
|
|
36
|
+
### 修复:点名了机器,却在服务器上跑
|
|
37
|
+
|
|
38
|
+
`prepareTicket` 在选不到设备时静默回退本地执行,并把这种行为写成了「有意为之」。
|
|
39
|
+
在「项目只存在于远程设备」的场景里,它给出一个看起来成功、实际毫无意义的结果。
|
|
40
|
+
|
|
41
|
+
现在:点名了设备或要求了标签的请求,选不到设备就记 `blockedReason`,由 executor
|
|
42
|
+
直接报 `blocked`,**不启动任何 Session**。人在面板上看到「没有设备能接」,
|
|
43
|
+
而不是假成功。没点名设备的仍然回退本地——本机可达的项目不该因为笔记本离线就卡住。
|
|
44
|
+
|
|
45
|
+
### 修复:worker 会话在侧栏看不到
|
|
46
|
+
|
|
47
|
+
原来把所有 `worker-*` 都藏起来。那恰好藏掉了唯一能回答「任务在哪台机器、哪个目录跑」的线索。
|
|
48
|
+
现在只藏主会话(用行上的 M 气泡进),worker 保留显示。
|
|
49
|
+
|
|
50
|
+
### 新增:设备可以删除,也可以改项目
|
|
51
|
+
|
|
52
|
+
- **删除**:吊销保留记录(审计用),删除连记录一起去掉(登记错了用)。删除需要二次确认。
|
|
53
|
+
- **编辑**:显示名、标签、它能接的项目都能改,**id 和令牌不动**,所以那台设备不用重新配置。
|
|
54
|
+
这解决了「登记时漏了项目只能重登」的问题。
|
|
55
|
+
|
|
56
|
+
## 0.3.0 — 2026-11-07
|
|
57
|
+
|
|
58
|
+
### 新增:在面板里登记设备,不用再跑脚本
|
|
59
|
+
|
|
60
|
+
「设置 › 设备」多了一个登记表单:填 id、显示名、它能接的项目和**那台机器上的路径**,
|
|
61
|
+
点「登记」就得到一枚令牌。卡片把设备要的四样东西排在一起——`url`、`deviceId`、`token`,
|
|
62
|
+
以及设备页面的入口——令牌只显示这一次,因为库里只存哈希,关掉或刷新就再也拿不回来。
|
|
63
|
+
|
|
64
|
+
以前这一步只能跑 `scripts/add-device.mjs`,那个脚本还在,行为不变;面板走的是同一个
|
|
65
|
+
`Devices.add`,所以两条路得到的结果一样。
|
|
66
|
+
|
|
67
|
+
重复的 id 会回 400 并明确说「去换令牌」,不是 500——那是操作者的失误,不是服务端故障。
|
|
68
|
+
|
|
69
|
+
### 新增接口
|
|
70
|
+
|
|
71
|
+
`POST /orchestrator/api/devices`,body `{ id, name, labels?, roots }`,
|
|
72
|
+
响应 `{ id, token }`。令牌只在这里出现一次。
|
|
73
|
+
|
|
74
|
+
### 修复:bundle patch 里的中文是乱码
|
|
75
|
+
|
|
76
|
+
`cordis.patch.yml` 里几行中文在初始提交时就已经是「UTF-8 字节被当成 GBK 读」的产物,
|
|
77
|
+
所以面板上主会话旁边显示的是 `dsh-foreman 涓?Session`。已改回正确的
|
|
78
|
+
`dsh-foreman 主 Session` 与 `dsh-foreman 执行器`。
|
|
79
|
+
|
|
80
|
+
### 依赖
|
|
81
|
+
|
|
82
|
+
`dsh-foreman-node` 精确锁到 `0.2.0`(不写 `^`):两边协议必须同版本,自动升级反而危险。
|
|
83
|
+
|
|
84
|
+
## 0.1.1 — 2026-11-07
|
|
85
|
+
|
|
86
|
+
修的是 `0.1.0` 发布时才暴露的两个问题,都是真机验证(上海服务器 srv-02)发现的。
|
|
87
|
+
|
|
88
|
+
### 修复:运维脚本没有进包
|
|
89
|
+
|
|
90
|
+
`0.1.0` 的 `files` 漏了 `scripts/`,于是装完 `dsh-foreman@0.1.0` 之后
|
|
91
|
+
`node scripts/add-device.mjs` **不存在** —— 而那是加设备的唯一入口,
|
|
92
|
+
文档里却写着让人这么跑。现在随包发三个脚本:
|
|
93
|
+
|
|
94
|
+
- `scripts/add-device.mjs`(加设备 / 换令牌 / 吊销)
|
|
95
|
+
- `scripts/migrate.mjs`(迁机打包与自检)
|
|
96
|
+
- `scripts/dsh-foreman`(服务脚本)
|
|
97
|
+
|
|
98
|
+
`scripts/sync-wsl.sh`、`scripts/deploy-preview.sh` 是开发用的,**不发布**。
|
|
99
|
+
|
|
100
|
+
### 修复:一个不带正文的帧会崩掉握手
|
|
101
|
+
|
|
102
|
+
`ws.ts` 把收到的任何消息一律 `.toString()`,于是 payload 缺失时会得到字符串 `"undefined"`,
|
|
103
|
+
再被当成帧去解析。现在只接受真的带文本的帧(字符串 / Buffer / Buffer[]),
|
|
104
|
+
其余直接忽略,并在握手阶段给出可读原因。
|
|
105
|
+
|
|
106
|
+
## 0.1.0 — 2026-11-07
|
|
107
|
+
|
|
108
|
+
首个公开发布。**一个常驻主 loop,把工单派给多台在线设备上各自完整运行的 DSH,账本只有一份。**
|
|
109
|
+
|
|
110
|
+
### 新增:设备与派单
|
|
111
|
+
|
|
112
|
+
- **设备注册表**(`src/foreman/devices.ts`):同一份 SQLite 里的 `foreman-device:` 家族,
|
|
113
|
+
令牌只存 SHA-256。加设备、换令牌、吊销都不需要重启主 loop。
|
|
114
|
+
CLI:`scripts/add-device.mjs add | list | rotate | revoke`。
|
|
115
|
+
- **Selector**(`src/foreman/selector.ts`):纯函数。吊销的、离线的、没有对应项目 root 的设备先出局;
|
|
116
|
+
显式点名或标签要求再收窄;剩下的按在跑数量最少,平局取最近心跳。
|
|
117
|
+
- **选择落在 accept 上**:`Ticket.target` 在 accept 时确定,`newAssignment` 复制到
|
|
118
|
+
`Assignment.target`。选设备在 `prepareTicket` 缝上,**不在** Executor 里——Executor 没有写库通道。
|
|
119
|
+
- **ForemanExecutor**(`src/foreman/executor.ts`):本机目标原样交给 `DshExecutor`;
|
|
120
|
+
远端目标变成一个 `assign` 帧。设备不可达时报 `blocked`,**不伪造 failed,也不悄悄改成本机执行**。
|
|
121
|
+
- **Foreman Hub**(`src/foreman/hub.ts` + `ws.ts`):设备主动外连,`ws` 只出现在一个文件里,
|
|
122
|
+
只监听 `127.0.0.1`(对外由隧道终结 TLS)。握手校验令牌与协议版本;吊销在下一帧就生效。
|
|
123
|
+
- **文本渲染**(`src/foreman/render.ts`):任务文本在主 loop 侧定稿,**不带任何主 loop 路径**;
|
|
124
|
+
附件走 base64 字节,按编码后大小卡 256 KiB 上限。
|
|
125
|
+
|
|
126
|
+
### 新增:断线与恢复
|
|
127
|
+
|
|
128
|
+
- **断线绝不自动重派**。`assignmentInterrupted` 会走 `Tickets.restart` 立刻重开一张,
|
|
129
|
+
而设备可能还在跑——那是「同一件事执行两遍」。超时只转 `blocked`,交给人。
|
|
130
|
+
- Hub 重启时,在飞的远端 assignment 一律先转 `blocked`,等设备 `hello` 对齐;
|
|
131
|
+
`DshExecutor.resume()` 按 target 跳过远端项。
|
|
132
|
+
- 设备重连后先 `hello` 报「我在跑哪些」,Hub 对齐而不是假设。
|
|
133
|
+
|
|
134
|
+
### 新增:设备侧插件(另一个包)
|
|
135
|
+
|
|
136
|
+
- [`dsh-foreman-node`](https://www.npmjs.com/package/dsh-foreman-node):连出去、接单、回报三件事。
|
|
137
|
+
job 会话是 `job-<assignmentId>`,权限是 **`workspace-write`**(不是主 loop 自己的 `danger-full-access`:
|
|
138
|
+
笔记本前没人,弹审批等于卡死)。
|
|
139
|
+
- 设备上注册 `foreman_report` 工具(`progress` / `ready` / `blocked` / `failed`)。
|
|
140
|
+
**`dropped` 不允许**:那是判断,设备没有判断权。
|
|
141
|
+
|
|
142
|
+
### 新增:面板
|
|
143
|
+
|
|
144
|
+
- 「设置 › 设备」:在线点、在跑数、最近活动、项目名(**不显示设备自己的路径**)、吊销与换令牌。
|
|
145
|
+
|
|
146
|
+
### 新增:飞书群即项目
|
|
147
|
+
|
|
148
|
+
- Policy 的 `routing.chats`:群 → 工作区,同时约束设备选择(内网群只选带 `intranet` 标签的设备)。
|
|
149
|
+
映射按 settings 修订号缓存,改了对新到的消息生效。
|
|
150
|
+
|
|
151
|
+
### 新增:迁移
|
|
152
|
+
|
|
153
|
+
- `scripts/migrate.mjs`:`plan` / `checkpoint` / `pack` / `verify`。
|
|
154
|
+
必须先 checkpoint——库跑在 WAL 模式,只拷 `.sqlite` 会**静默丢掉**最近的工单。
|
|
155
|
+
- [docs/migration.md](docs/migration.md) 五步 runbook。
|
|
156
|
+
|
|
157
|
+
### 依赖
|
|
158
|
+
|
|
159
|
+
- 唯一新增的运行时依赖是 `ws@8`。
|
|
160
|
+
- `@deepseek-ai/*` 全是 `peerDependencies`,由装了本插件的 DSH 实例提供。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 0.2.0 — 2026-11-01(上游基线,非本项目发布)
|
|
165
|
+
|
|
166
|
+
> 这一节记录的是代码基线 `dsh-superagent-local` 相对上游 `dsh-superagent-prototype` 的改动。
|
|
167
|
+
> 本项目的 `0.1.0` 已包含其中全部内容。
|
|
6
168
|
|
|
7
169
|
### 新增:需求来自本机操作者
|
|
8
170
|
|
package/lib/client.js
CHANGED
|
@@ -4614,6 +4614,13 @@ window.__ModuleLoader__.load({
|
|
|
4614
4614
|
deviceRevoked: ["已吊销", "Revoked"],
|
|
4615
4615
|
deviceRunning: ["在跑 {count} 件", "{count} running"],
|
|
4616
4616
|
deviceLastSeen: ["最近活动", "Last seen"],
|
|
4617
|
+
deviceEdit: ["编辑", "Edit"],
|
|
4618
|
+
deviceEditHint: ["改显示名、标签和能接的项目。id 和令牌不在这里改:id 已经写在那台机器的配置里,换令牌要先把新令牌给它。", "Change the display name, labels and projects. The id and token are not editable here: the id is already in that machine's configuration, and a new token must be handed to it first."],
|
|
4619
|
+
deviceLabels: ["标签", "Labels"],
|
|
4620
|
+
deviceLabelsHint: ["逗号分隔。群聊可以要求某个标签,只有带这个标签的设备才接得到。", "Comma separated. A chat can require a label, so only devices carrying it can take that work."],
|
|
4621
|
+
devicePathKeep: ["留空即保持原路径", "Leave empty to keep the current path"],
|
|
4622
|
+
deviceDelete: ["删除", "Delete"],
|
|
4623
|
+
deviceDeleteConfirm: ["确认删除", "Confirm delete"],
|
|
4617
4624
|
deviceRevoke: ["吊销", "Revoke"],
|
|
4618
4625
|
deviceRevokeConfirm: ["确认吊销", "Confirm revoke"],
|
|
4619
4626
|
deviceRotate: ["换令牌", "New token"],
|
|
@@ -5561,6 +5568,7 @@ window.__ModuleLoader__.load({
|
|
|
5561
5568
|
const { t, call } = usePanel();
|
|
5562
5569
|
const { data, error, reload } = usePoll(() => call("GET devices"), []);
|
|
5563
5570
|
const [rotated, setRotated] = (0, react.useState)();
|
|
5571
|
+
const [editing, setEditing] = (0, react.useState)();
|
|
5564
5572
|
const revoke = async (id) => {
|
|
5565
5573
|
await call("POST devices/:id/revoke", { id });
|
|
5566
5574
|
await reload();
|
|
@@ -5572,6 +5580,10 @@ window.__ModuleLoader__.load({
|
|
|
5572
5580
|
});
|
|
5573
5581
|
await reload();
|
|
5574
5582
|
};
|
|
5583
|
+
const remove = async (id) => {
|
|
5584
|
+
await call("DELETE devices/:id", { id });
|
|
5585
|
+
await reload();
|
|
5586
|
+
};
|
|
5575
5587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5576
5588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageHead, {
|
|
5577
5589
|
back: false,
|
|
@@ -5615,11 +5627,20 @@ window.__ModuleLoader__.load({
|
|
|
5615
5627
|
children: data.rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceLine, {
|
|
5616
5628
|
row,
|
|
5617
5629
|
onRevoke: () => revoke(row.id),
|
|
5618
|
-
onRotate: () => rotate(row.id)
|
|
5630
|
+
onRotate: () => rotate(row.id),
|
|
5631
|
+
onEdit: () => setEditing(row),
|
|
5632
|
+
onDelete: () => remove(row.id)
|
|
5619
5633
|
}, row.id))
|
|
5620
5634
|
}) : void 0
|
|
5621
5635
|
})
|
|
5622
5636
|
}),
|
|
5637
|
+
editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditDevice, {
|
|
5638
|
+
row: editing,
|
|
5639
|
+
onClose: () => setEditing(void 0),
|
|
5640
|
+
onSaved: () => {
|
|
5641
|
+
reload();
|
|
5642
|
+
}
|
|
5643
|
+
}),
|
|
5623
5644
|
rotated && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
5624
5645
|
title: t("devicesRotated"),
|
|
5625
5646
|
tone: "warn",
|
|
@@ -5638,10 +5659,11 @@ window.__ModuleLoader__.load({
|
|
|
5638
5659
|
})
|
|
5639
5660
|
] });
|
|
5640
5661
|
}
|
|
5641
|
-
/** One device: name, online dot, load, project names, last seen, and the
|
|
5642
|
-
function DeviceLine({ row, onRevoke, onRotate }) {
|
|
5662
|
+
/** One device: name, online dot, load, project names, last seen, and the actions an operator needs. */
|
|
5663
|
+
function DeviceLine({ row, onRevoke, onRotate, onEdit, onDelete }) {
|
|
5643
5664
|
const { t } = usePanel();
|
|
5644
5665
|
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
5666
|
+
const [deleting, setDeleting] = (0, react.useState)(false);
|
|
5645
5667
|
const tone = row.revoked ? "idle" : row.online ? "done" : "warning";
|
|
5646
5668
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
5647
5669
|
className: "dt-row",
|
|
@@ -5683,31 +5705,58 @@ window.__ModuleLoader__.load({
|
|
|
5683
5705
|
})]
|
|
5684
5706
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5685
5707
|
className: "dt-row-actions",
|
|
5686
|
-
children: [
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
},
|
|
5693
|
-
children:
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5708
|
+
children: [
|
|
5709
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5710
|
+
variant: "ghost",
|
|
5711
|
+
size: "sm",
|
|
5712
|
+
onClick: onEdit,
|
|
5713
|
+
children: t("deviceEdit")
|
|
5714
|
+
}),
|
|
5715
|
+
!row.revoked && (confirming ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5716
|
+
variant: "danger",
|
|
5717
|
+
size: "sm",
|
|
5718
|
+
onClick: () => {
|
|
5719
|
+
setConfirming(false);
|
|
5720
|
+
onRevoke();
|
|
5721
|
+
},
|
|
5722
|
+
children: t("deviceRevokeConfirm")
|
|
5723
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5724
|
+
size: "sm",
|
|
5725
|
+
onClick: () => setConfirming(false),
|
|
5726
|
+
children: t("cancel")
|
|
5727
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5728
|
+
variant: "ghost",
|
|
5729
|
+
size: "sm",
|
|
5730
|
+
onClick: () => setConfirming(true),
|
|
5731
|
+
children: t("deviceRevoke")
|
|
5732
|
+
})),
|
|
5733
|
+
!row.revoked && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5734
|
+
variant: "ghost",
|
|
5735
|
+
size: "sm",
|
|
5736
|
+
onClick: () => {
|
|
5737
|
+
onRotate();
|
|
5738
|
+
},
|
|
5739
|
+
children: t("deviceRotate")
|
|
5740
|
+
}),
|
|
5741
|
+
deleting ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5742
|
+
variant: "danger",
|
|
5743
|
+
size: "sm",
|
|
5744
|
+
onClick: () => {
|
|
5745
|
+
setDeleting(false);
|
|
5746
|
+
onDelete();
|
|
5747
|
+
},
|
|
5748
|
+
children: t("deviceDeleteConfirm")
|
|
5749
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5750
|
+
size: "sm",
|
|
5751
|
+
onClick: () => setDeleting(false),
|
|
5752
|
+
children: t("cancel")
|
|
5753
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5754
|
+
variant: "ghost",
|
|
5755
|
+
size: "sm",
|
|
5756
|
+
onClick: () => setDeleting(true),
|
|
5757
|
+
children: t("deviceDelete")
|
|
5758
|
+
})
|
|
5759
|
+
]
|
|
5711
5760
|
})]
|
|
5712
5761
|
});
|
|
5713
5762
|
}
|
|
@@ -5848,6 +5897,118 @@ window.__ModuleLoader__.load({
|
|
|
5848
5897
|
["token", minted.token]
|
|
5849
5898
|
];
|
|
5850
5899
|
}
|
|
5900
|
+
/**
|
|
5901
|
+
* Editing what a device offers: its display name, its labels, and the projects it can work on.
|
|
5902
|
+
*
|
|
5903
|
+
* The id and the token are not editable here on purpose. The id is what the device's own configuration already says,
|
|
5904
|
+
* and changing it would need the machine in front of you; a token is rotated deliberately, because the machine must
|
|
5905
|
+
* be given the new one before it can connect again.
|
|
5906
|
+
*/
|
|
5907
|
+
function EditDevice({ row, onClose, onSaved }) {
|
|
5908
|
+
const { t, call } = usePanel();
|
|
5909
|
+
const [name, setName] = (0, react.useState)(row.name);
|
|
5910
|
+
const [labels, setLabels] = (0, react.useState)(row.labels.join(", "));
|
|
5911
|
+
const [roots, setRoots] = (0, react.useState)(row.projects.map((project) => ({
|
|
5912
|
+
project,
|
|
5913
|
+
path: ""
|
|
5914
|
+
})));
|
|
5915
|
+
const [refusal, setRefusal] = (0, react.useState)();
|
|
5916
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5917
|
+
const save = async () => {
|
|
5918
|
+
setBusy(true);
|
|
5919
|
+
setRefusal(void 0);
|
|
5920
|
+
try {
|
|
5921
|
+
await call("PUT devices/:id", {
|
|
5922
|
+
id: row.id,
|
|
5923
|
+
body: {
|
|
5924
|
+
name: name.trim(),
|
|
5925
|
+
labels: labels.split(",").map((label) => label.trim()).filter(Boolean),
|
|
5926
|
+
roots: roots.filter((root) => root.project.trim() && root.path.trim()).map((root) => ({
|
|
5927
|
+
project: root.project.trim(),
|
|
5928
|
+
path: root.path.trim()
|
|
5929
|
+
}))
|
|
5930
|
+
}
|
|
5931
|
+
});
|
|
5932
|
+
onSaved();
|
|
5933
|
+
onClose();
|
|
5934
|
+
} catch (error) {
|
|
5935
|
+
setRefusal(errorText(error));
|
|
5936
|
+
} finally {
|
|
5937
|
+
setBusy(false);
|
|
5938
|
+
}
|
|
5939
|
+
};
|
|
5940
|
+
const edit = (index, patch) => setRoots((current) => current.map((root, at) => at === index ? {
|
|
5941
|
+
...root,
|
|
5942
|
+
...patch
|
|
5943
|
+
} : root));
|
|
5944
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
5945
|
+
title: `${t("deviceEdit")} · ${row.id}`,
|
|
5946
|
+
hint: t("deviceEditHint"),
|
|
5947
|
+
children: [
|
|
5948
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FieldGrid, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5949
|
+
label: t("devicesAddName"),
|
|
5950
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
5951
|
+
value: name,
|
|
5952
|
+
onChange: (event) => setName(event.target.value)
|
|
5953
|
+
})
|
|
5954
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5955
|
+
label: t("deviceLabels"),
|
|
5956
|
+
note: t("deviceLabelsHint"),
|
|
5957
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
5958
|
+
value: labels,
|
|
5959
|
+
onChange: (event) => setLabels(event.target.value),
|
|
5960
|
+
placeholder: "intranet, office"
|
|
5961
|
+
})
|
|
5962
|
+
})] }),
|
|
5963
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldGrid, { children: roots.map((root, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5964
|
+
label: `${t("devicesAddProject")} ${index + 1}`,
|
|
5965
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
5966
|
+
value: root.project,
|
|
5967
|
+
onChange: (event) => edit(index, { project: event.target.value })
|
|
5968
|
+
})
|
|
5969
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
5970
|
+
label: `${t("devicesAddPath")} ${index + 1}`,
|
|
5971
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Input, {
|
|
5972
|
+
value: root.path,
|
|
5973
|
+
onChange: (event) => edit(index, { path: event.target.value }),
|
|
5974
|
+
placeholder: t("devicePathKeep")
|
|
5975
|
+
})
|
|
5976
|
+
})] }, index)) }),
|
|
5977
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5978
|
+
className: "dt-form-actions",
|
|
5979
|
+
children: [
|
|
5980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5981
|
+
size: "sm",
|
|
5982
|
+
onClick: () => setRoots((current) => [...current, {
|
|
5983
|
+
project: "",
|
|
5984
|
+
path: ""
|
|
5985
|
+
}]),
|
|
5986
|
+
"data-edit-root": "1",
|
|
5987
|
+
children: t("devicesAddRoot")
|
|
5988
|
+
}),
|
|
5989
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5990
|
+
size: "sm",
|
|
5991
|
+
variant: "primary",
|
|
5992
|
+
busy,
|
|
5993
|
+
onClick: () => {
|
|
5994
|
+
save();
|
|
5995
|
+
},
|
|
5996
|
+
children: t("save")
|
|
5997
|
+
}),
|
|
5998
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
5999
|
+
size: "sm",
|
|
6000
|
+
onClick: onClose,
|
|
6001
|
+
children: t("cancel")
|
|
6002
|
+
})
|
|
6003
|
+
]
|
|
6004
|
+
}),
|
|
6005
|
+
refusal !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorLine, {
|
|
6006
|
+
error: refusal,
|
|
6007
|
+
title: t("loadFailed")
|
|
6008
|
+
})
|
|
6009
|
+
]
|
|
6010
|
+
});
|
|
6011
|
+
}
|
|
5851
6012
|
//#endregion
|
|
5852
6013
|
//#region src/client/logs.tsx
|
|
5853
6014
|
/**
|
|
@@ -8327,10 +8488,15 @@ window.__ModuleLoader__.load({
|
|
|
8327
8488
|
return key.startsWith("workspace:") ? key.slice(10) : void 0;
|
|
8328
8489
|
};
|
|
8329
8490
|
/**
|
|
8330
|
-
*
|
|
8331
|
-
*
|
|
8491
|
+
* 插件自己建的会话里,只有主会话被藏起来。
|
|
8492
|
+
*
|
|
8493
|
+
* `orchestrator-` 是主会话本身,人用行上的 M 气泡进,侧栏再列一行就是重复。
|
|
8494
|
+
*
|
|
8495
|
+
* `worker-` 以前也藏,理由是「数量会多」。但那让人失去唯一的线索:任务到底在哪台机器、哪个目录里跑。
|
|
8496
|
+
* 一张工单派到别的设备时,设备上那条会话就是答案,而它恰恰被藏掉了。现在保留显示:多一行,
|
|
8497
|
+
* 换来看得见工作发生的地方。
|
|
8332
8498
|
*/
|
|
8333
|
-
const ours = (id) => id.startsWith("
|
|
8499
|
+
const ours = (id) => id.startsWith("orchestrator-");
|
|
8334
8500
|
const rowId = (row) => (row.getAttribute("data-row-key") ?? "").replace(/^session:/, "");
|
|
8335
8501
|
/**
|
|
8336
8502
|
* 收起插件自己的会话行,以及状态目录那一个伪工作区行。
|
package/lib/feishu-watch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as Trigger, L as Thread } from "./intake-
|
|
2
|
-
import { g as Tally, h as SourceRegistry, n as SourceContext, t as SourceKind } from "./kind-
|
|
1
|
+
import { G as Trigger, L as Thread } from "./intake-B1UsoHuw.js";
|
|
2
|
+
import { g as Tally, h as SourceRegistry, n as SourceContext, t as SourceKind } from "./kind-BYtP14oM.js";
|
|
3
3
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/sources/feishu-read.d.ts
|
package/lib/github-watch.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as apply, c as name, d as pollThreads, f as pollTicketPulls, i as Retry, l as pollFailure, m as commentTrigger, o as githubKind, p as GithubComment, r as GithubConfig, s as inject, u as pollRound } from "./kind-
|
|
1
|
+
import { a as apply, c as name, d as pollThreads, f as pollTicketPulls, i as Retry, l as pollFailure, m as commentTrigger, o as githubKind, p as GithubComment, r as GithubConfig, s as inject, u as pollRound } from "./kind-BYtP14oM.js";
|
|
2
2
|
export { type GithubComment, GithubConfig, Retry, apply, commentTrigger, githubKind, inject, name, pollFailure, pollRound, pollThreads, pollTicketPulls };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as reached, A as LOCAL_TARGET, B as ThreadRef, C as ChannelRef, D as Execution, E as Evaluation, F as Strength, G as Trigger, H as TicketEndBy, I as Text, J as followupOwners, K as TriggerDisposition, L as Thread, M as MandateBasis, N as PolicyEffect, O as Follow, P as ReportState, Q as liveTicket, R as ThreadFact, S as Attention, T as DecisionKind, U as TicketInput, V as Ticket, W as TicketOutcome, X as latestAssignment, Y as isoTime, Z as latestReport, _ as ActorRef, a as DomainEvent, at as ticketOutcome, b as AssignmentState, c as GateResult, ct as Reason, d as Policy, dt as said, et as readUntil, f as ReplyStatus, ft as Config, g as AcceptMode, h as SourceScope, i as Candidate, it as threadKey, j as Mandate, k as FollowupRecords, l as OutboundMessage, lt as ReasonCode, m as ScopeResult, mt as DeploymentConfig, n as AssessmentOutcome, nt as settled, o as DomainEvents, ot as waitsForPerson, p as Repositories, pt as Deployment, q as assignmentTargetOf, r as Assessor, rt as targetOf, s as Executor, st as REASONS, t as ReceiveResult, tt as runningAssignment, u as Outbox, ut as reasonText, v as AdmissionCell, w as Decision, x as AssignmentTarget, y as Assignment, z as ThreadFacts } from "./intake-
|
|
1
|
+
import { $ as reached, A as LOCAL_TARGET, B as ThreadRef, C as ChannelRef, D as Execution, E as Evaluation, F as Strength, G as Trigger, H as TicketEndBy, I as Text, J as followupOwners, K as TriggerDisposition, L as Thread, M as MandateBasis, N as PolicyEffect, O as Follow, P as ReportState, Q as liveTicket, R as ThreadFact, S as Attention, T as DecisionKind, U as TicketInput, V as Ticket, W as TicketOutcome, X as latestAssignment, Y as isoTime, Z as latestReport, _ as ActorRef, a as DomainEvent, at as ticketOutcome, b as AssignmentState, c as GateResult, ct as Reason, d as Policy, dt as said, et as readUntil, f as ReplyStatus, ft as Config, g as AcceptMode, h as SourceScope, i as Candidate, it as threadKey, j as Mandate, k as FollowupRecords, l as OutboundMessage, lt as ReasonCode, m as ScopeResult, mt as DeploymentConfig, n as AssessmentOutcome, nt as settled, o as DomainEvents, ot as waitsForPerson, p as Repositories, pt as Deployment, q as assignmentTargetOf, r as Assessor, rt as targetOf, s as Executor, st as REASONS, t as ReceiveResult, tt as runningAssignment, u as Outbox, ut as reasonText, v as AdmissionCell, w as Decision, x as AssignmentTarget, y as Assignment, z as ThreadFacts } from "./intake-B1UsoHuw.js";
|
|
2
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
3
|
//#region src/core/part.d.ts
|
|
4
4
|
interface EngineOptions {
|
package/lib/index.js
CHANGED
|
@@ -5491,6 +5491,13 @@ var DshExecutor = class {
|
|
|
5491
5491
|
const ticket = this.host.store.tickets.get(ticketId);
|
|
5492
5492
|
const assignment = ticket && latestAssignment(ticket);
|
|
5493
5493
|
if (!ticket || !liveTicket(ticket) || assignment?.id !== assignmentId || assignment.state !== "pending") return;
|
|
5494
|
+
if (ticket.blockedReason !== void 0) {
|
|
5495
|
+
this.host.engine.report(ticketId, {
|
|
5496
|
+
state: "blocked",
|
|
5497
|
+
summary: ticket.blockedReason
|
|
5498
|
+
});
|
|
5499
|
+
return;
|
|
5500
|
+
}
|
|
5494
5501
|
this.slots.reserve(ticketId, "launch");
|
|
5495
5502
|
try {
|
|
5496
5503
|
await this.launch(ticket, assignment);
|
|
@@ -9197,6 +9204,28 @@ function routes(runtime) {
|
|
|
9197
9204
|
runtime.revokeDevice(decodeURIComponent(params.id));
|
|
9198
9205
|
return { revoked: true };
|
|
9199
9206
|
},
|
|
9207
|
+
/** 删掉一台设备。和吊销不同:吊销留着记录,删除连记录一起去掉。 */
|
|
9208
|
+
"DELETE devices/:id": ({ params }) => {
|
|
9209
|
+
runtime.deleteDevice(decodeURIComponent(params.id));
|
|
9210
|
+
return { deleted: true };
|
|
9211
|
+
},
|
|
9212
|
+
/** 改一台设备的名字、标签或项目;id 与令牌不动,所以那台设备不用重新配置。 */
|
|
9213
|
+
"PUT devices/:id": async ({ params, body: readBody }) => {
|
|
9214
|
+
const body = z.object({
|
|
9215
|
+
name: z.string().trim().min(1).optional(),
|
|
9216
|
+
labels: z.array(z.string().trim().min(1)).optional(),
|
|
9217
|
+
roots: z.array(z.object({
|
|
9218
|
+
project: z.string().trim().min(1),
|
|
9219
|
+
path: z.string().trim().min(1)
|
|
9220
|
+
})).optional()
|
|
9221
|
+
}).parse(await readBody());
|
|
9222
|
+
try {
|
|
9223
|
+
runtime.updateDevice(decodeURIComponent(params.id), body);
|
|
9224
|
+
} catch (error) {
|
|
9225
|
+
throw new RouteError(400, error instanceof Error ? error.message : String(error));
|
|
9226
|
+
}
|
|
9227
|
+
return { updated: true };
|
|
9228
|
+
},
|
|
9200
9229
|
/** 轮换令牌:旧令牌立刻失效,新令牌只返回这一次。 */
|
|
9201
9230
|
"POST devices/:id/rotate": ({ params }) => ({ token: runtime.rotateDeviceToken(decodeURIComponent(params.id)) }),
|
|
9202
9231
|
"GET prompts": ({ query }) => {
|
|
@@ -9504,6 +9533,36 @@ var Devices = class {
|
|
|
9504
9533
|
revokedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9505
9534
|
});
|
|
9506
9535
|
}
|
|
9536
|
+
/**
|
|
9537
|
+
* Delete a device outright, revoked or not.
|
|
9538
|
+
*
|
|
9539
|
+
* Revoking keeps the record for the audit trail; deleting is for the mistake — a device registered under the wrong
|
|
9540
|
+
* id, or one that will never come back — that an operator does not want cluttering the list forever. The two are
|
|
9541
|
+
* deliberately separate actions: revoke is the safe one, delete is the one that loses the history.
|
|
9542
|
+
*
|
|
9543
|
+
* @throws When the device is unknown, so a typo is reported rather than silently removing nothing.
|
|
9544
|
+
*/
|
|
9545
|
+
delete(id) {
|
|
9546
|
+
this.require(id);
|
|
9547
|
+
this.store.delete(KV.foreman.device(id));
|
|
9548
|
+
}
|
|
9549
|
+
/**
|
|
9550
|
+
* Replace what a device offers: its display name, its labels, and the projects it can work on.
|
|
9551
|
+
*
|
|
9552
|
+
* The id and the token are not touched: an id is what the device's own configuration already says, and rotating a
|
|
9553
|
+
* token is a separate decision with its own effect (the device must be given the new one). A handshake in flight
|
|
9554
|
+
* keeps using the record it read, which is why the hub drops the connection after a change.
|
|
9555
|
+
*
|
|
9556
|
+
* @throws When the device is unknown.
|
|
9557
|
+
*/
|
|
9558
|
+
update(id, change) {
|
|
9559
|
+
const next = { ...this.require(id) };
|
|
9560
|
+
if (change.name !== void 0) next.name = change.name;
|
|
9561
|
+
if (change.labels !== void 0) next.labels = [...change.labels];
|
|
9562
|
+
if (change.roots !== void 0) next.roots = change.roots.map((root) => ({ ...root }));
|
|
9563
|
+
this.store.set(KV.foreman.device(id), next);
|
|
9564
|
+
return next;
|
|
9565
|
+
}
|
|
9507
9566
|
/** Whether a token is this device's and the device is still allowed to connect; `false` for an unknown or revoked id. */
|
|
9508
9567
|
verify(id, token) {
|
|
9509
9568
|
const device = this.get(id);
|
|
@@ -9563,6 +9622,20 @@ function attachmentSection(names) {
|
|
|
9563
9622
|
*
|
|
9564
9623
|
* @throws When the attachments together exceed the frame budget: a truncated task would be worse than a refused one.
|
|
9565
9624
|
*/
|
|
9625
|
+
/**
|
|
9626
|
+
* What a device is told about reporting, appended to every assignment.
|
|
9627
|
+
*
|
|
9628
|
+
* Without it the job Session simply answers and stops: on a real deployment a device did thirty steps of real work,
|
|
9629
|
+
* wrote a complete answer, and never called `foreman_report`, so the hub sat at `running` with no report forever and
|
|
9630
|
+
* a person saw a job that never finished. The tool exists, but nothing told the model it was required or final.
|
|
9631
|
+
*/
|
|
9632
|
+
const REPORT_SECTION = [
|
|
9633
|
+
"\n## Reporting back\n",
|
|
9634
|
+
"\nWhen you have finished, call `foreman_report` — that tool is how this job reaches the person who asked, and ",
|
|
9635
|
+
"answering in chat does not reach them. Report `ready` with a one-sentence summary of the result, or `blocked` with ",
|
|
9636
|
+
"the one question only they can answer, or `failed` if the work cannot be done. The job is not finished until you ",
|
|
9637
|
+
"have reported.\n"
|
|
9638
|
+
].join("");
|
|
9566
9639
|
function renderForDevice(input) {
|
|
9567
9640
|
const { ticket, trigger } = input;
|
|
9568
9641
|
const refs = refsOf(trigger);
|
|
@@ -9586,7 +9659,8 @@ function renderForDevice(input) {
|
|
|
9586
9659
|
`# ${ticket.title}`,
|
|
9587
9660
|
scope === "" ? "" : `\n${scope}\n`,
|
|
9588
9661
|
request === "" ? "" : `\n## The request as it was written\n\n${request}\n`,
|
|
9589
|
-
attachmentSection(attachments.map((attachment) => attachment.name ?? attachment.id))
|
|
9662
|
+
attachmentSection(attachments.map((attachment) => attachment.name ?? attachment.id)),
|
|
9663
|
+
REPORT_SECTION
|
|
9590
9664
|
].filter((section) => section !== "").join(""),
|
|
9591
9665
|
attachments
|
|
9592
9666
|
};
|
|
@@ -9789,6 +9863,8 @@ var ForemanExecutor = class {
|
|
|
9789
9863
|
*/
|
|
9790
9864
|
var Hub = class {
|
|
9791
9865
|
connections = /* @__PURE__ */ new Map();
|
|
9866
|
+
/** Connections that have arrived but not yet verified: parked so they cannot disturb an established one. */
|
|
9867
|
+
pending = /* @__PURE__ */ new Map();
|
|
9792
9868
|
devices;
|
|
9793
9869
|
onFrame;
|
|
9794
9870
|
log;
|
|
@@ -9799,9 +9875,16 @@ var Hub = class {
|
|
|
9799
9875
|
this.log = input.log ?? (() => {});
|
|
9800
9876
|
this.now = input.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
9801
9877
|
}
|
|
9802
|
-
/**
|
|
9878
|
+
/**
|
|
9879
|
+
* Register a connection under the device id the transport says it belongs to; it is a claim until `hello`.
|
|
9880
|
+
*
|
|
9881
|
+
* A new connection does NOT evict an established one here. Evicting on arrival let two clients using the same id
|
|
9882
|
+
* close each other in a loop — each reconnect killed the other's socket, neither ever finished its handshake, and
|
|
9883
|
+
* the device stayed offline forever while both kept retrying. Instead a fresh connection waits in `pending` until
|
|
9884
|
+
* its `hello` verifies, and only then replaces whoever held the id (`admit`). A half-finished connection therefore
|
|
9885
|
+
* cannot disturb a working one, while a genuine reconnect still takes over once it has proved itself.
|
|
9886
|
+
*/
|
|
9803
9887
|
attach(deviceId, channel) {
|
|
9804
|
-
this.connections.get(deviceId)?.channel.close();
|
|
9805
9888
|
const connection = {
|
|
9806
9889
|
channel,
|
|
9807
9890
|
handshook: false,
|
|
@@ -9809,12 +9892,23 @@ var Hub = class {
|
|
|
9809
9892
|
lastSeenAt: this.now(),
|
|
9810
9893
|
dedupe: new FrameDedupe(/* @__PURE__ */ new Set())
|
|
9811
9894
|
};
|
|
9812
|
-
this.
|
|
9895
|
+
this.pending.set(channel, {
|
|
9896
|
+
deviceId,
|
|
9897
|
+
connection
|
|
9898
|
+
});
|
|
9813
9899
|
channel.onMessage((text) => this.receive(deviceId, connection, text));
|
|
9814
9900
|
channel.onClose(() => {
|
|
9901
|
+
this.pending.delete(channel);
|
|
9815
9902
|
if (this.connections.get(deviceId) === connection) this.connections.delete(deviceId);
|
|
9816
9903
|
});
|
|
9817
9904
|
}
|
|
9905
|
+
/** Promote a verified connection to be the one the id answers to, replacing whatever held it. */
|
|
9906
|
+
admit(deviceId, connection) {
|
|
9907
|
+
const previous = this.connections.get(deviceId);
|
|
9908
|
+
if (previous !== void 0 && previous !== connection) previous.channel.close();
|
|
9909
|
+
this.connections.set(deviceId, connection);
|
|
9910
|
+
this.pending.delete(connection.channel);
|
|
9911
|
+
}
|
|
9818
9912
|
/** Drop a device's connection and stop listing it as online. */
|
|
9819
9913
|
detach(deviceId) {
|
|
9820
9914
|
const connection = this.connections.get(deviceId);
|
|
@@ -9855,6 +9949,8 @@ var Hub = class {
|
|
|
9855
9949
|
}
|
|
9856
9950
|
/** Close every device connection, used at shutdown so in-flight work fails fast instead of hanging. */
|
|
9857
9951
|
closeAll() {
|
|
9952
|
+
for (const [, parked] of [...this.pending]) parked.connection.channel.close();
|
|
9953
|
+
this.pending.clear();
|
|
9858
9954
|
for (const [id, connection] of [...this.connections]) {
|
|
9859
9955
|
this.connections.delete(id);
|
|
9860
9956
|
connection.channel.close();
|
|
@@ -9915,6 +10011,7 @@ var Hub = class {
|
|
|
9915
10011
|
connection.running = [...frame.running];
|
|
9916
10012
|
connection.runningCount = connection.running.length;
|
|
9917
10013
|
connection.lastSeenAt = this.now();
|
|
10014
|
+
this.admit(deviceId, connection);
|
|
9918
10015
|
this.devices.seen(deviceId, connection.lastSeenAt);
|
|
9919
10016
|
this.onFrame(deviceId, frame);
|
|
9920
10017
|
}
|
|
@@ -9929,6 +10026,7 @@ var Hub = class {
|
|
|
9929
10026
|
}));
|
|
9930
10027
|
} catch {}
|
|
9931
10028
|
this.log(`foreman hub refused a device: ${reason}`);
|
|
10029
|
+
this.pending.delete(connection.channel);
|
|
9932
10030
|
connection.channel.close();
|
|
9933
10031
|
for (const [id, current] of this.connections) if (current === connection) this.connections.delete(id);
|
|
9934
10032
|
}
|
|
@@ -10180,6 +10278,14 @@ function prepareTicketWithForeman(input) {
|
|
|
10180
10278
|
const labels = input.labelsOf?.(trigger);
|
|
10181
10279
|
if (labels !== void 0 && labels.length > 0) request.labels = labels;
|
|
10182
10280
|
const target = select(request, input.devices(), input.online());
|
|
10281
|
+
const mustBeRemote = request.device !== void 0 || request.labels !== void 0 && request.labels.length > 0;
|
|
10282
|
+
if (target === void 0 && mustBeRemote) {
|
|
10283
|
+
const blockedReason = `no online device can take this: it asked for ${request.device !== void 0 ? `device "${request.device}"` : "a restricted machine"} on project "${request.project}"; nothing was run`;
|
|
10284
|
+
return {
|
|
10285
|
+
...ticket,
|
|
10286
|
+
blockedReason
|
|
10287
|
+
};
|
|
10288
|
+
}
|
|
10183
10289
|
const prepared = input.local({
|
|
10184
10290
|
...ticket,
|
|
10185
10291
|
...target === void 0 ? {} : { target }
|
|
@@ -12415,6 +12521,14 @@ var Runtime = class extends Service {
|
|
|
12415
12521
|
const token = runtime.devices.rotate(id);
|
|
12416
12522
|
runtime.hub?.detach(id);
|
|
12417
12523
|
return token;
|
|
12524
|
+
},
|
|
12525
|
+
deleteDevice: (id) => {
|
|
12526
|
+
runtime.hub?.detach(id);
|
|
12527
|
+
runtime.devices.delete(id);
|
|
12528
|
+
},
|
|
12529
|
+
updateDevice: (id, change) => {
|
|
12530
|
+
runtime.devices.update(id, change);
|
|
12531
|
+
runtime.hub?.detach(id);
|
|
12418
12532
|
}
|
|
12419
12533
|
};
|
|
12420
12534
|
}
|
|
@@ -485,6 +485,14 @@ interface Ticket {
|
|
|
485
485
|
* It lives on the Ticket because `newAssignment` never sees the accept outcome that made the choice.
|
|
486
486
|
*/
|
|
487
487
|
target?: AssignmentTarget;
|
|
488
|
+
/**
|
|
489
|
+
* Why this ticket cannot start, when its acceptance required a machine and none could take it.
|
|
490
|
+
*
|
|
491
|
+
* Set instead of throwing: the request is real and worth recording, but running it on the host would answer a
|
|
492
|
+
* different question than the one asked. The executor reports it `blocked` without starting a Session, so it
|
|
493
|
+
* reaches a person as a visible failure rather than a silent success somewhere else.
|
|
494
|
+
*/
|
|
495
|
+
blockedReason?: string;
|
|
488
496
|
inputs: TicketInput[];
|
|
489
497
|
/** Inputs not yet handed to a running assignment. */
|
|
490
498
|
undelivered: string[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as ThreadRef, C as ChannelRef, G as Trigger, L as Thread, R as ThreadFact, V as Ticket, h as SourceScope, l as OutboundMessage, m as ScopeResult, pt as Deployment, t as ReceiveResult, v as AdmissionCell } from "./intake-
|
|
1
|
+
import { B as ThreadRef, C as ChannelRef, G as Trigger, L as Thread, R as ThreadFact, V as Ticket, h as SourceScope, l as OutboundMessage, m as ScopeResult, pt as Deployment, t as ReceiveResult, v as AdmissionCell } from "./intake-B1UsoHuw.js";
|
|
2
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
package/lib/local-watch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as Trigger } from "./intake-
|
|
2
|
-
import { t as SourceKind } from "./kind-
|
|
1
|
+
import { G as Trigger } from "./intake-B1UsoHuw.js";
|
|
2
|
+
import { t as SourceKind } from "./kind-BYtP14oM.js";
|
|
3
3
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/sources/dsh.d.ts
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-foreman",
|
|
3
3
|
"author": "niechao136",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.2",
|
|
6
6
|
"description": "dsh-foreman:主 loop 在多设备上派单的 DSH bundle:需求来自本机操作者的主会话而不是 GitHub/飞书,每个工作区各有一条主会话,主会话只负责路由,真正的活儿派给 Worker 在该工作区目录里执行。",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "lib/index.js",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"packageManager": "pnpm@11.7.0",
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"dsh-foreman-node": "0.2.
|
|
65
|
+
"dsh-foreman-node": "0.2.1",
|
|
66
66
|
"proper-lockfile": "4.1.2",
|
|
67
67
|
"ws": "8.22.0",
|
|
68
68
|
"zod": "4.4.3"
|