dsh-hooks 0.4.0 → 0.5.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/README.md +25 -6
- package/README.zh.md +25 -6
- package/bin/dsh-hooks.mjs +37 -197
- package/examples/notify-feishu.d.mts +31 -0
- package/lib/client.js +368 -50
- package/lib/feishu-session.d.ts +47 -0
- package/lib/feishu-session.js +94 -0
- package/lib/feishu.d.ts +119 -0
- package/lib/feishu.js +282 -0
- package/lib/history.d.ts +5 -0
- package/lib/history.js +102 -3
- package/lib/index.js +13 -4
- package/lib/server.d.ts +15 -3
- package/lib/server.js +93 -0
- package/package.json +2 -2
package/lib/server.js
CHANGED
|
@@ -2,6 +2,8 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { describeHook, evaluateHooks, mockContext } from './dry-run.js';
|
|
3
3
|
import { createHookRunner } from './runner.js';
|
|
4
4
|
import { fireNotify } from './notify.js';
|
|
5
|
+
import { FEISHU_SETUP_BUSY } from './feishu-session.js';
|
|
6
|
+
import { readFeishuSummary, runFeishuTest, updateFeishuResultMaxChars } from './feishu.js';
|
|
5
7
|
/** Plugin version, read from package.json (this package ships its own). */
|
|
6
8
|
export function pluginVersion() {
|
|
7
9
|
const require = createRequire(import.meta.url);
|
|
@@ -48,6 +50,9 @@ async function readJsonBody(req) {
|
|
|
48
50
|
export function createHookHandler(options) {
|
|
49
51
|
const { hooks, history } = options;
|
|
50
52
|
const version = options.version ?? pluginVersion();
|
|
53
|
+
const feishu = options.feishu;
|
|
54
|
+
const runFeishuTestCard = feishu?.runTest ?? runFeishuTest;
|
|
55
|
+
const feishuConfigPath = feishu?.configPath;
|
|
51
56
|
return async (req, res) => {
|
|
52
57
|
if (!isLoopbackRequest(req)) {
|
|
53
58
|
json(res, FAIL('forbidden', 'loopback-only'), 403);
|
|
@@ -56,6 +61,9 @@ export function createHookHandler(options) {
|
|
|
56
61
|
const url = new URL(req.url ?? '/', 'http://x');
|
|
57
62
|
const pathname = url.pathname;
|
|
58
63
|
if (req.method === 'GET' && pathname === '/dsh-hooks/status') {
|
|
64
|
+
// Pull in disk records (pre-restart and other-process appends) so the
|
|
65
|
+
// badge reflects the durable log, not just this process's memory.
|
|
66
|
+
history.sync();
|
|
59
67
|
json(res, OK({ name: 'dsh-hooks', version, hookCount: hooks.length, historyCount: history.recent().length }));
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
@@ -63,6 +71,7 @@ export function createHookHandler(options) {
|
|
|
63
71
|
const raw = url.searchParams.get('n');
|
|
64
72
|
const parsed = raw === null ? 50 : Number(raw);
|
|
65
73
|
const n = Number.isFinite(parsed) && parsed > 0 ? Math.min(500, Math.floor(parsed)) : 50;
|
|
74
|
+
history.sync();
|
|
66
75
|
const records = history.recent();
|
|
67
76
|
json(res, OK(records.slice(Math.max(0, records.length - n))));
|
|
68
77
|
return;
|
|
@@ -119,6 +128,90 @@ export function createHookHandler(options) {
|
|
|
119
128
|
}));
|
|
120
129
|
return;
|
|
121
130
|
}
|
|
131
|
+
if (feishu !== undefined && req.method === 'GET' && pathname === '/dsh-hooks/feishu/status') {
|
|
132
|
+
const summary = readFeishuSummary(feishuConfigPath);
|
|
133
|
+
json(res, OK({ ...summary, setup: feishu.manager.status() }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (feishu !== undefined && req.method === 'POST' && pathname === '/dsh-hooks/feishu/setup') {
|
|
137
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
138
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
139
|
+
json(res, FAIL('bad-request', 'POST 需要 application/json'), 415);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const payload = await readJsonBody(req);
|
|
143
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
144
|
+
json(res, FAIL('bad-request', 'malformed JSON body'), 400);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const body = payload;
|
|
148
|
+
const profile = typeof body.profile === 'string' && body.profile.trim() !== '' ? body.profile.trim() : 'web';
|
|
149
|
+
const resultMaxChars = typeof body.resultMaxChars === 'number' && Number.isFinite(body.resultMaxChars)
|
|
150
|
+
? body.resultMaxChars
|
|
151
|
+
: undefined;
|
|
152
|
+
try {
|
|
153
|
+
const setup = resultMaxChars === undefined
|
|
154
|
+
? await feishu.manager.start(profile)
|
|
155
|
+
: await feishu.manager.start(profile, { resultMaxChars });
|
|
156
|
+
json(res, OK({ setup }));
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
160
|
+
json(res, FAIL('pending', message), message === FEISHU_SETUP_BUSY ? 409 : 500);
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (feishu !== undefined && req.method === 'POST' && pathname === '/dsh-hooks/feishu/config') {
|
|
165
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
166
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
167
|
+
json(res, FAIL('bad-request', 'POST 需要 application/json'), 415);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const payload = await readJsonBody(req);
|
|
171
|
+
if (typeof payload !== 'object' || payload === null) {
|
|
172
|
+
json(res, FAIL('bad-request', 'malformed JSON body'), 400);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const value = payload.resultMaxChars;
|
|
176
|
+
if (typeof value !== 'number') {
|
|
177
|
+
json(res, FAIL('bad-request', '缺少数字字段 resultMaxChars'), 400);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const resultMaxChars = updateFeishuResultMaxChars(feishuConfigPath, value);
|
|
182
|
+
json(res, OK({ resultMaxChars }));
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
186
|
+
json(res, FAIL('bad-request', message), 400);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (feishu !== undefined && req.method === 'POST' && pathname === '/dsh-hooks/feishu/cancel') {
|
|
191
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
192
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
193
|
+
json(res, FAIL('bad-request', 'POST 需要 application/json'), 415);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
json(res, OK({ cancelled: feishu.manager.cancel() }));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (feishu !== undefined && req.method === 'POST' && pathname === '/dsh-hooks/feishu/test') {
|
|
200
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
201
|
+
if (!contentType.toLowerCase().startsWith('application/json')) {
|
|
202
|
+
json(res, FAIL('bad-request', 'POST 需要 application/json'), 415);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const message = await runFeishuTestCard();
|
|
207
|
+
json(res, OK({ message }));
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
json(res, FAIL('send-failed', message), 500);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
122
215
|
json(res, FAIL('not-found', `unknown route ${pathname}`), 404);
|
|
123
216
|
};
|
|
124
217
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"packageManager": "pnpm@11.21.0",
|
|
5
|
-
"description": "Config-driven lifecycle hooks plugin for DeepSeek Harness: declare event -> command hooks in cordis.patch.yml, no plugin code required. Includes a Hooks section in the Web GUI settings (history timeline + manual tester).",
|
|
5
|
+
"description": "Config-driven lifecycle hooks plugin for DeepSeek Harness: declare event -> command hooks in cordis.patch.yml, no plugin code required. Includes a Hooks section in the Web GUI settings (history timeline + manual tester + Feishu connect).",
|
|
6
6
|
"author": "PeterBon",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|