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/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.4.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": {