scientify 1.12.1 → 1.12.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/README.md +3 -1
- package/README.zh.md +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/src/cli/research.d.ts +1 -1
- package/dist/src/cli/research.d.ts.map +1 -1
- package/dist/src/cli/research.js +123 -227
- package/dist/src/cli/research.js.map +1 -1
- package/dist/src/commands/metabolism-status.d.ts +2 -2
- package/dist/src/commands/metabolism-status.d.ts.map +1 -1
- package/dist/src/commands/metabolism-status.js +75 -72
- package/dist/src/commands/metabolism-status.js.map +1 -1
- package/dist/src/commands.d.ts.map +1 -1
- package/dist/src/commands.js +55 -0
- package/dist/src/commands.js.map +1 -1
- package/dist/src/hooks/research-mode.d.ts.map +1 -1
- package/dist/src/hooks/research-mode.js +54 -37
- package/dist/src/hooks/research-mode.js.map +1 -1
- package/dist/src/hooks/scientify-signature.d.ts.map +1 -1
- package/dist/src/hooks/scientify-signature.js +5 -2
- package/dist/src/hooks/scientify-signature.js.map +1 -1
- package/dist/src/knowledge-state/render.d.ts +1 -0
- package/dist/src/knowledge-state/render.d.ts.map +1 -1
- package/dist/src/knowledge-state/render.js +101 -33
- package/dist/src/knowledge-state/render.js.map +1 -1
- package/dist/src/knowledge-state/store.d.ts.map +1 -1
- package/dist/src/knowledge-state/store.js +206 -33
- package/dist/src/knowledge-state/store.js.map +1 -1
- package/dist/src/knowledge-state/types.d.ts +12 -0
- package/dist/src/knowledge-state/types.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.d.ts.map +1 -1
- package/dist/src/literature/subscription-state.js +579 -7
- package/dist/src/literature/subscription-state.js.map +1 -1
- package/dist/src/research-subscriptions/constants.d.ts +1 -1
- package/dist/src/research-subscriptions/constants.js +1 -1
- package/dist/src/research-subscriptions/parse.d.ts.map +1 -1
- package/dist/src/research-subscriptions/parse.js +10 -0
- package/dist/src/research-subscriptions/parse.js.map +1 -1
- package/dist/src/research-subscriptions/prompt.d.ts +1 -1
- package/dist/src/research-subscriptions/prompt.d.ts.map +1 -1
- package/dist/src/research-subscriptions/prompt.js +142 -221
- package/dist/src/research-subscriptions/prompt.js.map +1 -1
- package/dist/src/research-subscriptions/types.d.ts +1 -0
- package/dist/src/research-subscriptions/types.d.ts.map +1 -1
- package/dist/src/templates/bootstrap.d.ts.map +1 -1
- package/dist/src/templates/bootstrap.js +19 -32
- package/dist/src/templates/bootstrap.js.map +1 -1
- package/dist/src/tools/scientify-cron.d.ts +4 -2
- package/dist/src/tools/scientify-cron.d.ts.map +1 -1
- package/dist/src/tools/scientify-cron.js +369 -17
- package/dist/src/tools/scientify-cron.js.map +1 -1
- package/dist/src/tools/scientify-literature-state.d.ts +8 -0
- package/dist/src/tools/scientify-literature-state.d.ts.map +1 -1
- package/dist/src/tools/scientify-literature-state.js +140 -71
- package/dist/src/tools/scientify-literature-state.js.map +1 -1
- package/openclaw.plugin.json +2 -4
- package/package.json +1 -1
- package/skills/research-subscription/SKILL.md +7 -0
|
@@ -13,36 +13,25 @@ export function renderBootstrapMd(projectName) {
|
|
|
13
13
|
2. 根据用户回答,提取:
|
|
14
14
|
- 核心域关键词(3-5 个)
|
|
15
15
|
- 建议的 arXiv 分类(如 cs.LG, cs.AI)
|
|
16
|
-
-
|
|
16
|
+
- 建议的跨域探索方向(反射带)
|
|
17
17
|
3. 向用户确认以上配置,接受调整
|
|
18
18
|
4. 确认后执行以下写入操作:
|
|
19
19
|
- 更新 SOUL.md:填写研究方向、核心域、监测带各字段
|
|
20
|
-
- 生成
|
|
21
|
-
5.
|
|
22
|
-
-
|
|
20
|
+
- 生成 task.json(记录 topic / mode / created)
|
|
21
|
+
5. 询问用户是否立即执行首轮研究(持续研究引擎)
|
|
22
|
+
- 如果是,执行 prepare -> collect/filter -> reflect -> record -> status
|
|
23
|
+
- 研究状态写入 knowledge_state/
|
|
23
24
|
6. 删除本文件(BOOTSTRAP.md)
|
|
24
25
|
|
|
25
|
-
##
|
|
26
|
+
## task.json 模板
|
|
26
27
|
|
|
27
28
|
\`\`\`json
|
|
28
29
|
{
|
|
29
30
|
"projectId": "${projectName}",
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
"monitorZone": {
|
|
36
|
-
"categories": ["相邻领域分类"],
|
|
37
|
-
"enabled": true
|
|
38
|
-
},
|
|
39
|
-
"heartbeat": {
|
|
40
|
-
"cronExpression": "0 6 * * *",
|
|
41
|
-
"timezone": "Asia/Shanghai",
|
|
42
|
-
"enabled": true
|
|
43
|
-
},
|
|
44
|
-
"agentId": "research-${projectName}",
|
|
45
|
-
"currentDay": 0,
|
|
31
|
+
"topic": "由用户确认后的研究主题",
|
|
32
|
+
"coreKeywords": ["关键词1", "关键词2"],
|
|
33
|
+
"monitorKeywords": ["跨域探索关键词"],
|
|
34
|
+
"mode": "continuous-research-engine",
|
|
46
35
|
"createdAt": "${new Date().toISOString()}"
|
|
47
36
|
}
|
|
48
37
|
\`\`\`
|
|
@@ -75,14 +64,13 @@ export function renderAgentsMd() {
|
|
|
75
64
|
$W/
|
|
76
65
|
├── SOUL.md # 身份 + 研究方向
|
|
77
66
|
├── AGENTS.md # 本文档
|
|
78
|
-
├──
|
|
79
|
-
│ ├──
|
|
80
|
-
│ ├──
|
|
81
|
-
│
|
|
82
|
-
│
|
|
83
|
-
│ ├──
|
|
84
|
-
│
|
|
85
|
-
│ └── log/ # 运行日志
|
|
67
|
+
├── knowledge_state/ # 持续研究状态真源(唯一)
|
|
68
|
+
│ ├── knowledge/
|
|
69
|
+
│ ├── daily_changes/
|
|
70
|
+
│ ├── hypotheses/
|
|
71
|
+
│ ├── logs/
|
|
72
|
+
│ ├── state.json
|
|
73
|
+
│ └── events.jsonl
|
|
86
74
|
├── survey/ # /research-collect outputs
|
|
87
75
|
│ ├── search_terms.json
|
|
88
76
|
│ └── report.md
|
|
@@ -102,14 +90,13 @@ $W/
|
|
|
102
90
|
├── iterations/ # /research-review: 审查迭代
|
|
103
91
|
│ └── judge_v*.md
|
|
104
92
|
├── experiment_res.md # /research-experiment: 实验报告
|
|
105
|
-
└── skills/ # workspace skills (metabolism 等)
|
|
106
93
|
\`\`\`
|
|
107
94
|
|
|
108
95
|
## Session Context
|
|
109
96
|
|
|
110
97
|
你可能在不同类型的 session 中被唤醒:
|
|
111
98
|
- **Main session**:与人类直接对话,可触发 research-pipeline 等编排 skill
|
|
112
|
-
- **Cron session
|
|
99
|
+
- **Cron session**:定时触发,执行周期性研究心跳
|
|
113
100
|
- **Spawn session**:被 main session 调度(sessions_spawn),执行一次性重任务
|
|
114
101
|
|
|
115
102
|
任务指令会在 session 启动时注入,按指令执行即可。
|
|
@@ -123,7 +110,7 @@ $W/
|
|
|
123
110
|
产出文件一旦写入不修改,除非用户明确要求。例外:\`project/\` 在 implement-review 迭代中可变。
|
|
124
111
|
|
|
125
112
|
### Knowledge File Rules
|
|
126
|
-
-
|
|
113
|
+
- knowledge_state/ 下的文件是持久知识状态,修改需谨慎
|
|
127
114
|
- 每次修改必须先读取当前内容再更新
|
|
128
115
|
- _index.md 是全景索引,必须与 topic 文件保持同步
|
|
129
116
|
- topic 文件数上限 50,低活跃主题应合并归档
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../../src/templates/bootstrap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO;;YAEG,WAAW;;;;kBAIL,WAAW
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../../src/templates/bootstrap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO;;YAEG,WAAW;;;;kBAIL,WAAW;;;;;;;;;;;;;;;;;;kBAkBX,WAAW;;;;;kBAKX,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;;;CAGzC,CAAC;AACF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAAmB;IAC9C,OAAO,qBAAqB,WAAW;;OAElC,WAAW;;;;;;;;;;;CAWjB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFR,CAAC;AACF,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginLogger, PluginRuntime } from "openclaw";
|
|
2
2
|
export declare const ScientifyCronToolSchema: import("@sinclair/typebox").TObject<{
|
|
3
|
-
action: import("@sinclair/typebox").TString
|
|
3
|
+
action: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
4
4
|
scope: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
5
5
|
schedule: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
6
6
|
topic: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
@@ -19,6 +19,7 @@ export declare const ScientifyCronToolSchema: import("@sinclair/typebox").TObjec
|
|
|
19
19
|
channel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
20
20
|
to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
21
21
|
no_deliver: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
22
|
+
metadata_only: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
22
23
|
run_now: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
23
24
|
job_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
24
25
|
}>;
|
|
@@ -31,7 +32,7 @@ export declare function createScientifyCronTool(deps: CronToolDeps): {
|
|
|
31
32
|
name: string;
|
|
32
33
|
description: string;
|
|
33
34
|
parameters: import("@sinclair/typebox").TObject<{
|
|
34
|
-
action: import("@sinclair/typebox").TString
|
|
35
|
+
action: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
35
36
|
scope: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
36
37
|
schedule: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
37
38
|
topic: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
@@ -50,6 +51,7 @@ export declare function createScientifyCronTool(deps: CronToolDeps): {
|
|
|
50
51
|
channel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
51
52
|
to: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
52
53
|
no_deliver: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
54
|
+
metadata_only: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
53
55
|
run_now: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
54
56
|
job_id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
55
57
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scientify-cron.d.ts","sourceRoot":"","sources":["../../../src/tools/scientify-cron.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA6C,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"scientify-cron.d.ts","sourceRoot":"","sources":["../../../src/tools/scientify-cron.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA6C,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAevG,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;EAiGlC,CAAC;AAEH,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAobF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAWzB,MAAM,WAAW,OAAO;;;;;;;;EAuNxD"}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { normalizeDeliveryChannelOverride } from "../research-subscriptions/delivery.js";
|
|
2
|
+
import { buildStateScopeKey, normalizeDeliveryChannelOverride, resolveDeliveryTarget, } from "../research-subscriptions/delivery.js";
|
|
3
|
+
import { parseSubscribeOptions } from "../research-subscriptions/parse.js";
|
|
3
4
|
import { createResearchSubscribeHandler, createResearchSubscriptionsHandler, createResearchUnsubscribeHandler, } from "../research-subscriptions.js";
|
|
5
|
+
import { getIncrementalStateStatus, recordIncrementalPush } from "../literature/subscription-state.js";
|
|
4
6
|
import { Result } from "./result.js";
|
|
5
7
|
export const ScientifyCronToolSchema = Type.Object({
|
|
6
|
-
action: Type.String({
|
|
7
|
-
description: 'Action: "upsert" | "list" | "remove".',
|
|
8
|
-
}),
|
|
8
|
+
action: Type.Optional(Type.String({
|
|
9
|
+
description: 'Action: "upsert" | "list" | "remove". When omitted, tool infers action from parameters.',
|
|
10
|
+
})),
|
|
9
11
|
scope: Type.Optional(Type.String({
|
|
10
12
|
description: "Scope key for grouping jobs (e.g. user ID or thread ID). Default: global.",
|
|
11
13
|
})),
|
|
@@ -48,8 +50,11 @@ export const ScientifyCronToolSchema = Type.Object({
|
|
|
48
50
|
no_deliver: Type.Optional(Type.Boolean({
|
|
49
51
|
description: "If true, run in background without push delivery.",
|
|
50
52
|
})),
|
|
53
|
+
metadata_only: Type.Optional(Type.Boolean({
|
|
54
|
+
description: "If true, allow metadata-only reading (skip full-text-first strict default). Use only when user explicitly requests it.",
|
|
55
|
+
})),
|
|
51
56
|
run_now: Type.Optional(Type.Boolean({
|
|
52
|
-
description: "If true (upsert only), trigger one immediate run after job creation/update
|
|
57
|
+
description: "If true (upsert only), trigger one immediate run after job creation/update; for research tasks, also return a status_json snapshot.",
|
|
53
58
|
})),
|
|
54
59
|
job_id: Type.Optional(Type.String({
|
|
55
60
|
description: "Specific job id to remove (only used when action=remove).",
|
|
@@ -64,6 +69,78 @@ function readStringParam(params, key) {
|
|
|
64
69
|
const str = String(value).trim();
|
|
65
70
|
return str.length > 0 ? str : undefined;
|
|
66
71
|
}
|
|
72
|
+
function sanitizeProjectId(raw) {
|
|
73
|
+
const trimmed = raw.trim();
|
|
74
|
+
if (/^[A-Za-z0-9_-]+$/.test(trimmed))
|
|
75
|
+
return trimmed;
|
|
76
|
+
const slug = trimmed
|
|
77
|
+
.toLowerCase()
|
|
78
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
79
|
+
.replace(/-+/g, "-")
|
|
80
|
+
.replace(/^-|-$/g, "");
|
|
81
|
+
return slug || "project";
|
|
82
|
+
}
|
|
83
|
+
function normalizeScheduleInput(raw, runNow) {
|
|
84
|
+
if (!raw)
|
|
85
|
+
return undefined;
|
|
86
|
+
const trimmed = raw.trim();
|
|
87
|
+
if (!trimmed)
|
|
88
|
+
return undefined;
|
|
89
|
+
const lower = trimmed.toLowerCase();
|
|
90
|
+
if (["now", "immediate", "immediately", "right now", "asap", "立即", "马上", "立刻"].includes(lower)) {
|
|
91
|
+
// run_now already triggers immediate execution; keep a valid persistent schedule.
|
|
92
|
+
return runNow ? "daily 09:00 Asia/Shanghai" : "at 2m";
|
|
93
|
+
}
|
|
94
|
+
if (/^\d+[smhdw]$/i.test(trimmed)) {
|
|
95
|
+
return `at ${trimmed}`;
|
|
96
|
+
}
|
|
97
|
+
if (/^every\s*hour$/i.test(trimmed) || /^每小时$/u.test(trimmed)) {
|
|
98
|
+
return "every 1h";
|
|
99
|
+
}
|
|
100
|
+
// Guard against `at <past-time>` generated by model/tool callers.
|
|
101
|
+
if (lower.startsWith("at ")) {
|
|
102
|
+
const when = trimmed.slice(3).trim();
|
|
103
|
+
if (when) {
|
|
104
|
+
const atMs = Date.parse(when);
|
|
105
|
+
if (!Number.isNaN(atMs) && atMs <= Date.now()) {
|
|
106
|
+
return runNow ? "daily 09:00 Asia/Shanghai" : "at 2m";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return trimmed;
|
|
111
|
+
}
|
|
112
|
+
function inferAction(params) {
|
|
113
|
+
const raw = readStringParam(params, "action")?.toLowerCase();
|
|
114
|
+
if (raw) {
|
|
115
|
+
if (["upsert", "create", "add", "set", "update", "start", "schedule", "new", "insert"].includes(raw)) {
|
|
116
|
+
return "upsert";
|
|
117
|
+
}
|
|
118
|
+
if (["list", "show", "ls", "status"].includes(raw)) {
|
|
119
|
+
return "list";
|
|
120
|
+
}
|
|
121
|
+
if (["remove", "delete", "cancel", "rm", "unsubscribe"].includes(raw)) {
|
|
122
|
+
return "remove";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const hasJobId = Boolean(readStringParam(params, "job_id"));
|
|
126
|
+
const hasUpsertSignals = Boolean(readStringParam(params, "schedule")) ||
|
|
127
|
+
Boolean(readStringParam(params, "topic")) ||
|
|
128
|
+
Boolean(readStringParam(params, "message")) ||
|
|
129
|
+
Boolean(readStringParam(params, "project")) ||
|
|
130
|
+
readBooleanParam(params, "run_now") ||
|
|
131
|
+
readBooleanParam(params, "no_deliver") ||
|
|
132
|
+
readBooleanParam(params, "metadata_only") ||
|
|
133
|
+
readNumberParam(params, "max_papers") !== undefined ||
|
|
134
|
+
readNumberParam(params, "recency_days") !== undefined ||
|
|
135
|
+
readNumberParam(params, "candidate_pool") !== undefined ||
|
|
136
|
+
Boolean(readStringParam(params, "channel")) ||
|
|
137
|
+
Boolean(readStringParam(params, "to"));
|
|
138
|
+
if (hasUpsertSignals)
|
|
139
|
+
return "upsert";
|
|
140
|
+
if (hasJobId)
|
|
141
|
+
return "remove";
|
|
142
|
+
return "list";
|
|
143
|
+
}
|
|
67
144
|
function readBooleanParam(params, key) {
|
|
68
145
|
return params[key] === true;
|
|
69
146
|
}
|
|
@@ -155,22 +232,179 @@ function deriveTopicFromResearchMessage(message) {
|
|
|
155
232
|
const normalized = text.trim();
|
|
156
233
|
return normalized.length > 0 ? normalized : message.trim();
|
|
157
234
|
}
|
|
158
|
-
function
|
|
159
|
-
const parts = [];
|
|
160
|
-
const schedule = readStringParam(params, "schedule") ?? "daily 09:00 Asia/Shanghai";
|
|
161
|
-
parts.push(schedule);
|
|
235
|
+
function resolveTopicAndMessage(params) {
|
|
162
236
|
let topic = readStringParam(params, "topic");
|
|
163
237
|
let message = readStringParam(params, "message");
|
|
164
238
|
if (!topic && message && shouldPromoteMessageToTopic(message)) {
|
|
165
239
|
topic = deriveTopicFromResearchMessage(message);
|
|
166
240
|
message = undefined;
|
|
167
241
|
}
|
|
242
|
+
return { topic, message };
|
|
243
|
+
}
|
|
244
|
+
function parseIncrementalScopeFromResultText(text) {
|
|
245
|
+
const fenced = text.match(/Incremental Scope:\s*`([^`]+)`/i);
|
|
246
|
+
if (fenced?.[1])
|
|
247
|
+
return fenced[1].trim();
|
|
248
|
+
const plain = text.match(/Incremental Scope:\s*([^\n]+)/i);
|
|
249
|
+
if (plain?.[1])
|
|
250
|
+
return plain[1].trim();
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
function latestRunId(status) {
|
|
254
|
+
return status?.recentChangeStats[0]?.runId;
|
|
255
|
+
}
|
|
256
|
+
function lastRunAtMs(status) {
|
|
257
|
+
return status?.knowledgeStateSummary?.lastRunAtMs ?? 0;
|
|
258
|
+
}
|
|
259
|
+
function lastPushedAtMs(status) {
|
|
260
|
+
return status?.lastPushedAtMs ?? 0;
|
|
261
|
+
}
|
|
262
|
+
function hasFreshRun(before, after) {
|
|
263
|
+
const beforeRunId = latestRunId(before);
|
|
264
|
+
const afterRunId = latestRunId(after);
|
|
265
|
+
if (!before) {
|
|
266
|
+
return after.totalRuns > 0 || Boolean(afterRunId) || lastRunAtMs(after) > 0 || lastPushedAtMs(after) > 0;
|
|
267
|
+
}
|
|
268
|
+
if (after.totalRuns > before.totalRuns)
|
|
269
|
+
return true;
|
|
270
|
+
if (afterRunId && beforeRunId && afterRunId !== beforeRunId)
|
|
271
|
+
return true;
|
|
272
|
+
if (!beforeRunId && afterRunId)
|
|
273
|
+
return true;
|
|
274
|
+
if (lastRunAtMs(after) > lastRunAtMs(before))
|
|
275
|
+
return true;
|
|
276
|
+
if (lastPushedAtMs(after) > lastPushedAtMs(before))
|
|
277
|
+
return true;
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
function buildFallbackRunId(jobId) {
|
|
281
|
+
const ts = new Date().toISOString().replace(/[-:.]/g, "").replace("T", "t").replace("Z", "z");
|
|
282
|
+
return `cron-${jobId}-${ts}-autofallback`;
|
|
283
|
+
}
|
|
284
|
+
function sleep(ms) {
|
|
285
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
286
|
+
}
|
|
287
|
+
function parseCronRunMarker(raw) {
|
|
288
|
+
const text = (raw ?? "").trim();
|
|
289
|
+
if (!text)
|
|
290
|
+
return undefined;
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(text);
|
|
293
|
+
return {
|
|
294
|
+
ok: typeof parsed.ok === "boolean" ? parsed.ok : undefined,
|
|
295
|
+
ran: typeof parsed.ran === "boolean" ? parsed.ran : undefined,
|
|
296
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : undefined,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function serializeRunStatusSnapshot(status) {
|
|
304
|
+
const projectRecentPapers = status.knowledgeStateSummary?.recentPapers ?? [];
|
|
305
|
+
const globalById = new Map(status.recentPapers.map((paper) => [paper.id, paper]));
|
|
306
|
+
return {
|
|
307
|
+
scope: status.scope,
|
|
308
|
+
topic: status.topic,
|
|
309
|
+
topic_key: status.topicKey,
|
|
310
|
+
known_paper_count: status.knownPaperCount,
|
|
311
|
+
total_runs: status.totalRuns,
|
|
312
|
+
last_status: status.lastStatus ?? null,
|
|
313
|
+
last_pushed_at_ms: status.lastPushedAtMs ?? null,
|
|
314
|
+
latest_run_id: status.recentChangeStats[0]?.runId ?? null,
|
|
315
|
+
knowledge_state_summary: status.knowledgeStateSummary
|
|
316
|
+
? {
|
|
317
|
+
project_id: status.knowledgeStateSummary.projectId,
|
|
318
|
+
stream_key: status.knowledgeStateSummary.streamKey,
|
|
319
|
+
run_profile: status.knowledgeStateSummary.runProfile,
|
|
320
|
+
total_runs: status.knowledgeStateSummary.totalRuns,
|
|
321
|
+
total_hypotheses: status.knowledgeStateSummary.totalHypotheses,
|
|
322
|
+
knowledge_topics_count: status.knowledgeStateSummary.knowledgeTopicsCount,
|
|
323
|
+
paper_notes_count: status.knowledgeStateSummary.paperNotesCount,
|
|
324
|
+
trigger_state: {
|
|
325
|
+
consecutive_new_revise_days: status.knowledgeStateSummary.triggerState.consecutiveNewReviseDays,
|
|
326
|
+
bridge_count_7d: status.knowledgeStateSummary.triggerState.bridgeCount7d,
|
|
327
|
+
unread_core_backlog: status.knowledgeStateSummary.triggerState.unreadCoreBacklog,
|
|
328
|
+
last_updated_at_ms: status.knowledgeStateSummary.triggerState.lastUpdatedAtMs,
|
|
329
|
+
},
|
|
330
|
+
quality_gate: {
|
|
331
|
+
passed: status.knowledgeStateSummary.qualityGate.passed,
|
|
332
|
+
full_text_coverage_pct: status.knowledgeStateSummary.qualityGate.fullTextCoveragePct,
|
|
333
|
+
evidence_binding_rate_pct: status.knowledgeStateSummary.qualityGate.evidenceBindingRatePct,
|
|
334
|
+
citation_error_rate_pct: status.knowledgeStateSummary.qualityGate.citationErrorRatePct,
|
|
335
|
+
reasons: status.knowledgeStateSummary.qualityGate.reasons,
|
|
336
|
+
},
|
|
337
|
+
hypothesis_gate: {
|
|
338
|
+
accepted: status.knowledgeStateSummary.hypothesisGate.accepted,
|
|
339
|
+
rejected: status.knowledgeStateSummary.hypothesisGate.rejected,
|
|
340
|
+
rejection_reasons: status.knowledgeStateSummary.hypothesisGate.rejectionReasons,
|
|
341
|
+
},
|
|
342
|
+
last_reflection_tasks: status.knowledgeStateSummary.lastReflectionTasks,
|
|
343
|
+
}
|
|
344
|
+
: null,
|
|
345
|
+
recent_change_stats: status.recentChangeStats.map((item) => ({
|
|
346
|
+
day: item.day,
|
|
347
|
+
run_id: item.runId,
|
|
348
|
+
new_count: item.newCount,
|
|
349
|
+
confirm_count: item.confirmCount,
|
|
350
|
+
revise_count: item.reviseCount,
|
|
351
|
+
bridge_count: item.bridgeCount,
|
|
352
|
+
})),
|
|
353
|
+
recent_papers: (projectRecentPapers.length > 0 ? projectRecentPapers : status.recentPapers).map((paper) => {
|
|
354
|
+
const paperId = typeof paper.id === "string" ? paper.id : "";
|
|
355
|
+
const fromGlobal = paperId ? globalById.get(paperId) : undefined;
|
|
356
|
+
return {
|
|
357
|
+
id: paperId || null,
|
|
358
|
+
title: paper.title ?? null,
|
|
359
|
+
url: paper.url ?? null,
|
|
360
|
+
last_score: "lastScore" in paper && typeof paper.lastScore === "number"
|
|
361
|
+
? paper.lastScore
|
|
362
|
+
: "score" in paper && typeof paper.score === "number"
|
|
363
|
+
? paper.score
|
|
364
|
+
: fromGlobal?.lastScore ?? null,
|
|
365
|
+
last_reason: "lastReason" in paper && typeof paper.lastReason === "string"
|
|
366
|
+
? paper.lastReason
|
|
367
|
+
: "reason" in paper && typeof paper.reason === "string"
|
|
368
|
+
? paper.reason
|
|
369
|
+
: fromGlobal?.lastReason ?? null,
|
|
370
|
+
first_pushed_at_ms: "firstPushedAtMs" in paper && typeof paper.firstPushedAtMs === "number"
|
|
371
|
+
? paper.firstPushedAtMs
|
|
372
|
+
: fromGlobal?.firstPushedAtMs ?? null,
|
|
373
|
+
last_pushed_at_ms: "lastPushedAtMs" in paper && typeof paper.lastPushedAtMs === "number"
|
|
374
|
+
? paper.lastPushedAtMs
|
|
375
|
+
: fromGlobal?.lastPushedAtMs ?? null,
|
|
376
|
+
push_count: "pushCount" in paper && typeof paper.pushCount === "number"
|
|
377
|
+
? paper.pushCount
|
|
378
|
+
: fromGlobal?.pushCount ?? null,
|
|
379
|
+
};
|
|
380
|
+
}),
|
|
381
|
+
global_recent_papers: status.recentPapers.map((paper) => ({
|
|
382
|
+
id: paper.id,
|
|
383
|
+
title: paper.title ?? null,
|
|
384
|
+
url: paper.url ?? null,
|
|
385
|
+
last_score: paper.lastScore ?? null,
|
|
386
|
+
last_reason: paper.lastReason ?? null,
|
|
387
|
+
first_pushed_at_ms: paper.firstPushedAtMs,
|
|
388
|
+
last_pushed_at_ms: paper.lastPushedAtMs,
|
|
389
|
+
push_count: paper.pushCount,
|
|
390
|
+
})),
|
|
391
|
+
knowledge_state_missing_reason: status.knowledgeStateMissingReason ?? null,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function buildSubscribeArgs(params) {
|
|
395
|
+
const parts = [];
|
|
396
|
+
const schedule = normalizeScheduleInput(readStringParam(params, "schedule"), readBooleanParam(params, "run_now")) ??
|
|
397
|
+
"daily 09:00 Asia/Shanghai";
|
|
398
|
+
parts.push(schedule);
|
|
399
|
+
const resolved = resolveTopicAndMessage(params);
|
|
400
|
+
const topic = resolved.topic;
|
|
401
|
+
const message = resolved.message;
|
|
168
402
|
if (topic) {
|
|
169
403
|
parts.push("--topic", quoteArg(topic));
|
|
170
404
|
}
|
|
171
405
|
const project = readStringParam(params, "project");
|
|
172
406
|
if (project) {
|
|
173
|
-
parts.push("--project", quoteArg(project));
|
|
407
|
+
parts.push("--project", quoteArg(sanitizeProjectId(project)));
|
|
174
408
|
}
|
|
175
409
|
if (message) {
|
|
176
410
|
parts.push("--message", quoteArg(message));
|
|
@@ -212,6 +446,9 @@ function buildSubscribeArgs(params) {
|
|
|
212
446
|
if (readBooleanParam(params, "no_deliver")) {
|
|
213
447
|
parts.push("--no-deliver");
|
|
214
448
|
}
|
|
449
|
+
if (readBooleanParam(params, "metadata_only")) {
|
|
450
|
+
parts.push("--metadata-only");
|
|
451
|
+
}
|
|
215
452
|
return parts.join(" ");
|
|
216
453
|
}
|
|
217
454
|
export function createScientifyCronTool(deps) {
|
|
@@ -225,12 +462,32 @@ export function createScientifyCronTool(deps) {
|
|
|
225
462
|
parameters: ScientifyCronToolSchema,
|
|
226
463
|
execute: async (_toolCallId, rawArgs) => {
|
|
227
464
|
const params = rawArgs;
|
|
228
|
-
const action = (
|
|
465
|
+
const action = inferAction(params);
|
|
466
|
+
if (!action) {
|
|
467
|
+
return Result.err("invalid_params", 'Unable to infer action. Use one of: action="upsert" | "list" | "remove".');
|
|
468
|
+
}
|
|
229
469
|
const scope = normalizeScope(readStringParam(params, "scope"));
|
|
230
470
|
try {
|
|
231
471
|
if (action === "upsert") {
|
|
232
|
-
|
|
472
|
+
// In tool context, delivery target may be unavailable unless caller explicitly sets channel/to.
|
|
473
|
+
// Default to no-deliver when delivery is unspecified to avoid hard failure on creation.
|
|
474
|
+
const hasDeliveryHints = Boolean(readStringParam(params, "channel")) || Boolean(readStringParam(params, "to"));
|
|
475
|
+
const upsertParams = readBooleanParam(params, "no_deliver") || hasDeliveryHints ? params : { ...params, no_deliver: true };
|
|
476
|
+
const args = buildSubscribeArgs(upsertParams);
|
|
233
477
|
const ctx = buildToolContext(scope, args, `/research-subscribe ${args}`);
|
|
478
|
+
let expectedStateScopeKey;
|
|
479
|
+
try {
|
|
480
|
+
const parsed = parseSubscribeOptions(args);
|
|
481
|
+
if (!("error" in parsed)) {
|
|
482
|
+
const delivery = resolveDeliveryTarget(ctx, parsed);
|
|
483
|
+
if (!("error" in delivery)) {
|
|
484
|
+
expectedStateScopeKey = buildStateScopeKey(ctx, delivery);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// keep best-effort behavior; fallback to parsed text scope or caller scope
|
|
490
|
+
}
|
|
234
491
|
const res = await subscribe(ctx);
|
|
235
492
|
const err = getResultError(res);
|
|
236
493
|
if (err) {
|
|
@@ -238,15 +495,109 @@ export function createScientifyCronTool(deps) {
|
|
|
238
495
|
}
|
|
239
496
|
const text = getResultText(res);
|
|
240
497
|
const jobId = parseJobIdFromResultText(text);
|
|
241
|
-
const
|
|
498
|
+
const resolved = resolveTopicAndMessage(upsertParams);
|
|
499
|
+
const incrementalScope = parseIncrementalScopeFromResultText(text);
|
|
500
|
+
const project = readStringParam(upsertParams, "project");
|
|
501
|
+
const runNow = readBooleanParam(upsertParams, "run_now");
|
|
242
502
|
if (runNow && jobId) {
|
|
243
|
-
|
|
503
|
+
const statusScope = expectedStateScopeKey ?? incrementalScope ?? scope;
|
|
504
|
+
const beforeStatus = resolved.topic
|
|
505
|
+
? await getIncrementalStateStatus({
|
|
506
|
+
scope: statusScope,
|
|
507
|
+
topic: resolved.topic,
|
|
508
|
+
...(project ? { projectId: project } : {}),
|
|
509
|
+
}).catch(() => undefined)
|
|
510
|
+
: undefined;
|
|
511
|
+
const runArgsPrimary = [
|
|
512
|
+
"openclaw",
|
|
513
|
+
"cron",
|
|
514
|
+
"run",
|
|
515
|
+
jobId,
|
|
516
|
+
"--expect-final",
|
|
517
|
+
"--timeout",
|
|
518
|
+
"900000",
|
|
519
|
+
];
|
|
520
|
+
let runRes = await deps.runtime.system.runCommandWithTimeout(runArgsPrimary, {
|
|
521
|
+
timeoutMs: 920_000,
|
|
522
|
+
});
|
|
244
523
|
if (runRes.code !== 0 &&
|
|
245
|
-
/unknown option '--
|
|
246
|
-
|
|
524
|
+
/unknown option '--expect-final'|unknown option \"--expect-final\"|unknown option\s+--expect-final/i.test(runRes.stderr || "")) {
|
|
525
|
+
// Backward compatibility for older OpenClaw versions.
|
|
526
|
+
runRes = await deps.runtime.system.runCommandWithTimeout(["openclaw", "cron", "run", jobId], { timeoutMs: 600_000 });
|
|
247
527
|
}
|
|
528
|
+
let runAlreadyInProgress = false;
|
|
248
529
|
if (runRes.code !== 0) {
|
|
249
|
-
|
|
530
|
+
const marker = parseCronRunMarker(runRes.stdout) ?? parseCronRunMarker(runRes.stderr);
|
|
531
|
+
if (marker?.ok === true && marker?.ran === false && marker?.reason === "already-running") {
|
|
532
|
+
runAlreadyInProgress = true;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
return Result.err("operation_failed", runRes.stderr || runRes.stdout || `cron run failed for job ${jobId}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
let statusSnapshot;
|
|
539
|
+
if (resolved.topic) {
|
|
540
|
+
try {
|
|
541
|
+
let status;
|
|
542
|
+
const deadline = Date.now() + (runAlreadyInProgress ? 300_000 : 120_000);
|
|
543
|
+
while (Date.now() <= deadline) {
|
|
544
|
+
const fetched = await getIncrementalStateStatus({
|
|
545
|
+
scope: statusScope,
|
|
546
|
+
topic: resolved.topic,
|
|
547
|
+
...(project ? { projectId: project } : {}),
|
|
548
|
+
}).catch(() => undefined);
|
|
549
|
+
if (fetched && hasFreshRun(beforeStatus, fetched)) {
|
|
550
|
+
status = fetched;
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
await sleep(1_000);
|
|
554
|
+
}
|
|
555
|
+
if (!status) {
|
|
556
|
+
const fallbackError = "run_now completed but no new persisted research run was detected. Auto-persisted fallback error run.";
|
|
557
|
+
try {
|
|
558
|
+
const persisted = await recordIncrementalPush({
|
|
559
|
+
scope: statusScope,
|
|
560
|
+
topic: resolved.topic,
|
|
561
|
+
...(project ? { projectId: project } : {}),
|
|
562
|
+
status: "degraded_quality",
|
|
563
|
+
runId: buildFallbackRunId(jobId),
|
|
564
|
+
note: fallbackError,
|
|
565
|
+
papers: [],
|
|
566
|
+
knowledgeState: {
|
|
567
|
+
corePapers: [],
|
|
568
|
+
explorationPapers: [],
|
|
569
|
+
explorationTrace: [],
|
|
570
|
+
knowledgeChanges: [],
|
|
571
|
+
knowledgeUpdates: [],
|
|
572
|
+
hypotheses: [],
|
|
573
|
+
runLog: {
|
|
574
|
+
runProfile: readBooleanParam(upsertParams, "metadata_only") ? "fast" : "strict",
|
|
575
|
+
error: "run_now completed but the agent turn did not persist via scientify_literature_state.record",
|
|
576
|
+
notes: "Fallback persisted by scientify_cron_job guard to avoid stale status response.",
|
|
577
|
+
tempCleanupStatus: "not_needed",
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
status = await getIncrementalStateStatus({
|
|
582
|
+
scope: statusScope,
|
|
583
|
+
topic: resolved.topic,
|
|
584
|
+
...(project ? { projectId: project } : {}),
|
|
585
|
+
}).catch(() => undefined);
|
|
586
|
+
if (!status || !hasFreshRun(beforeStatus, status)) {
|
|
587
|
+
return Result.err("operation_failed", `${fallbackError} fallback_run_id=${persisted.runId}, but fresh status still unavailable.`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (persistError) {
|
|
591
|
+
return Result.err("operation_failed", `run_now completed but no new persisted research run was detected. Refusing stale status response. fallback_persist_error=${persistError instanceof Error ? persistError.message : String(persistError)}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
statusSnapshot = serializeRunStatusSnapshot(status);
|
|
595
|
+
}
|
|
596
|
+
catch (statusError) {
|
|
597
|
+
statusSnapshot = {
|
|
598
|
+
error: statusError instanceof Error ? statusError.message : String(statusError),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
250
601
|
}
|
|
251
602
|
return Result.ok({
|
|
252
603
|
action,
|
|
@@ -254,6 +605,7 @@ export function createScientifyCronTool(deps) {
|
|
|
254
605
|
job_id: jobId,
|
|
255
606
|
run_now: true,
|
|
256
607
|
run_result: runRes.stdout.trim(),
|
|
608
|
+
...(statusSnapshot ? { status_json: statusSnapshot } : {}),
|
|
257
609
|
result: text,
|
|
258
610
|
});
|
|
259
611
|
}
|