dsh-plugin-t-expert 0.3.89 → 0.3.91
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 +2 -27
- package/icon.svg +17 -0
- package/lib/client.js +223 -1458
- package/lib/i18n.js +0 -26
- package/lib/index.js +3 -125
- package/lib/remote-schemas.js +0 -72
- package/lib/remote.js +2 -145
- package/package.json +48 -41
- package/lib/schedule.js +0 -509
package/lib/i18n.js
CHANGED
|
@@ -48,19 +48,6 @@ const ZH = {
|
|
|
48
48
|
"summon.batchHeader": "批量召唤 {total} 位专家:成功 {ok} / 失败 {failed}",
|
|
49
49
|
"summon.itemOk": "## {expert}\n{answer}",
|
|
50
50
|
"summon.itemFail": "## {expert}\n失败:{error}",
|
|
51
|
-
// ---- 定时任务(host 侧只负责校验与执行失败的原因文案;面板文案在客户端字典里)----
|
|
52
|
-
"schedule.cronEmpty": "cron 定时器不能为空",
|
|
53
|
-
"schedule.invalidCron": "cron 表达式无法解析:{detail}",
|
|
54
|
-
"schedule.inputInvalid": "定时任务的入参必须是一个对象",
|
|
55
|
-
"schedule.titleEmpty": "定时任务的标题不能为空",
|
|
56
|
-
"schedule.titleTooLong": "标题超过 {limit} 个字符",
|
|
57
|
-
"schedule.promptEmpty": "定时任务的提示词不能为空",
|
|
58
|
-
"schedule.promptTooLong": "提示词超过 {limit} 个字符",
|
|
59
|
-
"schedule.itemMissing": "找不到定时任务「{id}」(它可能已被删除)",
|
|
60
|
-
"schedule.tooManyItems": "定时任务最多 {limit} 条:请先删掉一些再新建",
|
|
61
|
-
"schedule.agentsMissing": "宿主没有可用的 agents 服务,无法为定时任务新建会话",
|
|
62
|
-
"schedule.workspaceMissing": "定时任务绑定的工作区「{id}」不存在(可能已被删除)",
|
|
63
|
-
"schedule.disabled": "定时任务功能当前已关闭:请在 设置 → T专家 里重新打开「显示定时任务按钮」",
|
|
64
51
|
};
|
|
65
52
|
|
|
66
53
|
const EN = {
|
|
@@ -108,19 +95,6 @@ const EN = {
|
|
|
108
95
|
"summon.batchHeader": "Summoned {total} experts: {ok} succeeded / {failed} failed",
|
|
109
96
|
"summon.itemOk": "## {expert}\n{answer}",
|
|
110
97
|
"summon.itemFail": "## {expert}\nfailed: {error}",
|
|
111
|
-
// ---- Scheduled tasks (the host owns validation/execution failure text; the panel owns its own) ----
|
|
112
|
-
"schedule.cronEmpty": "The cron schedule must not be empty",
|
|
113
|
-
"schedule.invalidCron": "Cannot parse the cron expression: {detail}",
|
|
114
|
-
"schedule.inputInvalid": "A scheduled task must be an object",
|
|
115
|
-
"schedule.titleEmpty": "A scheduled task needs a title",
|
|
116
|
-
"schedule.titleTooLong": "The title exceeds {limit} characters",
|
|
117
|
-
"schedule.promptEmpty": "A scheduled task needs a prompt",
|
|
118
|
-
"schedule.promptTooLong": "The prompt exceeds {limit} characters",
|
|
119
|
-
"schedule.itemMissing": "Scheduled task \"{id}\" not found (it may have been deleted)",
|
|
120
|
-
"schedule.tooManyItems": "At most {limit} scheduled tasks: delete some before adding more",
|
|
121
|
-
"schedule.agentsMissing": "The host has no usable agents service, so no session can be created for this scheduled task",
|
|
122
|
-
"schedule.workspaceMissing": "The workspace bound to this scheduled task (\"{id}\") no longer exists",
|
|
123
|
-
"schedule.disabled": "Scheduled tasks are currently turned off: re-enable \"Show scheduled tasks button\" in Settings → T Expert",
|
|
124
98
|
};
|
|
125
99
|
|
|
126
100
|
/** 取文案并按 {name} 占位替换。 */
|
package/lib/index.js
CHANGED
|
@@ -42,9 +42,7 @@ import { localized, NS, readLocale, renderList, renderSummonResults, t, toRender
|
|
|
42
42
|
import { seedData } from "./bootstrap.js";
|
|
43
43
|
import { installBundledSkills } from "./skill.js";
|
|
44
44
|
import { SNAPSHOT_DIR } from "./bootstrap.js";
|
|
45
|
-
// 定时事项引擎:T专家 自己的实现(JSON 文件 + croner),与 @weibaohui/dsh-tasks 零共享 ——
|
|
46
45
|
// 两个插件允许同时安装同时可用,所以存储、服务名、错误码、消息来源全都各走各的。
|
|
47
|
-
import { SCHEDULE_FILE_NAME, SCHEDULE_SERVICE, createScheduleEngine } from "./schedule.js";
|
|
48
46
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
49
47
|
|
|
50
48
|
export const name = NS;
|
|
@@ -114,16 +112,6 @@ export const Config = schema.object({
|
|
|
114
112
|
summonTaskMaxChars: schema.natural().min(1).default(8000),
|
|
115
113
|
/** list_t_experts 里简介的截断长度。 */
|
|
116
114
|
descriptionLimit: schema.natural().min(1).default(120),
|
|
117
|
-
/**
|
|
118
|
-
* 定时任务默认绑定的工作区(首次使用时自动建/复用)。
|
|
119
|
-
*
|
|
120
|
-
* 为什么要有它:定时任务跑出来的会话如果"不绑定工作区",在侧栏里就没有归属,
|
|
121
|
-
* 一堆自动会话会散在工作区列表外面;给它们一个固定去处(默认「定时任务」工作区、
|
|
122
|
-
* 目录 <默认目录>/tesk)最好找。目录不存在会被建出来 —— 宿主的工作区 create
|
|
123
|
-
* 只接受已存在的目录。
|
|
124
|
-
*/
|
|
125
|
-
scheduleWorkspaceTitle: schema.string().default("定时任务"),
|
|
126
|
-
scheduleWorkspaceDir: schema.string().default(join(homedir(), "tesk")),
|
|
127
115
|
|
|
128
116
|
/**
|
|
129
117
|
* 以下五个是**用户偏好**(专家启停 + 界面开关),不是部署配置。
|
|
@@ -143,7 +131,6 @@ export const Config = schema.object({
|
|
|
143
131
|
/** 「默认全部启用」已按哪一版策略播种过;+1 即对所有用户重新播种一次。 */
|
|
144
132
|
enabledSeedVersion: volatileField(schema.number().default(0)),
|
|
145
133
|
/** 是否显示侧栏「定时事项」按钮(关掉 = 定时功能整体停用)。 */
|
|
146
|
-
showScheduleButton: volatileField(schema.boolean().default(true)),
|
|
147
134
|
/** 是否显示窗口右上角的「活跃指示」。 */
|
|
148
135
|
showActivePulse: volatileField(schema.boolean().default(true)),
|
|
149
136
|
});
|
|
@@ -151,12 +138,6 @@ export const Config = schema.object({
|
|
|
151
138
|
/** 供 remote 服务读取名册的服务名。 */
|
|
152
139
|
export const CATALOG_SERVICE = "tTeamCatalog";
|
|
153
140
|
/**
|
|
154
|
-
* 定时事项服务(remote 侧与服务访问器共用)。
|
|
155
|
-
*
|
|
156
|
-
* 定义在 `lib/schedule.js`(引擎与常量同处一地),这里**再导出**一次:`lib/remote.js` 与本文件
|
|
157
|
-
* 都从同一处取常量,避免出现两个字面量各写一遍、改名时漏改一处的经典漂移。
|
|
158
|
-
*/
|
|
159
|
-
export { SCHEDULE_SERVICE };
|
|
160
141
|
/** 设置命名空间(= 插件名)。 */
|
|
161
142
|
export const SETTINGS_NAMESPACE = NS;
|
|
162
143
|
|
|
@@ -189,9 +170,8 @@ const settingsSchema = schema.object({
|
|
|
189
170
|
* 是否显示侧栏「定时事项」按钮(界面偏好)。
|
|
190
171
|
*
|
|
191
172
|
* 语义(用户口径):**关掉 = 定时事项功能整体停用** —— 不只是藏按钮,还要停掉全部定时触发
|
|
192
|
-
*
|
|
173
|
+
* 任务数据完整保留,重新打开即恢复调度。
|
|
193
174
|
*/
|
|
194
|
-
showScheduleButton: schema.boolean().default(true),
|
|
195
175
|
/**
|
|
196
176
|
* 是否显示窗口右上角的「活跃指示」(心电图图标,界面偏好)。
|
|
197
177
|
*
|
|
@@ -283,98 +263,8 @@ export function apply(ctx, config) {
|
|
|
283
263
|
ctx.logger?.error?.(`[t-team] 名册快照同步失败,将按已有数据继续:${seeded.error}`);
|
|
284
264
|
}
|
|
285
265
|
|
|
286
|
-
// ---- 定时事项引擎(T专家 自己的数据文件 + croner 调度)----
|
|
287
|
-
// 数据落在**本插件自己的数据根**里(`~/.t-team/schedule.json`):
|
|
288
|
-
// 刻意不碰宿主的 dsh_tasks 存储域,也不碰 dsh-tasks 插件的任何文件——两个插件同时装着时,
|
|
289
|
-
// 各自的任务列表、执行记录、定时器完全隔离,谁先加载都不影响对方。
|
|
290
|
-
const scheduleEngine = createScheduleEngine({
|
|
291
|
-
ctx,
|
|
292
|
-
file: join(dirname(config.root), SCHEDULE_FILE_NAME),
|
|
293
|
-
logger: ctx.logger,
|
|
294
|
-
// 文案按**当前**语言现取:错误消息会出现在面板上,切了语言不该还留着旧语言。
|
|
295
|
-
t: (key, params) => t(locale(), key, params),
|
|
296
|
-
defaultCwd: process.cwd(),
|
|
297
|
-
// 提示词**原样提交**,不做任何改写:定时任务开的新会话与手动开的会话挂同一份
|
|
298
|
-
// system prompt(那节只在子会话里返回空串),里面已经写着「看到 `@专家名` 就
|
|
299
|
-
// summon_t_expert FIRST」。以前这里会把 `@名字` 翻译成一段"请用 summon_t_expert
|
|
300
|
-
// 召唤专家「…」"的指令,导致同一个任务在定时任务里显示成一长段模板、在手动对话里
|
|
301
|
-
// 却只是 `@名字` —— 两条入口观感不一致(用户实测反馈)。现在统一按原文提交。
|
|
302
|
-
// 工作区需求:定时任务默认绑到「定时任务」工作区(目录 <默认目录>/tesk),也可自定义目录。
|
|
303
|
-
ensureWorkspace: (path, title) => ensureWorkspace(path, title),
|
|
304
|
-
// 先按「开」起来;settings 就绪后立刻按真实偏好对齐(见下面的 inject 回调)。
|
|
305
|
-
armed: true,
|
|
306
|
-
});
|
|
307
|
-
ctx.effect(() => () => scheduleEngine.dispose(), "t-team: schedule jobs");
|
|
308
|
-
ctx.reflect.provide(SCHEDULE_SERVICE, scheduleEngine);
|
|
309
266
|
|
|
310
|
-
/**
|
|
311
|
-
* 确保一个工作区存在,并返回工作区实体。
|
|
312
|
-
*
|
|
313
|
-
* 顺序:目录不存在先 mkdir(宿主的工作区 create **只接受已存在的目录**)→ 交给
|
|
314
|
-
* workspaceRegistry.create:它内部对同路径工作区是**幂等复用**(createCanonical 第一件事
|
|
315
|
-
* 就是按 path 查找已有实体),所以重复调用不会造出第二个「定时任务」工作区。
|
|
316
|
-
*
|
|
317
|
-
* **但"同路径幂等"挡不住"路径漂移"**:只要默认目录变过一次、或者历史数据里存过另一个路径,
|
|
318
|
-
* 就会各自建出一个同名「定时任务」——侧栏里出现两个同名工作区(用户实测报过)。所以默认
|
|
319
|
-
* 工作区这条路径上,先按标题找一遍已存在的工作区直接复用,把"共用一个"变成硬保证。
|
|
320
|
-
*
|
|
321
|
-
* @param path - 目录绝对路径;空串表示用配置里的 scheduleWorkspaceDir。
|
|
322
|
-
* @param title - 工作区标题;缺省时:默认工作区用配置里的 scheduleWorkspaceTitle,自定义目录用目录名。
|
|
323
|
-
*/
|
|
324
|
-
async function ensureWorkspace(path, title) {
|
|
325
|
-
const registry = ctx.get("workspaceRegistry") ?? ctx.get("workspace");
|
|
326
|
-
if (registry === undefined || typeof registry.create !== "function") {
|
|
327
|
-
throw new Error("宿主没有可用的工作区服务(workspaceRegistry.create),无法准备定时任务工作区。");
|
|
328
|
-
}
|
|
329
|
-
const custom = typeof path === "string" && path.trim() !== "";
|
|
330
|
-
// 默认工作区(调用方没指定目录):已经有同名工作区就直接复用,不再按默认目录另起一个。
|
|
331
|
-
if (!custom) {
|
|
332
|
-
const existing = findWorkspaceByTitle(registry, config.scheduleWorkspaceTitle);
|
|
333
|
-
if (existing !== undefined) return existing;
|
|
334
|
-
}
|
|
335
|
-
const raw = custom ? path.trim() : config.scheduleWorkspaceDir;
|
|
336
|
-
const target = resolve(raw);
|
|
337
|
-
await mkdir(target, { recursive: true });
|
|
338
|
-
// 自定义目录的工作区标题缺省取目录名:否则几个自定义目录会挤成同名「定时任务」,侧栏里分不出来。
|
|
339
|
-
const label = title !== undefined && title !== ""
|
|
340
|
-
? title
|
|
341
|
-
: (custom ? basename(target) : config.scheduleWorkspaceTitle);
|
|
342
|
-
const entity = await registry.create(target, label);
|
|
343
|
-
// 把 entity 透出去:执行路径要调 entity.attachSession 把新建的 session 挂回工作区 sessionIds
|
|
344
|
-
// (侧栏的"工作区 → 会话"分组就是从这条列表里取的;不挂上用户感知是"任务跑了但侧栏没新会话")。
|
|
345
|
-
return entity;
|
|
346
|
-
}
|
|
347
267
|
|
|
348
|
-
/**
|
|
349
|
-
* 在宿主的工作区注册表里按标题找第一个工作区(找不到 / 宿主没提供 list 就返回 undefined)。
|
|
350
|
-
*
|
|
351
|
-
* 用于「默认工作区只有一个」这条保证:同名工作区已经存在时复用它,而不是按当前默认目录
|
|
352
|
-
* 再建一个。`WorkspaceEntity` 自带 id / path / title / attachSession,正是执行路径需要的形状。
|
|
353
|
-
*
|
|
354
|
-
* @param registry - 宿主的 workspaceRegistry。
|
|
355
|
-
* @param title - 要匹配的工作区标题。
|
|
356
|
-
*/
|
|
357
|
-
function findWorkspaceByTitle(registry, title) {
|
|
358
|
-
if (typeof registry?.list !== "function") return undefined;
|
|
359
|
-
try {
|
|
360
|
-
return registry.list().find((entity) => entity?.title === title);
|
|
361
|
-
} catch {
|
|
362
|
-
// list 不可用(宿主版本差异)不致命:退回"按路径 create",最多是没享受到复用。
|
|
363
|
-
return undefined;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* 把界面偏好里的「显示定时事项按钮」落到调度上。
|
|
369
|
-
*
|
|
370
|
-
* 为什么不是纯界面开关:用户口径是**关掉即功能整体停用** —— 只藏按钮却让 cron 继续在后台
|
|
371
|
-
* 开新会话干活,是最糟的一种"以为关了"。所以关掉就停掉全部定时触发,重开按 stored enabled 重建。
|
|
372
|
-
* @param prefs - 界面前缀(缺省视为开)。
|
|
373
|
-
*/
|
|
374
|
-
function applyScheduleArmed(prefs) {
|
|
375
|
-
const armed = prefs?.showScheduleButton !== false;
|
|
376
|
-
scheduleEngine.setArmed(armed);
|
|
377
|
-
}
|
|
378
268
|
|
|
379
269
|
// ---- 设置段(T专家 自己的命名空间,与其它插件不冲突)----
|
|
380
270
|
// `settings` 是**可选**服务(见文件顶部的 inject 注释):宿主没挂它时本插件仍要能加载并工作,
|
|
@@ -413,7 +303,7 @@ export function apply(ctx, config) {
|
|
|
413
303
|
if (typeof settingsService?.register === "function") {
|
|
414
304
|
// ── 旧宿主(≤0.1.6):按命名空间注册,读写都走返回的句柄。
|
|
415
305
|
scope = settingsService.register(SETTINGS_NAMESPACE, settingsSchema, {
|
|
416
|
-
base: { enabled: [], enabledSeeded: false, enabledSeedVersion: 0,
|
|
306
|
+
base: { enabled: [], enabledSeeded: false, enabledSeedVersion: 0, showActivePulse: true },
|
|
417
307
|
applies: "live",
|
|
418
308
|
validate: (value) => {
|
|
419
309
|
const list = value?.enabled;
|
|
@@ -423,9 +313,6 @@ export function apply(ctx, config) {
|
|
|
423
313
|
},
|
|
424
314
|
});
|
|
425
315
|
prefsMode = "scope";
|
|
426
|
-
// 设置段一就绪就把定时事项开关对齐真实偏好:base 默认是「开」,而用户可能早就关过 ——
|
|
427
|
-
// 不对齐的话,插件每次启动都会先把已经关掉的功能重新跑起来。
|
|
428
|
-
applyScheduleArmed(scope?.get?.());
|
|
429
316
|
return;
|
|
430
317
|
}
|
|
431
318
|
if (typeof settingsService?.mutate === "function") {
|
|
@@ -443,11 +330,7 @@ export function apply(ctx, config) {
|
|
|
443
330
|
);
|
|
444
331
|
}
|
|
445
332
|
if (typeof ctx.on === "function") {
|
|
446
|
-
// volatile 字段的变更不会重挂插件,只发这个事件。定时开关必须跟着走:
|
|
447
|
-
// 否则用户在面板里关掉「定时事项」后,后台定时器还在开新会话。
|
|
448
|
-
ctx.on("loader/volatile-update", () => { applyScheduleArmed(readPrefs()); });
|
|
449
333
|
}
|
|
450
|
-
applyScheduleArmed(readPrefs());
|
|
451
334
|
return;
|
|
452
335
|
}
|
|
453
336
|
ctx.logger?.warn?.("[t-team] settings 服务既没有 register 也没有 mutate:专家启停不可用(名册与召唤不受影响)。");
|
|
@@ -468,7 +351,6 @@ export function apply(ctx, config) {
|
|
|
468
351
|
enabled: unwrapVolatile(source.enabled),
|
|
469
352
|
enabledSeeded: unwrapVolatile(source.enabledSeeded),
|
|
470
353
|
enabledSeedVersion: unwrapVolatile(source.enabledSeedVersion),
|
|
471
|
-
showScheduleButton: unwrapVolatile(source.showScheduleButton),
|
|
472
354
|
showActivePulse: unwrapVolatile(source.showActivePulse),
|
|
473
355
|
};
|
|
474
356
|
}
|
|
@@ -1002,7 +884,6 @@ export function apply(ctx, config) {
|
|
|
1002
884
|
function getUiPrefs() {
|
|
1003
885
|
const current = readPrefs();
|
|
1004
886
|
return {
|
|
1005
|
-
showScheduleButton: current?.showScheduleButton !== false,
|
|
1006
887
|
showActivePulse: current?.showActivePulse !== false,
|
|
1007
888
|
};
|
|
1008
889
|
}
|
|
@@ -1011,7 +892,6 @@ export function apply(ctx, config) {
|
|
|
1011
892
|
function normalizePrefsPatch(patch) {
|
|
1012
893
|
const source = patch !== null && typeof patch === "object" ? patch : {};
|
|
1013
894
|
return {
|
|
1014
|
-
...(typeof source.showScheduleButton === "boolean" ? { showScheduleButton: source.showScheduleButton } : {}),
|
|
1015
895
|
...(typeof source.showActivePulse === "boolean" ? { showActivePulse: source.showActivePulse } : {}),
|
|
1016
896
|
};
|
|
1017
897
|
}
|
|
@@ -1027,12 +907,10 @@ export function apply(ctx, config) {
|
|
|
1027
907
|
await settingsService.mutate(
|
|
1028
908
|
SETTINGS_NAMESPACE,
|
|
1029
909
|
[
|
|
1030
|
-
{ op: "set", path: ["showScheduleButton"], value: next.showScheduleButton },
|
|
1031
910
|
{ op: "set", path: ["showActivePulse"], value: next.showActivePulse },
|
|
1032
911
|
],
|
|
1033
912
|
revision(),
|
|
1034
913
|
);
|
|
1035
|
-
applyScheduleArmed(next);
|
|
1036
914
|
return getUiPrefs();
|
|
1037
915
|
}
|
|
1038
916
|
|
|
@@ -1215,7 +1093,7 @@ export function apply(ctx, config) {
|
|
|
1215
1093
|
|
|
1216
1094
|
ctx.tools.register(defineTool({
|
|
1217
1095
|
name: "summon_t_experts",
|
|
1218
|
-
description: `Summon multiple T专家 experts in parallel for one mission. At most ${config.maxSummonBatch} experts run with concurrency ${config.summonConcurrency}; successful answers are returned even when some fail. Each expert is usually pre-named by the caller (a composer @ chip, or an explicit @专家名 anywhere in the text — including a
|
|
1096
|
+
description: `Summon multiple T专家 experts in parallel for one mission. At most ${config.maxSummonBatch} experts run with concurrency ${config.summonConcurrency}; successful answers are returned even when some fail. Each expert is usually pre-named by the caller (a composer @ chip, or an explicit @专家名 anywhere in the text — including a stored prompt from elsewhere) — when so, summon directly without list_t_experts; only call list_t_experts when no expert is named.`,
|
|
1219
1097
|
// 预算声明:批量最坏是 ceil(批大小 / 并发) 轮串行,给 20 分钟。
|
|
1220
1098
|
// 与单次召唤同理,它只是声明,强制 deadline 需要宿主装配 timeout-policy wrapper。
|
|
1221
1099
|
timeoutMs: 1_200_000,
|
package/lib/remote-schemas.js
CHANGED
|
@@ -77,85 +77,13 @@ export const enabledStateSchema = z.object({
|
|
|
77
77
|
* 加新键要同时改 host(lib/index.js 的 settingsSchema 与 getUiPrefs)与客户端消费处。
|
|
78
78
|
*/
|
|
79
79
|
export const uiPrefsSchema = z.object({
|
|
80
|
-
showScheduleButton: z.boolean(),
|
|
81
80
|
// 窗口右上角「活跃指示」是否显示。宿主**总是**返回布尔(缺省按 true 算),所以这里必填。
|
|
82
81
|
showActivePulse: z.boolean(),
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
/** `setUiPrefs` 的入参:想改哪个传哪个(未传的键保持原值)。 */
|
|
86
85
|
export const uiPrefsPatchSchema = z.object({
|
|
87
|
-
showScheduleButton: z.boolean().optional(),
|
|
88
86
|
showActivePulse: z.boolean().optional(),
|
|
89
87
|
});
|
|
90
88
|
|
|
91
89
|
export const promptSchema = z.object({ prompt: z.string() });
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------------------------- 定时事项
|
|
95
|
-
// 与 @weibaohui/dsh-tasks 的线格式**刻意不同名**:两个插件可以同时安装,各自的 schema / 服务名 /
|
|
96
|
-
// 数据文件互不相干,谁先加载都不会影响对方。
|
|
97
|
-
|
|
98
|
-
/** 一次执行尝试:什么时候跑的、开出哪个会话、成败与失败原因。 */
|
|
99
|
-
export const scheduleRunSchema = z.object({
|
|
100
|
-
at: z.string(),
|
|
101
|
-
ok: z.boolean(),
|
|
102
|
-
sessionId: z.string().optional(),
|
|
103
|
-
error: z.string().optional(),
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
/** 一条定时事项。 */
|
|
107
|
-
export const scheduleItemSchema = z.object({
|
|
108
|
-
id: z.string(),
|
|
109
|
-
title: z.string(),
|
|
110
|
-
prompt: z.string(),
|
|
111
|
-
cron: z.string(),
|
|
112
|
-
enabled: z.boolean(),
|
|
113
|
-
workspaceId: z.string().optional(),
|
|
114
|
-
/** 绑定时记下的工作区目录:工作区被删后凭它「重建」回到原路径。面板也会在编辑表单里显示它。 */
|
|
115
|
-
workspacePath: z.string().optional(),
|
|
116
|
-
createdAt: z.string(),
|
|
117
|
-
updatedAt: z.string(),
|
|
118
|
-
lastRunAt: z.string().optional(),
|
|
119
|
-
lastRunError: z.string().optional(),
|
|
120
|
-
runs: z.array(scheduleRunSchema),
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
/** 面板一次性拿全的数据:事项 + 开关 + 工作区选项 + 宿主能力探测。 */
|
|
124
|
-
export const scheduleSnapshotSchema = z.object({
|
|
125
|
-
items: z.array(scheduleItemSchema),
|
|
126
|
-
/** 定时事项功能是否启用(= 设置页那个开关的当前值)。 */
|
|
127
|
-
armed: z.boolean(),
|
|
128
|
-
/** 数据文件绝对路径(面板把它显示在说明里,便于用户备份)。 */
|
|
129
|
-
file: z.string(),
|
|
130
|
-
/** 正在执行中的事项 id(面板据此显示「执行中…」)。 */
|
|
131
|
-
running: z.array(z.string()),
|
|
132
|
-
capabilities: z.object({ agents: z.boolean(), presets: z.boolean() }),
|
|
133
|
-
/** 可选工作区(下拉选项);path 是目录,面板显示给用户看落地位置。 */
|
|
134
|
-
workspaces: z.array(z.object({ id: z.string(), title: z.string(), path: z.string().optional() })),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
/** 新建/编辑表单提交的字段。 */
|
|
138
|
-
export const scheduleInputSchema = z.object({
|
|
139
|
-
title: z.string().min(1).max(200),
|
|
140
|
-
prompt: z.string().min(1).max(20000),
|
|
141
|
-
cron: z.string().min(1).max(120),
|
|
142
|
-
enabled: z.boolean(),
|
|
143
|
-
/** 空串 = 不绑定工作区(显式解绑也走这个值)。 */
|
|
144
|
-
workspaceId: z.string().max(200).optional(),
|
|
145
|
-
/** 绑定时记下的工作区目录;老数据可缺省,按当前选中的 workspace 自动补。 */
|
|
146
|
-
workspacePath: z.string().max(1024).optional(),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
/** 编辑时的局部更新(未传的键保持原值)。 */
|
|
150
|
-
export const schedulePatchSchema = scheduleInputSchema.partial();
|
|
151
|
-
|
|
152
|
-
/** `previewSchedule` 的结果:接下来几次触发时间(ISO)。 */
|
|
153
|
-
export const schedulePreviewSchema = z.object({ fires: z.array(z.string()) });
|
|
154
|
-
|
|
155
|
-
/** `setArmed` 的结果。 */
|
|
156
|
-
export const scheduleArmedSchema = z.object({ armed: z.boolean() });
|
|
157
|
-
|
|
158
|
-
/** `ensureScheduleWorkspace` 的返回:定时任务要绑定的工作区(默认「定时任务」工作区或自定义目录)。 */
|
|
159
|
-
export const scheduleWorkspaceSchema = z.object({
|
|
160
|
-
workspace: z.object({ id: z.string(), title: z.string(), path: z.string() }),
|
|
161
|
-
});
|
package/lib/remote.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { CATALOG_SERVICE,
|
|
10
|
+
import { CATALOG_SERVICE, SETTINGS_NAMESPACE, UNKNOWN_SLUG_ERROR_CODE } from "./index.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* 领域码形态:`<domain>/<reason>`(如 `tTeam/
|
|
13
|
+
* 领域码形态:`<domain>/<reason>`(如 `tTeam/unknown-expert`)。
|
|
14
14
|
*
|
|
15
15
|
* 只有通过它校验的 `error.code` 才允许当 wire code 透传 —— 系统 errno(`EACCES`…)不是领域码,
|
|
16
16
|
* 透传出去会让客户端按 code 分支时把「文件权限」误认成业务失败类型(D-17)。口径与宿主网关一致
|
|
@@ -28,11 +28,6 @@ import {
|
|
|
28
28
|
enabledStateSchema,
|
|
29
29
|
expertSchema,
|
|
30
30
|
promptSchema,
|
|
31
|
-
scheduleInputSchema,
|
|
32
|
-
schedulePatchSchema,
|
|
33
|
-
schedulePreviewSchema,
|
|
34
|
-
scheduleWorkspaceSchema,
|
|
35
|
-
scheduleSnapshotSchema,
|
|
36
31
|
uiPrefsPatchSchema,
|
|
37
32
|
uiPrefsSchema,
|
|
38
33
|
} from "./remote-schemas.js";
|
|
@@ -64,62 +59,6 @@ const DESCRIPTORS = [
|
|
|
64
59
|
codec: { mode: "strict", typeSymbol: "TTeamUiPrefsPatch", schema: uiPrefsPatchSchema, create: () => uiPrefsPatchSchema },
|
|
65
60
|
},
|
|
66
61
|
], "TTeamUiPrefs", uiPrefsSchema),
|
|
67
|
-
// 定时事项:全部写操作都回传刷新后的整份快照,面板拿到就能直接重绘(与名册同一口径)。
|
|
68
|
-
descriptor("getSchedule", [], "TTeamSchedule", scheduleSnapshotSchema),
|
|
69
|
-
descriptor("createScheduleItem", [
|
|
70
|
-
{
|
|
71
|
-
name: "input",
|
|
72
|
-
wire: "input",
|
|
73
|
-
source: "json",
|
|
74
|
-
codec: { mode: "strict", typeSymbol: "TTeamScheduleInput", schema: scheduleInputSchema, create: () => scheduleInputSchema },
|
|
75
|
-
},
|
|
76
|
-
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
77
|
-
descriptor("updateScheduleItem", [
|
|
78
|
-
{
|
|
79
|
-
name: "id",
|
|
80
|
-
wire: "id",
|
|
81
|
-
source: "json",
|
|
82
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "patch",
|
|
86
|
-
wire: "patch",
|
|
87
|
-
source: "json",
|
|
88
|
-
codec: { mode: "strict", typeSymbol: "TTeamSchedulePatch", schema: schedulePatchSchema, create: () => schedulePatchSchema },
|
|
89
|
-
},
|
|
90
|
-
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
91
|
-
descriptor("deleteScheduleItem", [
|
|
92
|
-
{
|
|
93
|
-
name: "id",
|
|
94
|
-
wire: "id",
|
|
95
|
-
source: "json",
|
|
96
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
97
|
-
},
|
|
98
|
-
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
99
|
-
descriptor("runScheduleItem", [
|
|
100
|
-
{
|
|
101
|
-
name: "id",
|
|
102
|
-
wire: "id",
|
|
103
|
-
source: "json",
|
|
104
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(128), create: () => z.string().min(1).max(128) },
|
|
105
|
-
},
|
|
106
|
-
], "TTeamSchedule", scheduleSnapshotSchema),
|
|
107
|
-
descriptor("previewSchedule", [
|
|
108
|
-
{
|
|
109
|
-
name: "cron",
|
|
110
|
-
wire: "cron",
|
|
111
|
-
source: "json",
|
|
112
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().min(1).max(120), create: () => z.string().min(1).max(120) },
|
|
113
|
-
},
|
|
114
|
-
], "TTeamSchedulePreview", schedulePreviewSchema),
|
|
115
|
-
descriptor("ensureScheduleWorkspace", [
|
|
116
|
-
{
|
|
117
|
-
name: "path",
|
|
118
|
-
wire: "path",
|
|
119
|
-
source: "json",
|
|
120
|
-
codec: { mode: "strict", typeSymbol: "string", schema: z.string().max(1024), create: () => z.string().max(1024) },
|
|
121
|
-
},
|
|
122
|
-
], "TTeamScheduleWorkspace", scheduleWorkspaceSchema),
|
|
123
62
|
descriptor("setEnabled", [
|
|
124
63
|
{
|
|
125
64
|
name: "enabled",
|
|
@@ -322,94 +261,13 @@ class TTeamRemote extends TypertRemoteService {
|
|
|
322
261
|
}
|
|
323
262
|
}
|
|
324
263
|
|
|
325
|
-
/**
|
|
326
|
-
* 定时事项服务(T专家 自己的引擎,与 @weibaohui/dsh-tasks 完全独立)。
|
|
327
|
-
*
|
|
328
|
-
* 与 `catalog()` 同理:缺服务时给的是**功能语境的**错误码(`tTeam/schedule-unavailable`
|
|
329
|
-
* 读起来就是「定时事项用不了」),而不是笼统的 host-unavailable。
|
|
330
|
-
*/
|
|
331
|
-
schedule() {
|
|
332
|
-
const service = this.ctx.get(SCHEDULE_SERVICE);
|
|
333
|
-
if (service === undefined) {
|
|
334
|
-
// @ts-expect-error 上游类型缺口:同上,tTeam/* 业务码不在闭合的 RemoteErrorDetailsMap 里。
|
|
335
|
-
throw new RemoteError("tTeam/schedule-unavailable", "T专家 定时事项服务不可用(host 插件未加载或已卸载)", { service: SCHEDULE_SERVICE });
|
|
336
|
-
}
|
|
337
|
-
return service;
|
|
338
|
-
}
|
|
339
264
|
|
|
340
|
-
/** 定时事项快照:事项列表 + 开关状态 + 工作区选项 + 宿主能力探测。 */
|
|
341
|
-
async getSchedule() {
|
|
342
|
-
try {
|
|
343
|
-
return this.schedule().snapshot();
|
|
344
|
-
} catch (error) {
|
|
345
|
-
throw this.businessError(error, "tTeam/schedule-unreadable");
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
265
|
|
|
349
|
-
/** 新建一条定时事项。 */
|
|
350
|
-
async createScheduleItem(input) {
|
|
351
|
-
try {
|
|
352
|
-
const service = this.schedule();
|
|
353
|
-
await service.create(input);
|
|
354
|
-
return service.snapshot();
|
|
355
|
-
} catch (error) {
|
|
356
|
-
throw this.businessError(error, "tTeam/schedule-invalid");
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
266
|
|
|
360
|
-
/** 改一条定时事项(局部更新)。 */
|
|
361
|
-
async updateScheduleItem(id, patch) {
|
|
362
|
-
try {
|
|
363
|
-
const service = this.schedule();
|
|
364
|
-
await service.update(id, patch);
|
|
365
|
-
return service.snapshot();
|
|
366
|
-
} catch (error) {
|
|
367
|
-
throw this.businessError(error, "tTeam/schedule-invalid");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
267
|
|
|
371
|
-
/** 删一条定时事项。 */
|
|
372
|
-
async deleteScheduleItem(id) {
|
|
373
|
-
try {
|
|
374
|
-
const service = this.schedule();
|
|
375
|
-
await service.remove(id);
|
|
376
|
-
return service.snapshot();
|
|
377
|
-
} catch (error) {
|
|
378
|
-
throw this.businessError(error, "tTeam/schedule-invalid");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
268
|
|
|
382
|
-
/** 立即执行一条定时事项(等价于等它到点)。 */
|
|
383
|
-
async runScheduleItem(id) {
|
|
384
|
-
try {
|
|
385
|
-
const service = this.schedule();
|
|
386
|
-
await service.runNow(id);
|
|
387
|
-
return service.snapshot();
|
|
388
|
-
} catch (error) {
|
|
389
|
-
throw this.businessError(error, "tTeam/schedule-run-failed");
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
269
|
|
|
393
|
-
/**
|
|
394
|
-
* 确保定时任务要绑定的工作区存在:空 path = 配置里的默认「定时任务」工作区(目录 <默认目录>/tesk);
|
|
395
|
-
* 给了 path = 面板里填的自定义目录(不存在会建出来)。宿主的工作区 create 对同路径是幂等复用。
|
|
396
|
-
*/
|
|
397
|
-
async ensureScheduleWorkspace(path) {
|
|
398
|
-
try {
|
|
399
|
-
return { workspace: await this.schedule().ensureWorkspace(String(path ?? ""), undefined) };
|
|
400
|
-
} catch (error) {
|
|
401
|
-
throw this.businessError(error, "tTeam/schedule-workspace-failed");
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
270
|
|
|
405
|
-
/** 预览某个 cron 表达式接下来的 5 次触发时间(宿主用 croner 算,客户端不打包 cron 解析器)。 */
|
|
406
|
-
async previewSchedule(cron) {
|
|
407
|
-
try {
|
|
408
|
-
return this.schedule().preview(cron);
|
|
409
|
-
} catch (error) {
|
|
410
|
-
throw this.businessError(error, "tTeam/schedule-invalid");
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
271
|
|
|
414
272
|
async setEnabled(enabled, expectedRevision) {
|
|
415
273
|
try {
|
|
@@ -532,7 +390,6 @@ class TTeamRemote extends TypertRemoteService {
|
|
|
532
390
|
exposeRemoteMethods(TTeamRemote, [
|
|
533
391
|
"getCatalog", "setEnabled", "getPrompt",
|
|
534
392
|
"getUiPrefs", "setUiPrefs",
|
|
535
|
-
"getSchedule", "createScheduleItem", "updateScheduleItem", "deleteScheduleItem", "runScheduleItem", "previewSchedule", "ensureScheduleWorkspace",
|
|
536
393
|
"createExpert", "updateExpert", "deleteExpert",
|
|
537
394
|
"createCategory", "updateCategory", "deleteCategory",
|
|
538
395
|
]);
|