scientify 1.10.3 → 1.11.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.
Files changed (51) hide show
  1. package/README.md +20 -6
  2. package/README.zh.md +22 -8
  3. package/dist/src/commands.d.ts.map +1 -1
  4. package/dist/src/commands.js +63 -0
  5. package/dist/src/commands.js.map +1 -1
  6. package/dist/src/hooks/research-mode.d.ts.map +1 -1
  7. package/dist/src/hooks/research-mode.js +6 -1
  8. package/dist/src/hooks/research-mode.js.map +1 -1
  9. package/dist/src/knowledge-state/project.d.ts +13 -0
  10. package/dist/src/knowledge-state/project.d.ts.map +1 -0
  11. package/dist/src/knowledge-state/project.js +88 -0
  12. package/dist/src/knowledge-state/project.js.map +1 -0
  13. package/dist/src/knowledge-state/render.d.ts +63 -0
  14. package/dist/src/knowledge-state/render.d.ts.map +1 -0
  15. package/dist/src/knowledge-state/render.js +368 -0
  16. package/dist/src/knowledge-state/render.js.map +1 -0
  17. package/dist/src/knowledge-state/store.d.ts +19 -0
  18. package/dist/src/knowledge-state/store.d.ts.map +1 -0
  19. package/dist/src/knowledge-state/store.js +978 -0
  20. package/dist/src/knowledge-state/store.js.map +1 -0
  21. package/dist/src/knowledge-state/types.d.ts +182 -0
  22. package/dist/src/knowledge-state/types.d.ts.map +1 -0
  23. package/dist/src/knowledge-state/types.js +2 -0
  24. package/dist/src/knowledge-state/types.js.map +1 -0
  25. package/dist/src/literature/subscription-state.d.ts +11 -0
  26. package/dist/src/literature/subscription-state.d.ts.map +1 -1
  27. package/dist/src/literature/subscription-state.js +38 -2
  28. package/dist/src/literature/subscription-state.js.map +1 -1
  29. package/dist/src/research-subscriptions/handlers.d.ts.map +1 -1
  30. package/dist/src/research-subscriptions/handlers.js +1 -0
  31. package/dist/src/research-subscriptions/handlers.js.map +1 -1
  32. package/dist/src/research-subscriptions/parse.d.ts.map +1 -1
  33. package/dist/src/research-subscriptions/parse.js +14 -0
  34. package/dist/src/research-subscriptions/parse.js.map +1 -1
  35. package/dist/src/research-subscriptions/prompt.d.ts +1 -1
  36. package/dist/src/research-subscriptions/prompt.d.ts.map +1 -1
  37. package/dist/src/research-subscriptions/prompt.js +178 -23
  38. package/dist/src/research-subscriptions/prompt.js.map +1 -1
  39. package/dist/src/research-subscriptions/types.d.ts +1 -0
  40. package/dist/src/research-subscriptions/types.d.ts.map +1 -1
  41. package/dist/src/tools/scientify-cron.d.ts +2 -0
  42. package/dist/src/tools/scientify-cron.d.ts.map +1 -1
  43. package/dist/src/tools/scientify-cron.js +7 -0
  44. package/dist/src/tools/scientify-cron.js.map +1 -1
  45. package/dist/src/tools/scientify-literature-state.d.ts +234 -0
  46. package/dist/src/tools/scientify-literature-state.d.ts.map +1 -1
  47. package/dist/src/tools/scientify-literature-state.js +638 -3
  48. package/dist/src/tools/scientify-literature-state.js.map +1 -1
  49. package/package.json +1 -1
  50. package/skills/_shared/workspace-spec.md +15 -3
  51. package/skills/research-subscription/SKILL.md +18 -3
@@ -23,9 +23,105 @@ const PaperSchema = Type.Object({
23
23
  description: "Optional internal ranking rationale for backend logging.",
24
24
  })),
25
25
  });
26
+ const KnowledgePaperSchema = Type.Object({
27
+ id: Type.Optional(Type.String({ description: "Optional stable paper id." })),
28
+ title: Type.Optional(Type.String({ description: "Paper title." })),
29
+ url: Type.Optional(Type.String({ description: "Paper source URL." })),
30
+ source: Type.Optional(Type.String({ description: "Paper source name/domain." })),
31
+ published_at: Type.Optional(Type.String({ description: "Published time in ISO string." })),
32
+ score: Type.Optional(Type.Number({ description: "Optional score used in ranking." })),
33
+ reason: Type.Optional(Type.String({ description: "Optional rationale for selecting this paper." })),
34
+ summary: Type.Optional(Type.String({ description: "Optional short summary text." })),
35
+ evidence_ids: Type.Optional(Type.Array(Type.String({ description: "Optional evidence IDs for this paper." }))),
36
+ full_text_read: Type.Optional(Type.Boolean({ description: "Whether this paper was read from full text." })),
37
+ read_status: Type.Optional(Type.String({
38
+ description: "Read status: fulltext | partial | metadata | unread.",
39
+ })),
40
+ full_text_source: Type.Optional(Type.String({ description: "Where full text came from (e.g. arxiv_pdf)." })),
41
+ full_text_ref: Type.Optional(Type.String({ description: "Reference to full text location/path/URL." })),
42
+ unread_reason: Type.Optional(Type.String({ description: "Why full-text reading was not completed." })),
43
+ key_evidence_spans: Type.Optional(Type.Array(Type.String({ description: "Short evidence excerpts/anchors from full text." }))),
44
+ domain: Type.Optional(Type.String({ description: "Primary research domain." })),
45
+ subdomains: Type.Optional(Type.Array(Type.String({ description: "Sub-research domains." }))),
46
+ cross_domain_links: Type.Optional(Type.Array(Type.String({ description: "Cross-domain links/intersections." }))),
47
+ research_goal: Type.Optional(Type.String({ description: "Core research goal/question of this paper." })),
48
+ approach: Type.Optional(Type.String({ description: "What the paper does under that goal." })),
49
+ methodology_design: Type.Optional(Type.String({ description: "Method/experiment design summary." })),
50
+ key_contributions: Type.Optional(Type.Array(Type.String({ description: "Main contributions of this paper." }))),
51
+ practical_insights: Type.Optional(Type.Array(Type.String({ description: "Practical takeaways or reusable experience." }))),
52
+ must_understand_points: Type.Optional(Type.Array(Type.String({ description: "Critical points that must be understood after reading." }))),
53
+ limitations: Type.Optional(Type.Array(Type.String({ description: "Known limitations." }))),
54
+ evidence_anchors: Type.Optional(Type.Array(Type.Object({
55
+ section: Type.Optional(Type.String({ description: "Section title or identifier." })),
56
+ locator: Type.Optional(Type.String({ description: "Fine-grained locator, e.g. Eq.3/Table2/Page4." })),
57
+ claim: Type.String({ description: "Claim supported by this anchor." }),
58
+ quote: Type.Optional(Type.String({ description: "Short quote/excerpt supporting the claim." })),
59
+ }))),
60
+ });
61
+ const ExplorationTraceSchema = Type.Object({
62
+ query: Type.String({ description: "Exploration query text." }),
63
+ reason: Type.Optional(Type.String({ description: "Why this exploration query was chosen." })),
64
+ source: Type.Optional(Type.String({ description: "Source used for this exploration step (e.g. arxiv/openalex)." })),
65
+ candidates: Type.Optional(Type.Number({ description: "Candidate count before filtering." })),
66
+ filtered_to: Type.Optional(Type.Number({ description: "Candidate count after filtering." })),
67
+ filtered_out_reasons: Type.Optional(Type.Array(Type.String({ description: "Optional filtering reasons for dropped candidates." }))),
68
+ result_count: Type.Optional(Type.Number({ description: "Optional result count from this query." })),
69
+ });
70
+ const KnowledgeChangeSchema = Type.Object({
71
+ type: Type.String({ description: "One of NEW|CONFIRM|REVISE|BRIDGE." }),
72
+ statement: Type.String({ description: "Change statement." }),
73
+ evidence_ids: Type.Optional(Type.Array(Type.String({ description: "Evidence IDs for this change." }))),
74
+ topic: Type.Optional(Type.String({ description: "Optional topic affected by this change." })),
75
+ });
76
+ const KnowledgeUpdateSchema = Type.Object({
77
+ topic: Type.String({ description: "Topic name for this update." }),
78
+ op: Type.String({ description: "Update operation: append|revise|confirm|bridge." }),
79
+ content: Type.String({ description: "Update content text." }),
80
+ confidence: Type.Optional(Type.String({ description: "Optional confidence: low|medium|high." })),
81
+ evidence_ids: Type.Optional(Type.Array(Type.String({ description: "Evidence IDs for this update." }))),
82
+ });
83
+ const KnowledgeHypothesisSchema = Type.Object({
84
+ id: Type.Optional(Type.String({ description: "Optional hypothesis ID." })),
85
+ statement: Type.String({ description: "Hypothesis statement." }),
86
+ trigger: Type.String({ description: "Trigger type: GAP|BRIDGE|TREND|CONTRADICTION." }),
87
+ dependency_path: Type.Optional(Type.Array(Type.String({ description: "Dependency path steps." }))),
88
+ novelty: Type.Optional(Type.Number({ description: "Optional novelty score." })),
89
+ feasibility: Type.Optional(Type.Number({ description: "Optional feasibility score." })),
90
+ impact: Type.Optional(Type.Number({ description: "Optional impact score." })),
91
+ evidence_ids: Type.Optional(Type.Array(Type.String({ description: "Evidence IDs for this hypothesis." }))),
92
+ validation_status: Type.Optional(Type.String({
93
+ description: "Optional validation status: unchecked | supporting | conflicting | openreview_related | openreview_not_found.",
94
+ })),
95
+ validation_notes: Type.Optional(Type.String({ description: "Optional validation notes." })),
96
+ validation_evidence: Type.Optional(Type.Array(Type.String({ description: "Optional validation evidence links/ids." }))),
97
+ });
98
+ const KnowledgeRunLogSchema = Type.Object({
99
+ model: Type.Optional(Type.String({ description: "Optional model name." })),
100
+ duration_ms: Type.Optional(Type.Number({ description: "Optional run duration in ms." })),
101
+ error: Type.Optional(Type.String({ description: "Optional run error text." })),
102
+ degraded: Type.Optional(Type.Boolean({ description: "Whether this run used degraded behavior." })),
103
+ notes: Type.Optional(Type.String({ description: "Optional extra notes." })),
104
+ temp_full_text_dir: Type.Optional(Type.String({ description: "Temporary local directory for full-text files." })),
105
+ temp_files_downloaded: Type.Optional(Type.Number({ description: "Number of temporary full-text files downloaded." })),
106
+ temp_cleanup_status: Type.Optional(Type.String({
107
+ description: "Temporary full-text cleanup status: done | partial | failed | not_needed.",
108
+ })),
109
+ temp_cleanup_note: Type.Optional(Type.String({ description: "Optional cleanup note/error." })),
110
+ full_text_attempted: Type.Optional(Type.Number({ description: "Number of papers attempted for full-text read." })),
111
+ full_text_completed: Type.Optional(Type.Number({ description: "Number of papers successfully full-text read." })),
112
+ });
113
+ const KnowledgeStateSchema = Type.Optional(Type.Object({
114
+ core_papers: Type.Optional(Type.Array(KnowledgePaperSchema)),
115
+ exploration_papers: Type.Optional(Type.Array(KnowledgePaperSchema)),
116
+ exploration_trace: Type.Optional(Type.Array(ExplorationTraceSchema)),
117
+ knowledge_changes: Type.Optional(Type.Array(KnowledgeChangeSchema)),
118
+ knowledge_updates: Type.Optional(Type.Array(KnowledgeUpdateSchema)),
119
+ hypotheses: Type.Optional(Type.Array(KnowledgeHypothesisSchema)),
120
+ run_log: Type.Optional(KnowledgeRunLogSchema),
121
+ }));
26
122
  export const ScientifyLiteratureStateToolSchema = Type.Object({
27
123
  action: Type.String({
28
- description: 'Action: "prepare" | "record" | "feedback" | "status". `status` also returns `recent_papers` for follow-up traceability.',
124
+ description: 'Action: "prepare" | "record" | "feedback" | "status". `status` returns recent papers plus knowledge_state summary for follow-up traceability.',
29
125
  }),
30
126
  scope: Type.String({
31
127
  description: "Scope key used to isolate user/channel state.",
@@ -33,6 +129,9 @@ export const ScientifyLiteratureStateToolSchema = Type.Object({
33
129
  topic: Type.String({
34
130
  description: "Research topic text.",
35
131
  }),
132
+ project_id: Type.Optional(Type.String({
133
+ description: "Optional project id to pin knowledge_state writes.",
134
+ })),
36
135
  preferences: PreferencesSchema,
37
136
  papers: Type.Optional(Type.Array(PaperSchema, {
38
137
  description: "Papers to mark as pushed when action=record.",
@@ -60,6 +159,7 @@ export const ScientifyLiteratureStateToolSchema = Type.Object({
60
159
  tags: Type.Optional(Type.Array(Type.String({
61
160
  description: "Optional feedback tags, e.g. [\"physics simulation\", \"agent\"].",
62
161
  }))),
162
+ knowledge_state: KnowledgeStateSchema,
63
163
  });
64
164
  function readStringParam(params, key) {
65
165
  const value = params[key];
@@ -151,17 +251,474 @@ function readStringList(params, key) {
151
251
  .filter((item) => item.length > 0);
152
252
  return values.length > 0 ? values : undefined;
153
253
  }
254
+ function readKnowledgePapers(raw) {
255
+ if (!Array.isArray(raw))
256
+ return [];
257
+ const papers = [];
258
+ for (const item of raw) {
259
+ if (!item || typeof item !== "object" || Array.isArray(item))
260
+ continue;
261
+ const record = item;
262
+ const id = typeof record.id === "string" ? record.id.trim() : undefined;
263
+ const title = typeof record.title === "string" ? record.title.trim() : undefined;
264
+ const url = typeof record.url === "string" ? record.url.trim() : undefined;
265
+ const source = typeof record.source === "string" ? record.source.trim() : undefined;
266
+ const publishedAt = typeof record.published_at === "string"
267
+ ? record.published_at.trim()
268
+ : typeof record.publishedAt === "string"
269
+ ? record.publishedAt.trim()
270
+ : undefined;
271
+ const score = typeof record.score === "number" && Number.isFinite(record.score) ? record.score : undefined;
272
+ const reason = typeof record.reason === "string" ? record.reason.trim() : undefined;
273
+ const summary = typeof record.summary === "string" ? record.summary.trim() : undefined;
274
+ const evidenceRaw = record.evidence_ids ?? record.evidenceIds;
275
+ const evidenceIds = Array.isArray(evidenceRaw)
276
+ ? evidenceRaw
277
+ .filter((v) => typeof v === "string")
278
+ .map((v) => v.trim())
279
+ .filter((v) => v.length > 0)
280
+ : undefined;
281
+ const fullTextReadRaw = record.full_text_read ?? record.fullTextRead;
282
+ const fullTextRead = typeof fullTextReadRaw === "boolean" ? fullTextReadRaw : undefined;
283
+ const readStatusRaw = typeof (record.read_status ?? record.readStatus) === "string"
284
+ ? String(record.read_status ?? record.readStatus).trim().toLowerCase()
285
+ : undefined;
286
+ const readStatus = readStatusRaw && ["fulltext", "partial", "metadata", "unread"].includes(readStatusRaw)
287
+ ? readStatusRaw
288
+ : undefined;
289
+ const fullTextSource = typeof (record.full_text_source ?? record.fullTextSource) === "string"
290
+ ? String(record.full_text_source ?? record.fullTextSource).trim()
291
+ : undefined;
292
+ const fullTextRef = typeof (record.full_text_ref ?? record.fullTextRef) === "string"
293
+ ? String(record.full_text_ref ?? record.fullTextRef).trim()
294
+ : undefined;
295
+ const unreadReason = typeof (record.unread_reason ?? record.unreadReason) === "string"
296
+ ? String(record.unread_reason ?? record.unreadReason).trim()
297
+ : undefined;
298
+ const keyEvidenceSpansRaw = record.key_evidence_spans ?? record.keyEvidenceSpans;
299
+ const keyEvidenceSpans = Array.isArray(keyEvidenceSpansRaw)
300
+ ? keyEvidenceSpansRaw
301
+ .filter((v) => typeof v === "string")
302
+ .map((v) => v.trim())
303
+ .filter((v) => v.length > 0)
304
+ : undefined;
305
+ const domain = typeof record.domain === "string" ? record.domain.trim() : undefined;
306
+ const subdomainsRaw = record.subdomains;
307
+ const subdomains = Array.isArray(subdomainsRaw)
308
+ ? subdomainsRaw
309
+ .filter((v) => typeof v === "string")
310
+ .map((v) => v.trim())
311
+ .filter((v) => v.length > 0)
312
+ : undefined;
313
+ const crossDomainLinksRaw = record.cross_domain_links ?? record.crossDomainLinks;
314
+ const crossDomainLinks = Array.isArray(crossDomainLinksRaw)
315
+ ? crossDomainLinksRaw
316
+ .filter((v) => typeof v === "string")
317
+ .map((v) => v.trim())
318
+ .filter((v) => v.length > 0)
319
+ : undefined;
320
+ const researchGoal = typeof (record.research_goal ?? record.researchGoal) === "string"
321
+ ? String(record.research_goal ?? record.researchGoal).trim()
322
+ : undefined;
323
+ const approach = typeof record.approach === "string" ? record.approach.trim() : undefined;
324
+ const methodologyDesign = typeof (record.methodology_design ?? record.methodologyDesign) === "string"
325
+ ? String(record.methodology_design ?? record.methodologyDesign).trim()
326
+ : undefined;
327
+ const keyContributionsRaw = record.key_contributions ?? record.keyContributions;
328
+ const keyContributions = Array.isArray(keyContributionsRaw)
329
+ ? keyContributionsRaw
330
+ .filter((v) => typeof v === "string")
331
+ .map((v) => v.trim())
332
+ .filter((v) => v.length > 0)
333
+ : undefined;
334
+ const practicalInsightsRaw = record.practical_insights ?? record.practicalInsights;
335
+ const practicalInsights = Array.isArray(practicalInsightsRaw)
336
+ ? practicalInsightsRaw
337
+ .filter((v) => typeof v === "string")
338
+ .map((v) => v.trim())
339
+ .filter((v) => v.length > 0)
340
+ : undefined;
341
+ const mustUnderstandPointsRaw = record.must_understand_points ?? record.mustUnderstandPoints;
342
+ const mustUnderstandPoints = Array.isArray(mustUnderstandPointsRaw)
343
+ ? mustUnderstandPointsRaw
344
+ .filter((v) => typeof v === "string")
345
+ .map((v) => v.trim())
346
+ .filter((v) => v.length > 0)
347
+ : undefined;
348
+ const limitationsRaw = record.limitations;
349
+ const limitations = Array.isArray(limitationsRaw)
350
+ ? limitationsRaw
351
+ .filter((v) => typeof v === "string")
352
+ .map((v) => v.trim())
353
+ .filter((v) => v.length > 0)
354
+ : undefined;
355
+ const evidenceAnchorsRaw = record.evidence_anchors ?? record.evidenceAnchors;
356
+ const evidenceAnchors = Array.isArray(evidenceAnchorsRaw)
357
+ ? evidenceAnchorsRaw
358
+ .filter((v) => !!v && typeof v === "object" && !Array.isArray(v))
359
+ .map((anchor) => {
360
+ const section = typeof anchor.section === "string" ? anchor.section.trim() : undefined;
361
+ const locator = typeof anchor.locator === "string" ? anchor.locator.trim() : undefined;
362
+ const claim = typeof anchor.claim === "string" ? anchor.claim.trim() : "";
363
+ const quote = typeof anchor.quote === "string" ? anchor.quote.trim() : undefined;
364
+ if (!claim)
365
+ return undefined;
366
+ return {
367
+ ...(section ? { section } : {}),
368
+ ...(locator ? { locator } : {}),
369
+ claim,
370
+ ...(quote ? { quote } : {}),
371
+ };
372
+ })
373
+ .filter((v) => Boolean(v))
374
+ : undefined;
375
+ if (!id &&
376
+ !title &&
377
+ !url &&
378
+ !summary &&
379
+ !reason &&
380
+ !domain &&
381
+ !researchGoal &&
382
+ !approach &&
383
+ !methodologyDesign &&
384
+ (!keyContributions || keyContributions.length === 0) &&
385
+ (!practicalInsights || practicalInsights.length === 0) &&
386
+ (!mustUnderstandPoints || mustUnderstandPoints.length === 0) &&
387
+ (!limitations || limitations.length === 0)) {
388
+ continue;
389
+ }
390
+ papers.push({
391
+ ...(id ? { id } : {}),
392
+ ...(title ? { title } : {}),
393
+ ...(url ? { url } : {}),
394
+ ...(source ? { source } : {}),
395
+ ...(publishedAt ? { publishedAt } : {}),
396
+ ...(score !== undefined ? { score } : {}),
397
+ ...(reason ? { reason } : {}),
398
+ ...(summary ? { summary } : {}),
399
+ ...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
400
+ ...(typeof fullTextRead === "boolean" ? { fullTextRead } : {}),
401
+ ...(readStatus ? { readStatus } : {}),
402
+ ...(fullTextSource ? { fullTextSource } : {}),
403
+ ...(fullTextRef ? { fullTextRef } : {}),
404
+ ...(unreadReason ? { unreadReason } : {}),
405
+ ...(keyEvidenceSpans && keyEvidenceSpans.length > 0 ? { keyEvidenceSpans } : {}),
406
+ ...(domain ? { domain } : {}),
407
+ ...(subdomains && subdomains.length > 0 ? { subdomains } : {}),
408
+ ...(crossDomainLinks && crossDomainLinks.length > 0 ? { crossDomainLinks } : {}),
409
+ ...(researchGoal ? { researchGoal } : {}),
410
+ ...(approach ? { approach } : {}),
411
+ ...(methodologyDesign ? { methodologyDesign } : {}),
412
+ ...(keyContributions && keyContributions.length > 0 ? { keyContributions } : {}),
413
+ ...(practicalInsights && practicalInsights.length > 0 ? { practicalInsights } : {}),
414
+ ...(mustUnderstandPoints && mustUnderstandPoints.length > 0 ? { mustUnderstandPoints } : {}),
415
+ ...(limitations && limitations.length > 0 ? { limitations } : {}),
416
+ ...(evidenceAnchors && evidenceAnchors.length > 0 ? { evidenceAnchors } : {}),
417
+ });
418
+ }
419
+ return papers;
420
+ }
421
+ function readKnowledgeStatePayload(params) {
422
+ const raw = params.knowledge_state;
423
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
424
+ return undefined;
425
+ const record = raw;
426
+ const corePapers = readKnowledgePapers(record.core_papers ?? record.corePapers);
427
+ const explorationPapers = readKnowledgePapers(record.exploration_papers ?? record.explorationPapers);
428
+ const explorationTraceRaw = record.exploration_trace ?? record.explorationTrace;
429
+ const explorationTrace = Array.isArray(explorationTraceRaw)
430
+ ? explorationTraceRaw
431
+ .filter((item) => !!item && typeof item === "object" && !Array.isArray(item))
432
+ .map((item) => {
433
+ const query = typeof item.query === "string" ? item.query.trim() : "";
434
+ if (!query)
435
+ return undefined;
436
+ const reason = typeof item.reason === "string" ? item.reason.trim() : undefined;
437
+ const source = typeof item.source === "string" ? item.source.trim() : undefined;
438
+ const candidatesRaw = item.candidates;
439
+ const candidates = typeof candidatesRaw === "number" && Number.isFinite(candidatesRaw) ? candidatesRaw : undefined;
440
+ const filteredToRaw = item.filtered_to ?? item.filteredTo;
441
+ const filteredTo = typeof filteredToRaw === "number" && Number.isFinite(filteredToRaw) ? filteredToRaw : undefined;
442
+ const filteredOutRaw = item.filtered_out_reasons ?? item.filteredOutReasons;
443
+ const filteredOutReasons = Array.isArray(filteredOutRaw)
444
+ ? filteredOutRaw
445
+ .filter((v) => typeof v === "string")
446
+ .map((v) => v.trim())
447
+ .filter((v) => v.length > 0)
448
+ : undefined;
449
+ const resultCountRaw = item.result_count ?? item.resultCount;
450
+ const resultCount = typeof resultCountRaw === "number" && Number.isFinite(resultCountRaw)
451
+ ? resultCountRaw
452
+ : undefined;
453
+ return {
454
+ query,
455
+ ...(reason ? { reason } : {}),
456
+ ...(source ? { source } : {}),
457
+ ...(typeof candidates === "number" ? { candidates } : {}),
458
+ ...(typeof filteredTo === "number" ? { filteredTo } : {}),
459
+ ...(filteredOutReasons && filteredOutReasons.length > 0 ? { filteredOutReasons } : {}),
460
+ ...(typeof resultCount === "number" ? { resultCount } : {}),
461
+ };
462
+ })
463
+ .filter((item) => Boolean(item))
464
+ : [];
465
+ const knowledgeChangesRaw = record.knowledge_changes ?? record.knowledgeChanges;
466
+ const knowledgeChanges = [];
467
+ if (Array.isArray(knowledgeChangesRaw)) {
468
+ for (const item of knowledgeChangesRaw) {
469
+ if (!item || typeof item !== "object" || Array.isArray(item))
470
+ continue;
471
+ const row = item;
472
+ const rawType = typeof row.type === "string" ? row.type.trim().toUpperCase() : "NEW";
473
+ const type = ["NEW", "CONFIRM", "REVISE", "BRIDGE"].includes(rawType)
474
+ ? rawType
475
+ : "NEW";
476
+ const statement = typeof row.statement === "string" ? row.statement.trim() : "";
477
+ if (!statement)
478
+ continue;
479
+ const evidenceRaw = row.evidence_ids ?? row.evidenceIds;
480
+ const evidenceIds = Array.isArray(evidenceRaw)
481
+ ? evidenceRaw
482
+ .filter((v) => typeof v === "string")
483
+ .map((v) => v.trim())
484
+ .filter((v) => v.length > 0)
485
+ : undefined;
486
+ const topic = typeof row.topic === "string" ? row.topic.trim() : undefined;
487
+ knowledgeChanges.push({
488
+ type,
489
+ statement,
490
+ ...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
491
+ ...(topic ? { topic } : {}),
492
+ });
493
+ }
494
+ }
495
+ const knowledgeUpdatesRaw = record.knowledge_updates ?? record.knowledgeUpdates;
496
+ const knowledgeUpdates = [];
497
+ if (Array.isArray(knowledgeUpdatesRaw)) {
498
+ for (const item of knowledgeUpdatesRaw) {
499
+ if (!item || typeof item !== "object" || Array.isArray(item))
500
+ continue;
501
+ const row = item;
502
+ const topic = typeof row.topic === "string" ? row.topic.trim() : "";
503
+ const rawOp = typeof row.op === "string" ? row.op.trim().toLowerCase() : "append";
504
+ const op = ["append", "revise", "confirm", "bridge"].includes(rawOp)
505
+ ? rawOp
506
+ : "append";
507
+ const content = typeof row.content === "string" ? row.content.trim() : "";
508
+ if (!topic || !content)
509
+ continue;
510
+ const rawConfidence = typeof row.confidence === "string" ? row.confidence.trim().toLowerCase() : undefined;
511
+ const confidence = rawConfidence && ["low", "medium", "high"].includes(rawConfidence)
512
+ ? rawConfidence
513
+ : undefined;
514
+ const evidenceRaw = row.evidence_ids ?? row.evidenceIds;
515
+ const evidenceIds = Array.isArray(evidenceRaw)
516
+ ? evidenceRaw
517
+ .filter((v) => typeof v === "string")
518
+ .map((v) => v.trim())
519
+ .filter((v) => v.length > 0)
520
+ : undefined;
521
+ knowledgeUpdates.push({
522
+ topic,
523
+ op,
524
+ content,
525
+ ...(confidence ? { confidence } : {}),
526
+ ...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
527
+ });
528
+ }
529
+ }
530
+ const hypothesesRaw = record.hypotheses;
531
+ const hypotheses = [];
532
+ if (Array.isArray(hypothesesRaw)) {
533
+ for (const item of hypothesesRaw) {
534
+ if (!item || typeof item !== "object" || Array.isArray(item))
535
+ continue;
536
+ const row = item;
537
+ const id = typeof row.id === "string" ? row.id.trim() : undefined;
538
+ const statement = typeof row.statement === "string" ? row.statement.trim() : "";
539
+ const rawTrigger = typeof row.trigger === "string" ? row.trigger.trim().toUpperCase() : "TREND";
540
+ const trigger = ["GAP", "BRIDGE", "TREND", "CONTRADICTION"].includes(rawTrigger)
541
+ ? rawTrigger
542
+ : "TREND";
543
+ if (!statement)
544
+ continue;
545
+ const dependencyRaw = row.dependency_path ?? row.dependencyPath;
546
+ const dependencyPath = Array.isArray(dependencyRaw)
547
+ ? dependencyRaw
548
+ .filter((v) => typeof v === "string")
549
+ .map((v) => v.trim())
550
+ .filter((v) => v.length > 0)
551
+ : undefined;
552
+ const evidenceRaw = row.evidence_ids ?? row.evidenceIds;
553
+ const evidenceIds = Array.isArray(evidenceRaw)
554
+ ? evidenceRaw
555
+ .filter((v) => typeof v === "string")
556
+ .map((v) => v.trim())
557
+ .filter((v) => v.length > 0)
558
+ : undefined;
559
+ const validationStatusRaw = typeof (row.validation_status ?? row.validationStatus) === "string"
560
+ ? String(row.validation_status ?? row.validationStatus).trim().toLowerCase()
561
+ : undefined;
562
+ const validationStatus = validationStatusRaw &&
563
+ ["unchecked", "supporting", "conflicting", "openreview_related", "openreview_not_found"].includes(validationStatusRaw)
564
+ ? validationStatusRaw
565
+ : undefined;
566
+ const validationNotes = typeof (row.validation_notes ?? row.validationNotes) === "string"
567
+ ? String(row.validation_notes ?? row.validationNotes).trim()
568
+ : undefined;
569
+ const validationEvidenceRaw = row.validation_evidence ?? row.validationEvidence;
570
+ const validationEvidence = Array.isArray(validationEvidenceRaw)
571
+ ? validationEvidenceRaw
572
+ .filter((v) => typeof v === "string")
573
+ .map((v) => v.trim())
574
+ .filter((v) => v.length > 0)
575
+ : undefined;
576
+ const novelty = typeof row.novelty === "number" && Number.isFinite(row.novelty) ? row.novelty : undefined;
577
+ const feasibility = typeof row.feasibility === "number" && Number.isFinite(row.feasibility) ? row.feasibility : undefined;
578
+ const impact = typeof row.impact === "number" && Number.isFinite(row.impact) ? row.impact : undefined;
579
+ hypotheses.push({
580
+ ...(id ? { id } : {}),
581
+ statement,
582
+ trigger,
583
+ ...(dependencyPath && dependencyPath.length > 0 ? { dependencyPath } : {}),
584
+ ...(evidenceIds && evidenceIds.length > 0 ? { evidenceIds } : {}),
585
+ ...(validationStatus ? { validationStatus } : {}),
586
+ ...(validationNotes ? { validationNotes } : {}),
587
+ ...(validationEvidence && validationEvidence.length > 0 ? { validationEvidence } : {}),
588
+ ...(novelty !== undefined ? { novelty } : {}),
589
+ ...(feasibility !== undefined ? { feasibility } : {}),
590
+ ...(impact !== undefined ? { impact } : {}),
591
+ });
592
+ }
593
+ }
594
+ const runLogRaw = record.run_log ?? record.runLog;
595
+ const runLog = runLogRaw && typeof runLogRaw === "object" && !Array.isArray(runLogRaw)
596
+ ? (() => {
597
+ const runLogRecord = runLogRaw;
598
+ const model = typeof runLogRecord.model === "string" ? runLogRecord.model.trim() : undefined;
599
+ const durationMsRaw = runLogRecord.duration_ms ?? runLogRecord.durationMs;
600
+ const durationMs = typeof durationMsRaw === "number" && Number.isFinite(durationMsRaw) ? durationMsRaw : undefined;
601
+ const error = typeof runLogRecord.error === "string" ? runLogRecord.error.trim() : undefined;
602
+ const degraded = runLogRecord.degraded === true;
603
+ const notes = typeof runLogRecord.notes === "string" ? runLogRecord.notes.trim() : undefined;
604
+ const tempFullTextDir = typeof (runLogRecord.temp_full_text_dir ?? runLogRecord.tempFullTextDir) === "string"
605
+ ? String(runLogRecord.temp_full_text_dir ?? runLogRecord.tempFullTextDir).trim()
606
+ : undefined;
607
+ const tempFilesDownloadedRaw = runLogRecord.temp_files_downloaded ?? runLogRecord.tempFilesDownloaded;
608
+ const tempFilesDownloaded = typeof tempFilesDownloadedRaw === "number" && Number.isFinite(tempFilesDownloadedRaw)
609
+ ? tempFilesDownloadedRaw
610
+ : undefined;
611
+ const tempCleanupStatusRaw = typeof (runLogRecord.temp_cleanup_status ?? runLogRecord.tempCleanupStatus) === "string"
612
+ ? String(runLogRecord.temp_cleanup_status ?? runLogRecord.tempCleanupStatus).trim().toLowerCase()
613
+ : undefined;
614
+ const tempCleanupStatus = tempCleanupStatusRaw && ["done", "partial", "failed", "not_needed"].includes(tempCleanupStatusRaw)
615
+ ? tempCleanupStatusRaw
616
+ : undefined;
617
+ const tempCleanupNote = typeof (runLogRecord.temp_cleanup_note ?? runLogRecord.tempCleanupNote) === "string"
618
+ ? String(runLogRecord.temp_cleanup_note ?? runLogRecord.tempCleanupNote).trim()
619
+ : undefined;
620
+ const fullTextAttemptedRaw = runLogRecord.full_text_attempted ?? runLogRecord.fullTextAttempted;
621
+ const fullTextAttempted = typeof fullTextAttemptedRaw === "number" && Number.isFinite(fullTextAttemptedRaw)
622
+ ? fullTextAttemptedRaw
623
+ : undefined;
624
+ const fullTextCompletedRaw = runLogRecord.full_text_completed ?? runLogRecord.fullTextCompleted;
625
+ const fullTextCompleted = typeof fullTextCompletedRaw === "number" && Number.isFinite(fullTextCompletedRaw)
626
+ ? fullTextCompletedRaw
627
+ : undefined;
628
+ if (!model &&
629
+ durationMs === undefined &&
630
+ !error &&
631
+ !degraded &&
632
+ !notes &&
633
+ !tempFullTextDir &&
634
+ tempFilesDownloaded === undefined &&
635
+ !tempCleanupStatus &&
636
+ !tempCleanupNote &&
637
+ fullTextAttempted === undefined &&
638
+ fullTextCompleted === undefined) {
639
+ return undefined;
640
+ }
641
+ return {
642
+ ...(model ? { model } : {}),
643
+ ...(durationMs !== undefined ? { durationMs } : {}),
644
+ ...(error ? { error } : {}),
645
+ ...(degraded ? { degraded } : {}),
646
+ ...(notes ? { notes } : {}),
647
+ ...(tempFullTextDir ? { tempFullTextDir } : {}),
648
+ ...(tempFilesDownloaded !== undefined ? { tempFilesDownloaded } : {}),
649
+ ...(tempCleanupStatus ? { tempCleanupStatus } : {}),
650
+ ...(tempCleanupNote ? { tempCleanupNote } : {}),
651
+ ...(fullTextAttempted !== undefined ? { fullTextAttempted } : {}),
652
+ ...(fullTextCompleted !== undefined ? { fullTextCompleted } : {}),
653
+ };
654
+ })()
655
+ : undefined;
656
+ const hasAny = corePapers.length > 0 ||
657
+ explorationPapers.length > 0 ||
658
+ explorationTrace.length > 0 ||
659
+ knowledgeChanges.length > 0 ||
660
+ knowledgeUpdates.length > 0 ||
661
+ hypotheses.length > 0 ||
662
+ Boolean(runLog);
663
+ if (!hasAny)
664
+ return undefined;
665
+ return {
666
+ ...(corePapers.length > 0 ? { corePapers } : {}),
667
+ ...(explorationPapers.length > 0 ? { explorationPapers } : {}),
668
+ ...(explorationTrace.length > 0 ? { explorationTrace } : {}),
669
+ ...(knowledgeChanges.length > 0 ? { knowledgeChanges } : {}),
670
+ ...(knowledgeUpdates.length > 0 ? { knowledgeUpdates } : {}),
671
+ ...(hypotheses.length > 0 ? { hypotheses } : {}),
672
+ ...(runLog ? { runLog } : {}),
673
+ };
674
+ }
675
+ function serializeKnowledgePaper(paper) {
676
+ return {
677
+ id: paper.id ?? null,
678
+ title: paper.title ?? null,
679
+ url: paper.url ?? null,
680
+ source: paper.source ?? null,
681
+ published_at: paper.publishedAt ?? null,
682
+ score: paper.score ?? null,
683
+ reason: paper.reason ?? null,
684
+ summary: paper.summary ?? null,
685
+ evidence_ids: paper.evidenceIds ?? [],
686
+ full_text_read: paper.fullTextRead ?? null,
687
+ read_status: paper.readStatus ?? null,
688
+ full_text_source: paper.fullTextSource ?? null,
689
+ full_text_ref: paper.fullTextRef ?? null,
690
+ unread_reason: paper.unreadReason ?? null,
691
+ key_evidence_spans: paper.keyEvidenceSpans ?? [],
692
+ domain: paper.domain ?? null,
693
+ subdomains: paper.subdomains ?? [],
694
+ cross_domain_links: paper.crossDomainLinks ?? [],
695
+ research_goal: paper.researchGoal ?? null,
696
+ approach: paper.approach ?? null,
697
+ methodology_design: paper.methodologyDesign ?? null,
698
+ key_contributions: paper.keyContributions ?? [],
699
+ practical_insights: paper.practicalInsights ?? [],
700
+ must_understand_points: paper.mustUnderstandPoints ?? [],
701
+ limitations: paper.limitations ?? [],
702
+ evidence_anchors: (paper.evidenceAnchors ?? []).map((anchor) => ({
703
+ section: anchor.section ?? null,
704
+ locator: anchor.locator ?? null,
705
+ claim: anchor.claim,
706
+ quote: anchor.quote ?? null,
707
+ })),
708
+ };
709
+ }
154
710
  export function createScientifyLiteratureStateTool() {
155
711
  return {
156
712
  label: "Scientify Literature State",
157
713
  name: "scientify_literature_state",
158
- description: "Manage incremental literature state for subscriptions: prepare dedupe context, record pushed papers, persist lightweight feedback memory, and query status.",
714
+ description: "Manage incremental research state for subscriptions: prepare dedupe context, record pushed papers plus knowledge_state artifacts, persist lightweight feedback memory, and query status.",
159
715
  parameters: ScientifyLiteratureStateToolSchema,
160
716
  execute: async (_toolCallId, rawArgs) => {
161
717
  const params = (rawArgs ?? {});
162
718
  const action = (readStringParam(params, "action") ?? "").toLowerCase();
163
719
  const scope = readStringParam(params, "scope");
164
720
  const topic = readStringParam(params, "topic");
721
+ const projectId = readStringParam(params, "project_id");
165
722
  const preferences = readPreferences(params);
166
723
  if (!scope || !topic) {
167
724
  return Result.err("invalid_params", "Both `scope` and `topic` are required.");
@@ -197,6 +754,7 @@ export function createScientifyLiteratureStateTool() {
197
754
  const status = readStringParam(params, "status");
198
755
  const runId = readStringParam(params, "run_id");
199
756
  const note = readStringParam(params, "note");
757
+ const knowledgeState = readKnowledgeStatePayload(params);
200
758
  const recorded = await recordIncrementalPush({
201
759
  scope,
202
760
  topic,
@@ -205,6 +763,8 @@ export function createScientifyLiteratureStateTool() {
205
763
  runId,
206
764
  note,
207
765
  papers,
766
+ ...(projectId ? { projectId } : {}),
767
+ ...(knowledgeState ? { knowledgeState } : {}),
208
768
  });
209
769
  return Result.ok({
210
770
  action,
@@ -227,6 +787,30 @@ export function createScientifyLiteratureStateTool() {
227
787
  recorded_papers: recorded.recordedPapers,
228
788
  total_known_papers: recorded.totalKnownPapers,
229
789
  pushed_at_ms: recorded.pushedAtMs,
790
+ project_id: recorded.projectId ?? null,
791
+ knowledge_state_summary: recorded.knowledgeStateSummary
792
+ ? {
793
+ project_id: recorded.knowledgeStateSummary.projectId,
794
+ stream_key: recorded.knowledgeStateSummary.streamKey,
795
+ total_runs: recorded.knowledgeStateSummary.totalRuns,
796
+ total_hypotheses: recorded.knowledgeStateSummary.totalHypotheses,
797
+ knowledge_topics_count: recorded.knowledgeStateSummary.knowledgeTopicsCount,
798
+ paper_notes_count: recorded.knowledgeStateSummary.paperNotesCount,
799
+ recent_full_text_read_count: recorded.knowledgeStateSummary.recentFullTextReadCount,
800
+ recent_not_full_text_read_count: recorded.knowledgeStateSummary.recentNotFullTextReadCount,
801
+ quality_gate: {
802
+ passed: recorded.knowledgeStateSummary.qualityGate.passed,
803
+ full_text_coverage_pct: recorded.knowledgeStateSummary.qualityGate.fullTextCoveragePct,
804
+ evidence_binding_rate_pct: recorded.knowledgeStateSummary.qualityGate.evidenceBindingRatePct,
805
+ citation_error_rate_pct: recorded.knowledgeStateSummary.qualityGate.citationErrorRatePct,
806
+ reasons: recorded.knowledgeStateSummary.qualityGate.reasons,
807
+ },
808
+ unread_core_paper_ids: recorded.knowledgeStateSummary.unreadCorePaperIds,
809
+ last_run_at_ms: recorded.knowledgeStateSummary.lastRunAtMs ?? null,
810
+ last_status: recorded.knowledgeStateSummary.lastStatus ?? null,
811
+ recent_papers: recorded.knowledgeStateSummary.recentPapers.map(serializeKnowledgePaper),
812
+ }
813
+ : null,
230
814
  });
231
815
  }
232
816
  if (action === "feedback") {
@@ -275,7 +859,11 @@ export function createScientifyLiteratureStateTool() {
275
859
  });
276
860
  }
277
861
  if (action === "status") {
278
- const status = await getIncrementalStateStatus({ scope, topic });
862
+ const status = await getIncrementalStateStatus({
863
+ scope,
864
+ topic,
865
+ ...(projectId ? { projectId } : {}),
866
+ });
279
867
  return Result.ok({
280
868
  action,
281
869
  scope: status.scope,
@@ -299,6 +887,53 @@ export function createScientifyLiteratureStateTool() {
299
887
  total_runs: status.totalRuns,
300
888
  last_status: status.lastStatus ?? null,
301
889
  last_pushed_at_ms: status.lastPushedAtMs ?? null,
890
+ knowledge_state_summary: status.knowledgeStateSummary
891
+ ? {
892
+ project_id: status.knowledgeStateSummary.projectId,
893
+ stream_key: status.knowledgeStateSummary.streamKey,
894
+ total_runs: status.knowledgeStateSummary.totalRuns,
895
+ total_hypotheses: status.knowledgeStateSummary.totalHypotheses,
896
+ knowledge_topics_count: status.knowledgeStateSummary.knowledgeTopicsCount,
897
+ paper_notes_count: status.knowledgeStateSummary.paperNotesCount,
898
+ recent_full_text_read_count: status.knowledgeStateSummary.recentFullTextReadCount,
899
+ recent_not_full_text_read_count: status.knowledgeStateSummary.recentNotFullTextReadCount,
900
+ quality_gate: {
901
+ passed: status.knowledgeStateSummary.qualityGate.passed,
902
+ full_text_coverage_pct: status.knowledgeStateSummary.qualityGate.fullTextCoveragePct,
903
+ evidence_binding_rate_pct: status.knowledgeStateSummary.qualityGate.evidenceBindingRatePct,
904
+ citation_error_rate_pct: status.knowledgeStateSummary.qualityGate.citationErrorRatePct,
905
+ reasons: status.knowledgeStateSummary.qualityGate.reasons,
906
+ },
907
+ unread_core_paper_ids: status.knowledgeStateSummary.unreadCorePaperIds,
908
+ last_run_at_ms: status.knowledgeStateSummary.lastRunAtMs ?? null,
909
+ last_status: status.knowledgeStateSummary.lastStatus ?? null,
910
+ recent_papers: status.knowledgeStateSummary.recentPapers.map(serializeKnowledgePaper),
911
+ }
912
+ : null,
913
+ recent_hypotheses: status.recentHypotheses.map((item) => ({
914
+ id: item.id,
915
+ statement: item.statement,
916
+ trigger: item.trigger,
917
+ created_at_ms: item.createdAtMs,
918
+ file: item.file,
919
+ })),
920
+ recent_change_stats: status.recentChangeStats.map((item) => ({
921
+ day: item.day,
922
+ run_id: item.runId,
923
+ new_count: item.newCount,
924
+ confirm_count: item.confirmCount,
925
+ revise_count: item.reviseCount,
926
+ bridge_count: item.bridgeCount,
927
+ })),
928
+ last_exploration_trace: status.lastExplorationTrace.map((item) => ({
929
+ query: item.query,
930
+ reason: item.reason ?? null,
931
+ source: item.source ?? null,
932
+ candidates: item.candidates ?? null,
933
+ filtered_to: item.filteredTo ?? null,
934
+ filtered_out_reasons: item.filteredOutReasons ?? [],
935
+ result_count: item.resultCount ?? null,
936
+ })),
302
937
  recent_papers: status.recentPapers.map((paper) => ({
303
938
  id: paper.id,
304
939
  title: paper.title ?? null,