@xopcai/xopc 0.0.159 → 0.0.161
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{action-boundary-settings-panel-CaRK2vIy.js → action-boundary-settings-panel-BIVBUiNT.js} +1 -1
- package/dist/gateway/static/root/assets/activity-store-o7GRljfD.js +1 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BeVf03hx.js → agent-avatar-dicebear-9R3szLMc.js} +1 -1
- package/dist/gateway/static/root/assets/agents-_qbUaYbs.js +2 -0
- package/dist/gateway/static/root/assets/{api-BjcBIV2O.js → api-jSTqFMq9.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-BG2ulMYw.js → app-management-settings-panel-D81cyE76.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-0Kjenr2T.js → appearance-settings-Ct6bZQCV.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-0sPqD0FK.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-ChiNnH1e.js → archive-plugin-CfD7AZKn.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-pbBlQR-g.js → attachment-load-CP_Gaxt3.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-CTZDOyDy.js → automation-suggestion-card-BGKM_NDZ.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-BxWBuBsE.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-BeZGzpGF.js → binary-plugins-IPr2EVnS.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-BnDmhN5Z.js → block-editor-B6aHEGA6.js} +3 -3
- package/dist/gateway/static/root/assets/browser-settings-page-C499b9AH.js +8 -0
- package/dist/gateway/static/root/assets/{capability-presets-api-Ii36j_7v.js → capability-presets-api-CCX9-LjU.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-Cl3dw4yh.js → capability-presets-settings-panel-Dva9SiPK.js} +2 -2
- package/dist/gateway/static/root/assets/{channel-recipient-api-CFqrSi5K.js → channel-recipient-api-MmLMLDWf.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-DNAU7u9o.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-CIOH7tgn.js +1 -0
- package/dist/gateway/static/root/assets/check-DUBLcKHA.js +1 -0
- package/dist/gateway/static/root/assets/circle-alert-BEjgurr2.js +1 -0
- package/dist/gateway/static/root/assets/cn-JdwAhgNB.js +1 -0
- package/dist/gateway/static/root/assets/{connector-logo-CiLxuZ8S.js → connector-logo-BbjzIs87.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-TwiBp8vY.js +2 -0
- package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
- package/dist/gateway/static/root/assets/desktop-pet-CYH5ZSXy.js +1 -0
- package/dist/gateway/static/root/assets/desktop-pet-settings-DQLqGLc0.js +1 -0
- package/dist/gateway/static/root/assets/desktop-pet-sprite-DTqXSpqf.js +1 -0
- package/dist/gateway/static/root/assets/{directory-picker-path-field-CC2VL5Mc.js → directory-picker-path-field-ClsiXhmz.js} +1 -1
- package/dist/gateway/static/root/assets/{dreaming-settings-KY7ZfVsy.js → dreaming-settings-CjMPPdzg.js} +3 -3
- package/dist/gateway/static/root/assets/{extension-debug-page-C8KBeucs.js → extension-debug-page-57eizjhB.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CV-5Kcyk.js → extension-page-CsbSb3u1.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DvUtDrAu.js → extension-settings-page-DKA9-lyP.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-api-DXfVeJLc.js → gateway-config-api-DIPJuzBZ.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-settings-CDLqAQY7.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-cxQ94qMm.js → gateway-startup-retry-DrfSJtvT.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-Q3B0E-0s.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-C5VAK04G.js +4 -0
- package/dist/gateway/static/root/assets/{goals-config-api-P8VhLqYF.js → goals-config-api-lGC10UkU.js} +1 -1
- package/dist/gateway/static/root/assets/goals-page-BJb-5IYw.js +2 -0
- package/dist/gateway/static/root/assets/{goals-settings-DHXyaH_O.js → goals-settings-BWq0pdrA.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CNckP_Dv.js → heartbeat-settings-D4o8BJVv.js} +1 -1
- package/dist/gateway/static/root/assets/index-CkUHH170.css +1 -0
- package/dist/gateway/static/root/assets/index-vq_cJZJC.js +199 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-jXmv1RVX.js → keyboard-shortcuts-settings-6T2QYehS.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-euH17Kvb.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-Cs5LKjtc.js → local-apps-page-DCjg2K9f.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-C_M71MK9.js → locale-store-Dar-xqQr.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-CyYCBqym.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-BFqeSNte.js → markdown-split-rt1XO43g.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-Cc8379gq.js → media-plugins-Bwo8CJuh.js} +1 -1
- package/dist/gateway/static/root/assets/messages-CxySDn9d.js +5 -0
- package/dist/gateway/static/root/assets/models-hub-panel-DObxxjc9.js +2 -0
- package/dist/gateway/static/root/assets/{notes-page-DlGU4ipo.js → notes-page-Dp-pkX7d.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-CCb4RXcZ.js +4 -0
- package/dist/gateway/static/root/assets/{page-tabs-kFOswGlG.js → page-tabs-CzZ4iKOb.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-CvjuvABg.js → preview-open-alternatives-3E_Qa-58.js} +1 -1
- package/dist/gateway/static/root/assets/product-delivery-BTRT9kCS.js +1 -0
- package/dist/gateway/static/root/assets/product-open-page-B2T48Sbt.js +1 -0
- package/dist/gateway/static/root/assets/project-detail-page-BvEcriRC.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-B_e79MRJ.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-DGcqnYfR.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-DwrD7ODx.js → save-bar-controls-CkeKasGm.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-Ba4WzTmE.js → schedule-field-TkqUL3wm.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-DvydL8eF.js → schema-form-DC-iU5IG.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-DQZhrDJ8.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BkdeN99Y.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-LaQqGGej.js → settings-collapsible-section-Dpqth3oA.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DruI__qO.js → settings-form-section-CNqF6IwY.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-UvA-eDhF.js → settings-loading-skeleton-DkvwitK_.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-DG-XMIPu.js → settings-page-layout-DWvNgISx.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-5oCGSebm.js → settings-page-qPG7I2zd.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-lwRAh9aw.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-FLVeTrRt.js → share-preview-page-DGpSPW-n.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-BGj9IVUr.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-B2UBAs5U.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-DdjzFZ8I.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-CeK93xVi.js → system-settings-panel-8TxxSbUw.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-C8ohIHhw.js → theme-store-B5FzMWOx.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-D1Lr2gG0.js → typed-models-lib-DEGtSA5h.js} +1 -1
- package/dist/gateway/static/root/assets/{use-channel-catalog-BWzou5ar.js → use-channel-catalog-BDU6FneG.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-BTTZpAGV.js +1 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-DuDQd-WU.js +1 -0
- package/dist/gateway/static/root/assets/work-discovery-page-BUVkVCsW.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-DeEPPu4n.js +1 -0
- package/dist/gateway/static/root/assets/work-page-DQNUyTUq.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Bynqe4kV.js → workflow-run-setup-panel-Cwi8Pz7n.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-BIy4wrzO.js +11 -0
- package/dist/gateway/static/root/index.html +12 -10
- package/dist/package.js +1 -1
- package/dist/src/automations/service/action-executor.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/chat-stream/mapper.d.ts +1 -0
- package/dist/src/gateway/chat-stream/mapper.js +43 -7
- package/dist/src/gateway/hono/routes/home.js +15 -2
- package/dist/src/gateway/hono/routes/work-discovery.js +451 -0
- package/dist/src/gateway/hono/sse.d.ts +2 -0
- package/dist/src/gateway/hono/sse.js +10 -3
- package/dist/src/gateway/run-loop.js +17 -7
- package/dist/src/gateway/server.d.ts +1 -0
- package/dist/src/gateway/server.js +9 -0
- package/dist/src/gateway/service.d.ts +2 -0
- package/dist/src/gateway/service.js +27 -2
- package/dist/src/local-apps/preview-runtime-bridge.d.ts +1 -1
- package/dist/src/local-apps/preview-runtime-bridge.js +14 -11
- package/dist/src/mobile/notification-service.js +1 -0
- package/dist/src/pets/factory.js +23 -26
- package/dist/src/pets/manifest.d.ts +2 -1
- package/dist/src/pets/manifest.js +15 -8
- package/dist/src/pets/validator.js +8 -3
- package/dist/src/proactive/calendar-signals.d.ts +4 -0
- package/dist/src/proactive/calendar-signals.js +120 -0
- package/dist/src/proactive/focus-service.d.ts +62 -0
- package/dist/src/proactive/focus-service.js +173 -0
- package/dist/src/proactive/index.d.ts +7 -0
- package/dist/src/proactive/index.js +7 -0
- package/dist/src/proactive/insight-repository.d.ts +19 -0
- package/dist/src/proactive/insight-repository.js +106 -0
- package/dist/src/proactive/run-processor.d.ts +19 -0
- package/dist/src/proactive/run-processor.js +125 -0
- package/dist/src/proactive/trial-expiry-reconciler.d.ts +5 -0
- package/dist/src/proactive/trial-expiry-reconciler.js +30 -0
- package/dist/src/proactive/types.d.ts +64 -0
- package/dist/src/proactive/types.js +1 -0
- package/dist/src/proactive/watch-repository.d.ts +28 -0
- package/dist/src/proactive/watch-repository.js +129 -0
- package/dist/src/storage/sqlite/knowledge-repository.d.ts +1 -0
- package/dist/src/storage/sqlite/knowledge-repository.js +4 -0
- package/dist/src/storage/sqlite/migrations/047_work_discovery_sources.sql +16 -0
- package/dist/src/storage/sqlite/migrations/048_work_understanding_investigations.sql +40 -0
- package/dist/src/storage/sqlite/migrations/049_work_understanding_threads.sql +52 -0
- package/dist/src/storage/sqlite/migrations/050_work_discovery_source_refreshes.sql +15 -0
- package/dist/src/storage/sqlite/migrations/051_focus_watches.sql +26 -0
- package/dist/src/storage/sqlite/migrations/052_proactive_insights.sql +25 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/work-discovery/analyzer.d.ts +12 -1
- package/dist/src/work-discovery/analyzer.js +126 -7
- package/dist/src/work-discovery/candidate-discovery.d.ts +17 -0
- package/dist/src/work-discovery/candidate-discovery.js +279 -0
- package/dist/src/work-discovery/evaluation.d.ts +11 -0
- package/dist/src/work-discovery/evaluation.js +17 -0
- package/dist/src/work-discovery/governance.d.ts +25 -0
- package/dist/src/work-discovery/governance.js +96 -0
- package/dist/src/work-discovery/incremental-repository.d.ts +13 -0
- package/dist/src/work-discovery/incremental-repository.js +55 -0
- package/dist/src/work-discovery/incremental.d.ts +2 -0
- package/dist/src/work-discovery/incremental.js +7 -0
- package/dist/src/work-discovery/index.d.ts +10 -0
- package/dist/src/work-discovery/index.js +13 -3
- package/dist/src/work-discovery/investigation-repository.d.ts +16 -0
- package/dist/src/work-discovery/investigation-repository.js +122 -0
- package/dist/src/work-discovery/investigator.d.ts +55 -0
- package/dist/src/work-discovery/investigator.js +358 -0
- package/dist/src/work-discovery/probe.d.ts +22 -0
- package/dist/src/work-discovery/probe.js +88 -1
- package/dist/src/work-discovery/service.d.ts +110 -1
- package/dist/src/work-discovery/service.js +578 -8
- package/dist/src/work-discovery/source-repository.d.ts +13 -0
- package/dist/src/work-discovery/source-repository.js +72 -0
- package/dist/src/work-discovery/thread-repository.d.ts +34 -0
- package/dist/src/work-discovery/thread-repository.js +112 -0
- package/dist/src/work-discovery/thread-service.d.ts +8 -0
- package/dist/src/work-discovery/thread-service.js +104 -0
- package/dist/src/work-discovery/types.d.ts +145 -0
- package/package.json +1 -1
- package/skills/creative/hatch-pet/SKILL.md +2 -2
- package/dist/gateway/static/root/assets/activity-store-C-57rvkX.js +0 -1
- package/dist/gateway/static/root/assets/agents-P05C4A-3.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-DANGrBpN.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-BXfGL_cm.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-534luV0B.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-M91zFJVi.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-vooEF4lG.js +0 -1
- package/dist/gateway/static/root/assets/check-BODqo66o.js +0 -1
- package/dist/gateway/static/root/assets/circle-alert-RzVebuX2.js +0 -1
- package/dist/gateway/static/root/assets/cn-BQ_8x0bT.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-CV5UulW6.js +0 -2
- package/dist/gateway/static/root/assets/desktop-pet-Cil2eSpW.js +0 -1
- package/dist/gateway/static/root/assets/desktop-pet-settings-DwsC6l5F.js +0 -1
- package/dist/gateway/static/root/assets/desktop-pet-sprite-DqZ6fbIj.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-BpJ0v56l.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-lMHD-2CX.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-yPmF-G7b.js +0 -4
- package/dist/gateway/static/root/assets/goals-page-BHTCKobG.js +0 -2
- package/dist/gateway/static/root/assets/index-BgZhEOkN.css +0 -1
- package/dist/gateway/static/root/assets/index-BuqGh2SW.js +0 -193
- package/dist/gateway/static/root/assets/local-app-workbench-page-zpp_wciE.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-B4CRYqYR.js +0 -7
- package/dist/gateway/static/root/assets/messages-BuT0SR1R.js +0 -5
- package/dist/gateway/static/root/assets/models-hub-panel-1hdoAoB_.js +0 -2
- package/dist/gateway/static/root/assets/notes-workbench-DQEAZTA5.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-Bp9bC7A6.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-RNorFXTw.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BqRTcM_6.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-B2EfW1cm.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-CklL85T4.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-D3my1HgB.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-DVxgjrsP.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-Bcu58ZaF.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-CDd-84FE.js +0 -2
- package/dist/gateway/static/root/assets/sliding-segmented-uWmrxE3P.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-1OyJnTyR.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-B2ncRwHS.js +0 -1
- package/dist/gateway/static/root/assets/work-discovery-page-BZlY4PZL.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-DPXMrneY.js +0 -1
- package/dist/gateway/static/root/assets/work-page-CTERoqyv.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-CLgsnqFQ.js +0 -11
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { GoalService } from "../goals/goal-service.js";
|
|
4
|
+
import "../goals/index.js";
|
|
5
|
+
import { addWorkUnderstandingThreadFeedback, getWorkUnderstandingThread, listWorkUnderstandingThreads } from "../work-discovery/thread-repository.js";
|
|
6
|
+
import { createFocusWatch, getFocusWatchByThreadAndKind, listFocusWatches, recordFocusWatchFeedback, restartFocusWatchTrial, setFocusWatchStatus } from "./watch-repository.js";
|
|
7
|
+
//#region src/proactive/focus-service.ts
|
|
8
|
+
init_logger();
|
|
9
|
+
const TRIAL_DURATION_MS = 7 * 864e5;
|
|
10
|
+
const DAILY_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
11
|
+
const log = createLogger("FocusService");
|
|
12
|
+
function instructionForFocus(title, summary, kind, eventContext) {
|
|
13
|
+
const intent = kind === "progress" ? "Look for meaningful progress, blockers, and the most useful next step." : kind === "staleness" ? "Check whether this focus has stalled and identify the smallest credible unblock." : kind === "deadline" ? "Check upcoming deadlines or meetings and prepare only the material that is needed next." : "Search for external changes published since the previous daily check. Use primary sources when possible, include the publication date and canonical URL, reject generic background news, and explain the concrete impact on this focus.";
|
|
14
|
+
return [
|
|
15
|
+
"Maintain this user-confirmed focus in suggest-only mode.",
|
|
16
|
+
`Focus: ${title}`,
|
|
17
|
+
`Context: ${summary}`,
|
|
18
|
+
...eventContext ? [`Upcoming event selected by the user: ${eventContext}`] : [],
|
|
19
|
+
intent,
|
|
20
|
+
"Inspect only evidence that changed since the previous daily check; use timestamps, history, status, or source revisions to establish the delta.",
|
|
21
|
+
"Use evidence available to the agent. Do not modify files, send messages, publish content, or claim progress without evidence.",
|
|
22
|
+
"Return JSON only, with no markdown.",
|
|
23
|
+
"If nothing materially changed: {\"meaningful\":false}.",
|
|
24
|
+
"Otherwise return {\"meaningful\":true,\"title\":\"short title\",\"summary\":\"what changed\",\"whyItMatters\":\"concrete impact\",\"nextAction\":\"one next action\",\"evidence\":[{\"label\":\"specific observed evidence\",\"source\":\"file, event, or canonical URL\",\"publishedAt\":\"ISO date when this is external news\"}]}.",
|
|
25
|
+
"A meaningful result requires at least one specific evidence item. Never invent evidence. Keep the entire JSON response under 2,000 characters."
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
var FocusService = class {
|
|
29
|
+
goals = new GoalService();
|
|
30
|
+
constructor(automations) {
|
|
31
|
+
this.automations = automations;
|
|
32
|
+
}
|
|
33
|
+
list(options = {}) {
|
|
34
|
+
const threads = listWorkUnderstandingThreads({ limit: 200 }).filter((thread) => options.includeUnreviewed || thread.userStatus === "confirmed" || thread.userStatus === "corrected");
|
|
35
|
+
const watches = listFocusWatches();
|
|
36
|
+
const goals = this.goals.list({
|
|
37
|
+
status: [
|
|
38
|
+
"active",
|
|
39
|
+
"blocked",
|
|
40
|
+
"needs_input",
|
|
41
|
+
"paused"
|
|
42
|
+
],
|
|
43
|
+
limit: 200
|
|
44
|
+
});
|
|
45
|
+
return threads.map((thread) => {
|
|
46
|
+
const goal = goals.find((candidate) => candidate.projectId && thread.projectIds.includes(candidate.projectId));
|
|
47
|
+
return {
|
|
48
|
+
id: thread.id,
|
|
49
|
+
title: thread.title,
|
|
50
|
+
summary: thread.summary,
|
|
51
|
+
status: thread.status,
|
|
52
|
+
horizon: thread.horizon,
|
|
53
|
+
confidence: thread.confidence,
|
|
54
|
+
focusScore: thread.focusScore,
|
|
55
|
+
userStatus: thread.userStatus,
|
|
56
|
+
projectIds: thread.projectIds,
|
|
57
|
+
...goal ? {
|
|
58
|
+
goalId: goal.id,
|
|
59
|
+
nextAction: goal.nextAction,
|
|
60
|
+
blockedReason: goal.blockedReason
|
|
61
|
+
} : {},
|
|
62
|
+
watches: watches.filter((watch) => watch.threadId === thread.id),
|
|
63
|
+
lastObservedAt: thread.lastObservedAt
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
confirm(id) {
|
|
68
|
+
const thread = addWorkUnderstandingThreadFeedback({
|
|
69
|
+
threadId: id,
|
|
70
|
+
decision: "confirmed"
|
|
71
|
+
});
|
|
72
|
+
return thread ? this.list({ includeUnreviewed: true }).find((focus) => focus.id === thread.id) ?? null : null;
|
|
73
|
+
}
|
|
74
|
+
async activateTrial(input) {
|
|
75
|
+
const kind = input.kind ?? "progress";
|
|
76
|
+
let thread = getWorkUnderstandingThread(input.threadId);
|
|
77
|
+
if (!thread || thread.userStatus === "rejected") throw new Error("Focus not found");
|
|
78
|
+
if (thread.userStatus === "unreviewed") thread = addWorkUnderstandingThreadFeedback({
|
|
79
|
+
threadId: thread.id,
|
|
80
|
+
decision: "confirmed"
|
|
81
|
+
});
|
|
82
|
+
if (!thread) throw new Error("Focus not found");
|
|
83
|
+
const existing = getFocusWatchByThreadAndKind(thread.id, kind);
|
|
84
|
+
if (existing) {
|
|
85
|
+
if (input.eventContext) await this.automations.update(existing.automationId, { action: {
|
|
86
|
+
kind: "agent",
|
|
87
|
+
instruction: instructionForFocus(thread.title, thread.summary, kind, input.eventContext),
|
|
88
|
+
timeoutSeconds: 300
|
|
89
|
+
} });
|
|
90
|
+
if (existing.status === "paused") {
|
|
91
|
+
await this.automations.resume(existing.automationId);
|
|
92
|
+
restartFocusWatchTrial(existing.id, Date.now() + TRIAL_DURATION_MS);
|
|
93
|
+
}
|
|
94
|
+
if (input.eventContext) this.automations.runNow(existing.automationId).catch((err) => {
|
|
95
|
+
log.warn({
|
|
96
|
+
err,
|
|
97
|
+
automationId: existing.automationId,
|
|
98
|
+
watchId: existing.id
|
|
99
|
+
}, "Focus event preparation run failed");
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
focus: this.list({ includeUnreviewed: true }).find((item) => item.id === thread.id),
|
|
103
|
+
watch: getFocusWatchByThreadAndKind(thread.id, kind)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const automation = await this.automations.create({
|
|
107
|
+
name: `Keep moving: ${thread.title}`.slice(0, 200),
|
|
108
|
+
description: `Seven-day ${kind} watch for a user-confirmed focus.`,
|
|
109
|
+
...thread.projectIds[0] ? { projectId: thread.projectIds[0] } : {},
|
|
110
|
+
trigger: {
|
|
111
|
+
kind: "schedule",
|
|
112
|
+
schedule: {
|
|
113
|
+
kind: "interval",
|
|
114
|
+
everyMs: DAILY_INTERVAL_MS
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
action: {
|
|
118
|
+
kind: "agent",
|
|
119
|
+
instruction: instructionForFocus(thread.title, thread.summary, kind, input.eventContext),
|
|
120
|
+
timeoutSeconds: 300
|
|
121
|
+
},
|
|
122
|
+
safety: { mode: "suggest_only" },
|
|
123
|
+
afterRun: { kind: "none" },
|
|
124
|
+
reliability: {
|
|
125
|
+
timeoutSeconds: 300,
|
|
126
|
+
disableAfterConsecutiveFailures: 3
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const watch = createFocusWatch({
|
|
130
|
+
threadId: thread.id,
|
|
131
|
+
automationId: automation.id,
|
|
132
|
+
kind,
|
|
133
|
+
config: {
|
|
134
|
+
cadence: "daily",
|
|
135
|
+
notifyOnlyWhenMeaningful: true
|
|
136
|
+
},
|
|
137
|
+
trialEndsAt: Date.now() + TRIAL_DURATION_MS
|
|
138
|
+
});
|
|
139
|
+
this.automations.runNow(automation.id).catch((err) => {
|
|
140
|
+
log.warn({
|
|
141
|
+
err,
|
|
142
|
+
automationId: automation.id,
|
|
143
|
+
watchId: watch.id
|
|
144
|
+
}, "Initial focus watch run failed");
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
focus: this.list({ includeUnreviewed: true }).find((item) => item.id === thread.id),
|
|
148
|
+
watch
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async pauseWatch(id) {
|
|
152
|
+
const watch = listFocusWatches().find((item) => item.id === id);
|
|
153
|
+
if (!watch) return null;
|
|
154
|
+
await this.automations.pause(watch.automationId);
|
|
155
|
+
return setFocusWatchStatus(id, "paused");
|
|
156
|
+
}
|
|
157
|
+
async reconcileExpiredTrials(nowMs = Date.now()) {
|
|
158
|
+
const expired = listFocusWatches({ status: "active" }).filter((watch) => watch.trialEndsAt != null && watch.trialEndsAt <= nowMs);
|
|
159
|
+
for (const watch of expired) {
|
|
160
|
+
await this.automations.pause(watch.automationId);
|
|
161
|
+
setFocusWatchStatus(watch.id, "paused", nowMs);
|
|
162
|
+
}
|
|
163
|
+
return expired.length;
|
|
164
|
+
}
|
|
165
|
+
async recordInsightFeedback(watchId, useful) {
|
|
166
|
+
const result = recordFocusWatchFeedback(watchId, useful);
|
|
167
|
+
if (!result) return null;
|
|
168
|
+
if (!useful && result.consecutiveDismissed >= 3) return this.pauseWatch(watchId);
|
|
169
|
+
return result.watch;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { FocusService };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './calendar-signals.js';
|
|
3
|
+
export * from './focus-service.js';
|
|
4
|
+
export * from './insight-repository.js';
|
|
5
|
+
export * from './run-processor.js';
|
|
6
|
+
export * from './trial-expiry-reconciler.js';
|
|
7
|
+
export * from './watch-repository.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { buildFocusCalendarSignals, listFocusCalendarSignals } from "./calendar-signals.js";
|
|
2
|
+
import { createFocusWatch, getFocusWatchByAutomationId, getFocusWatchByThreadAndKind, listFocusWatches, recordFocusWatchFeedback, recordFocusWatchRun, restartFocusWatchTrial, setFocusWatchStatus } from "./watch-repository.js";
|
|
3
|
+
import { FocusService } from "./focus-service.js";
|
|
4
|
+
import { claimProactiveInsightApproval, createProactiveInsight, getProactiveInsight, listProactiveInsights, setProactiveInsightStatus } from "./insight-repository.js";
|
|
5
|
+
import { hasValidIntelligenceEvidence, parseFocusRunResult, processFocusAutomationRun } from "./run-processor.js";
|
|
6
|
+
import { startTrialExpiryReconciler } from "./trial-expiry-reconciler.js";
|
|
7
|
+
export { FocusService, buildFocusCalendarSignals, claimProactiveInsightApproval, createFocusWatch, createProactiveInsight, getFocusWatchByAutomationId, getFocusWatchByThreadAndKind, getProactiveInsight, hasValidIntelligenceEvidence, listFocusCalendarSignals, listFocusWatches, listProactiveInsights, parseFocusRunResult, processFocusAutomationRun, recordFocusWatchFeedback, recordFocusWatchRun, restartFocusWatchTrial, setFocusWatchStatus, setProactiveInsightStatus, startTrialExpiryReconciler };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FocusWatchKind, ProactiveEvidence, ProactiveInsight, ProactiveInsightStatus } from './types.js';
|
|
2
|
+
export declare function getProactiveInsight(id: string): ProactiveInsight | null;
|
|
3
|
+
export declare function listProactiveInsights(options?: {
|
|
4
|
+
status?: ProactiveInsightStatus[];
|
|
5
|
+
limit?: number;
|
|
6
|
+
}): ProactiveInsight[];
|
|
7
|
+
export declare function createProactiveInsight(input: {
|
|
8
|
+
watchId: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
kind: FocusWatchKind;
|
|
11
|
+
title: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
whyItMatters: string;
|
|
14
|
+
nextAction: string;
|
|
15
|
+
evidence: ProactiveEvidence[];
|
|
16
|
+
nowMs?: number;
|
|
17
|
+
}): ProactiveInsight | null;
|
|
18
|
+
export declare function setProactiveInsightStatus(id: string, status: ProactiveInsightStatus, nowMs?: number, expectedStatus?: ProactiveInsightStatus): ProactiveInsight | null;
|
|
19
|
+
export declare function claimProactiveInsightApproval(id: string, nowMs?: number): ProactiveInsight | null;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { requireXopcDatabase } from "../storage/sqlite/connection.js";
|
|
2
|
+
import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
//#region src/proactive/insight-repository.ts
|
|
5
|
+
function insightFromRow(row) {
|
|
6
|
+
return {
|
|
7
|
+
id: row.insight_id,
|
|
8
|
+
watchId: row.watch_id,
|
|
9
|
+
runId: row.run_id,
|
|
10
|
+
kind: row.kind,
|
|
11
|
+
title: row.title,
|
|
12
|
+
summary: row.summary,
|
|
13
|
+
whyItMatters: row.why_it_matters,
|
|
14
|
+
nextAction: row.next_action,
|
|
15
|
+
evidence: JSON.parse(row.evidence_json),
|
|
16
|
+
status: row.status,
|
|
17
|
+
createdAt: row.created_at,
|
|
18
|
+
updatedAt: row.updated_at
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getProactiveInsight(id) {
|
|
22
|
+
const { db } = requireXopcDatabase();
|
|
23
|
+
const row = db.prepare("SELECT * FROM proactive_insights WHERE insight_id = ?").get(id);
|
|
24
|
+
return row ? insightFromRow(row) : null;
|
|
25
|
+
}
|
|
26
|
+
function listProactiveInsights(options = {}) {
|
|
27
|
+
const statuses = options.status ?? ["unread", "read"];
|
|
28
|
+
if (statuses.length === 0) return [];
|
|
29
|
+
const limit = Math.max(1, Math.min(100, options.limit ?? 20));
|
|
30
|
+
const { db } = requireXopcDatabase();
|
|
31
|
+
return db.prepare(`SELECT * FROM proactive_insights
|
|
32
|
+
WHERE status IN (${statuses.map(() => "?").join(", ")})
|
|
33
|
+
ORDER BY created_at DESC LIMIT ?`).all(...statuses, limit).map(insightFromRow);
|
|
34
|
+
}
|
|
35
|
+
function createProactiveInsight(input) {
|
|
36
|
+
const id = randomUUID();
|
|
37
|
+
const now = input.nowMs ?? Date.now();
|
|
38
|
+
const intelligenceUpdates = input.evidence.map((item) => [
|
|
39
|
+
canonicalSource(item.source),
|
|
40
|
+
canonicalPublishedAt(item.publishedAt),
|
|
41
|
+
normalizeIdentityText(item.label)
|
|
42
|
+
]).filter(([source]) => Boolean(source)).toSorted((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
43
|
+
const fingerprint = input.kind === "intelligence" && intelligenceUpdates.length > 0 ? JSON.stringify({
|
|
44
|
+
kind: input.kind,
|
|
45
|
+
updates: intelligenceUpdates
|
|
46
|
+
}) : JSON.stringify({
|
|
47
|
+
kind: input.kind,
|
|
48
|
+
title: input.title.toLocaleLowerCase().trim(),
|
|
49
|
+
summary: input.summary.toLocaleLowerCase().trim(),
|
|
50
|
+
evidence: input.evidence.map((item) => [
|
|
51
|
+
item.label.toLocaleLowerCase().trim(),
|
|
52
|
+
canonicalSource(item.source),
|
|
53
|
+
item.publishedAt ?? ""
|
|
54
|
+
])
|
|
55
|
+
});
|
|
56
|
+
const contentHash = createHash("sha256").update(fingerprint).digest("hex");
|
|
57
|
+
let created = false;
|
|
58
|
+
runSqliteWriteTransaction((db) => {
|
|
59
|
+
created = db.prepare(`INSERT OR IGNORE INTO proactive_insights (
|
|
60
|
+
insight_id, watch_id, run_id, kind, title, summary, why_it_matters,
|
|
61
|
+
next_action, evidence_json, content_hash, status, created_at, updated_at
|
|
62
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'unread', ?, ?)`).run(id, input.watchId, input.runId, input.kind, input.title, input.summary, input.whyItMatters, input.nextAction, JSON.stringify(input.evidence), contentHash, now, now).changes > 0;
|
|
63
|
+
});
|
|
64
|
+
return created ? listProactiveInsights({ limit: 100 }).find((item) => item.id === id) ?? null : null;
|
|
65
|
+
}
|
|
66
|
+
function canonicalPublishedAt(value) {
|
|
67
|
+
if (!value) return "";
|
|
68
|
+
const timestamp = Date.parse(value);
|
|
69
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : value.trim();
|
|
70
|
+
}
|
|
71
|
+
function normalizeIdentityText(value) {
|
|
72
|
+
return value.trim().toLocaleLowerCase().replace(/\s+/g, " ");
|
|
73
|
+
}
|
|
74
|
+
function canonicalSource(source) {
|
|
75
|
+
const value = source?.trim();
|
|
76
|
+
if (!value) return "";
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(value);
|
|
79
|
+
url.hash = "";
|
|
80
|
+
for (const key of [...url.searchParams.keys()]) if (key.toLowerCase().startsWith("utm_") || key === "ref" || key === "source") url.searchParams.delete(key);
|
|
81
|
+
url.pathname = url.pathname.replace(/\/$/, "");
|
|
82
|
+
return url.toString().toLocaleLowerCase();
|
|
83
|
+
} catch {
|
|
84
|
+
return value.toLocaleLowerCase();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function setProactiveInsightStatus(id, status, nowMs = Date.now(), expectedStatus) {
|
|
88
|
+
let changed = false;
|
|
89
|
+
runSqliteWriteTransaction((db) => {
|
|
90
|
+
changed = db.prepare(`UPDATE proactive_insights SET status = ?, updated_at = ? WHERE insight_id = ?
|
|
91
|
+
${expectedStatus ? "AND status = ?" : ""}`).run(status, nowMs, id, ...expectedStatus ? [expectedStatus] : []).changes > 0;
|
|
92
|
+
});
|
|
93
|
+
if (!changed) return null;
|
|
94
|
+
const { db } = requireXopcDatabase();
|
|
95
|
+
return insightFromRow(db.prepare("SELECT * FROM proactive_insights WHERE insight_id = ?").get(id));
|
|
96
|
+
}
|
|
97
|
+
function claimProactiveInsightApproval(id, nowMs = Date.now()) {
|
|
98
|
+
let claimed = false;
|
|
99
|
+
runSqliteWriteTransaction((db) => {
|
|
100
|
+
claimed = db.prepare(`UPDATE proactive_insights SET status = 'approved', updated_at = ?
|
|
101
|
+
WHERE insight_id = ? AND status = 'unread'`).run(nowMs, id).changes > 0;
|
|
102
|
+
});
|
|
103
|
+
return claimed ? getProactiveInsight(id) : null;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { claimProactiveInsightApproval, createProactiveInsight, getProactiveInsight, listProactiveInsights, setProactiveInsightStatus };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AutomationRun } from '../automations/index.js';
|
|
2
|
+
import type { ProactiveEvidence, ProactiveInsight } from './types.js';
|
|
3
|
+
interface MeaningfulResult {
|
|
4
|
+
meaningful: true;
|
|
5
|
+
title: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
whyItMatters: string;
|
|
8
|
+
nextAction: string;
|
|
9
|
+
evidence: ProactiveEvidence[];
|
|
10
|
+
}
|
|
11
|
+
export declare function parseFocusRunResult(summary: string): MeaningfulResult | {
|
|
12
|
+
meaningful: false;
|
|
13
|
+
} | null;
|
|
14
|
+
export declare function hasValidIntelligenceEvidence(evidence: ProactiveEvidence[]): boolean;
|
|
15
|
+
export declare function processFocusAutomationRun(run: AutomationRun): {
|
|
16
|
+
handled: boolean;
|
|
17
|
+
insight?: ProactiveInsight;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { getFocusWatchByAutomationId, recordFocusWatchRun } from "./watch-repository.js";
|
|
4
|
+
import { createProactiveInsight } from "./insight-repository.js";
|
|
5
|
+
//#region src/proactive/run-processor.ts
|
|
6
|
+
init_logger();
|
|
7
|
+
const log = createLogger("ProactiveRunProcessor");
|
|
8
|
+
function nonEmptyString(value) {
|
|
9
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10
|
+
}
|
|
11
|
+
function parseFocusRunResult(summary) {
|
|
12
|
+
const start = summary.indexOf("{");
|
|
13
|
+
const end = summary.lastIndexOf("}");
|
|
14
|
+
if (start < 0 || end <= start) return null;
|
|
15
|
+
let value;
|
|
16
|
+
try {
|
|
17
|
+
value = JSON.parse(summary.slice(start, end + 1));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
22
|
+
const result = value;
|
|
23
|
+
if (result.meaningful === false) return { meaningful: false };
|
|
24
|
+
if (result.meaningful !== true) return null;
|
|
25
|
+
const title = nonEmptyString(result.title);
|
|
26
|
+
const resultSummary = nonEmptyString(result.summary);
|
|
27
|
+
const whyItMatters = nonEmptyString(result.whyItMatters);
|
|
28
|
+
const nextAction = nonEmptyString(result.nextAction);
|
|
29
|
+
const evidence = Array.isArray(result.evidence) ? result.evidence.flatMap((item) => {
|
|
30
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
31
|
+
const label = nonEmptyString(item.label);
|
|
32
|
+
const source = nonEmptyString(item.source);
|
|
33
|
+
const publishedAtValue = nonEmptyString(item.publishedAt);
|
|
34
|
+
const publishedAt = publishedAtValue && Number.isFinite(Date.parse(publishedAtValue)) ? new Date(publishedAtValue).toISOString() : null;
|
|
35
|
+
return label ? [{
|
|
36
|
+
label,
|
|
37
|
+
...source ? { source } : {},
|
|
38
|
+
...publishedAt ? { publishedAt } : {}
|
|
39
|
+
}] : [];
|
|
40
|
+
}).slice(0, 8) : [];
|
|
41
|
+
if (!title || !resultSummary || !whyItMatters || !nextAction || evidence.length === 0) return null;
|
|
42
|
+
return {
|
|
43
|
+
meaningful: true,
|
|
44
|
+
title,
|
|
45
|
+
summary: resultSummary,
|
|
46
|
+
whyItMatters,
|
|
47
|
+
nextAction,
|
|
48
|
+
evidence
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function hasValidIntelligenceEvidence(evidence) {
|
|
52
|
+
return evidence.some((item) => {
|
|
53
|
+
if (!item.source || !item.publishedAt) return false;
|
|
54
|
+
try {
|
|
55
|
+
const url = new URL(item.source);
|
|
56
|
+
return (url.protocol === "https:" || url.protocol === "http:") && Number.isFinite(Date.parse(item.publishedAt));
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function processFocusAutomationRun(run) {
|
|
63
|
+
const watch = getFocusWatchByAutomationId(run.automationId);
|
|
64
|
+
if (!watch) return { handled: false };
|
|
65
|
+
if (run.status !== "succeeded" || !run.summary) {
|
|
66
|
+
recordFocusWatchRun({
|
|
67
|
+
id: watch.id,
|
|
68
|
+
runId: run.id,
|
|
69
|
+
outcome: "failed",
|
|
70
|
+
nowMs: run.endedAtMs
|
|
71
|
+
});
|
|
72
|
+
return { handled: true };
|
|
73
|
+
}
|
|
74
|
+
const result = parseFocusRunResult(run.summary);
|
|
75
|
+
if (!result) {
|
|
76
|
+
recordFocusWatchRun({
|
|
77
|
+
id: watch.id,
|
|
78
|
+
runId: run.id,
|
|
79
|
+
outcome: "failed",
|
|
80
|
+
nowMs: run.endedAtMs
|
|
81
|
+
});
|
|
82
|
+
log.warn({
|
|
83
|
+
automationId: run.automationId,
|
|
84
|
+
runId: run.id
|
|
85
|
+
}, "Focus watch returned an invalid result");
|
|
86
|
+
return { handled: true };
|
|
87
|
+
}
|
|
88
|
+
if (result.meaningful && watch.kind === "intelligence" && !hasValidIntelligenceEvidence(result.evidence)) {
|
|
89
|
+
recordFocusWatchRun({
|
|
90
|
+
id: watch.id,
|
|
91
|
+
runId: run.id,
|
|
92
|
+
outcome: "failed",
|
|
93
|
+
nowMs: run.endedAtMs
|
|
94
|
+
});
|
|
95
|
+
log.warn({
|
|
96
|
+
automationId: run.automationId,
|
|
97
|
+
runId: run.id
|
|
98
|
+
}, "Intelligence watch result lacked dated web evidence");
|
|
99
|
+
return { handled: true };
|
|
100
|
+
}
|
|
101
|
+
if (!recordFocusWatchRun({
|
|
102
|
+
id: watch.id,
|
|
103
|
+
runId: run.id,
|
|
104
|
+
outcome: result.meaningful ? "meaningful" : "empty",
|
|
105
|
+
nowMs: run.endedAtMs
|
|
106
|
+
})) return { handled: true };
|
|
107
|
+
if (!result.meaningful) return { handled: true };
|
|
108
|
+
const insight = createProactiveInsight({
|
|
109
|
+
watchId: watch.id,
|
|
110
|
+
runId: run.id,
|
|
111
|
+
kind: watch.kind,
|
|
112
|
+
title: result.title,
|
|
113
|
+
summary: result.summary,
|
|
114
|
+
whyItMatters: result.whyItMatters,
|
|
115
|
+
nextAction: result.nextAction,
|
|
116
|
+
evidence: result.evidence,
|
|
117
|
+
nowMs: run.endedAtMs
|
|
118
|
+
});
|
|
119
|
+
return insight ? {
|
|
120
|
+
handled: true,
|
|
121
|
+
insight
|
|
122
|
+
} : { handled: true };
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { hasValidIntelligenceEvidence, parseFocusRunResult, processFocusAutomationRun };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
//#region src/proactive/trial-expiry-reconciler.ts
|
|
4
|
+
init_logger();
|
|
5
|
+
const DEFAULT_RECONCILE_INTERVAL_MS = 6e4;
|
|
6
|
+
const log = createLogger("FocusTrialExpiry");
|
|
7
|
+
function startTrialExpiryReconciler(service, intervalMs = DEFAULT_RECONCILE_INTERVAL_MS) {
|
|
8
|
+
let stopped = false;
|
|
9
|
+
let pending = null;
|
|
10
|
+
const reconcile = () => {
|
|
11
|
+
if (stopped || pending) return;
|
|
12
|
+
pending = service.reconcileExpiredTrials().then((pausedCount) => {
|
|
13
|
+
if (pausedCount > 0) log.info({ pausedCount }, "Expired focus watch trials paused");
|
|
14
|
+
}).catch((err) => {
|
|
15
|
+
log.warn({ err }, "Focus watch trial expiry reconciliation failed");
|
|
16
|
+
}).finally(() => {
|
|
17
|
+
pending = null;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
reconcile();
|
|
21
|
+
const timer = setInterval(reconcile, intervalMs);
|
|
22
|
+
timer.unref();
|
|
23
|
+
return async () => {
|
|
24
|
+
stopped = true;
|
|
25
|
+
clearInterval(timer);
|
|
26
|
+
await pending;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { startTrialExpiryReconciler };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { WorkUnderstandingThread } from '../work-discovery/types.js';
|
|
2
|
+
export type FocusWatchKind = 'progress' | 'staleness' | 'deadline' | 'intelligence';
|
|
3
|
+
export type FocusWatchStatus = 'active' | 'paused';
|
|
4
|
+
export interface FocusWatch {
|
|
5
|
+
id: string;
|
|
6
|
+
threadId: string;
|
|
7
|
+
goalId?: string;
|
|
8
|
+
automationId: string;
|
|
9
|
+
kind: FocusWatchKind;
|
|
10
|
+
status: FocusWatchStatus;
|
|
11
|
+
config: Record<string, unknown>;
|
|
12
|
+
trialEndsAt?: number;
|
|
13
|
+
lastCursor?: string;
|
|
14
|
+
lastRunAt?: number;
|
|
15
|
+
lastUsefulResultAt?: number;
|
|
16
|
+
consecutiveEmptyRuns: number;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
}
|
|
20
|
+
export interface FocusView {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
summary: string;
|
|
24
|
+
status: WorkUnderstandingThread['status'];
|
|
25
|
+
horizon: WorkUnderstandingThread['horizon'];
|
|
26
|
+
confidence: number;
|
|
27
|
+
focusScore: number;
|
|
28
|
+
userStatus: WorkUnderstandingThread['userStatus'];
|
|
29
|
+
projectIds: string[];
|
|
30
|
+
goalId?: string;
|
|
31
|
+
nextAction?: string;
|
|
32
|
+
blockedReason?: string;
|
|
33
|
+
watches: FocusWatch[];
|
|
34
|
+
lastObservedAt: number;
|
|
35
|
+
}
|
|
36
|
+
export type ProactiveInsightStatus = 'unread' | 'read' | 'approved' | 'dismissed';
|
|
37
|
+
export interface ProactiveEvidence {
|
|
38
|
+
label: string;
|
|
39
|
+
source?: string;
|
|
40
|
+
publishedAt?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ProactiveInsight {
|
|
43
|
+
id: string;
|
|
44
|
+
watchId: string;
|
|
45
|
+
runId: string;
|
|
46
|
+
kind: FocusWatchKind;
|
|
47
|
+
title: string;
|
|
48
|
+
summary: string;
|
|
49
|
+
whyItMatters: string;
|
|
50
|
+
nextAction: string;
|
|
51
|
+
evidence: ProactiveEvidence[];
|
|
52
|
+
status: ProactiveInsightStatus;
|
|
53
|
+
createdAt: number;
|
|
54
|
+
updatedAt: number;
|
|
55
|
+
}
|
|
56
|
+
export interface FocusCalendarSignal {
|
|
57
|
+
id: string;
|
|
58
|
+
focusId: string;
|
|
59
|
+
focusTitle: string;
|
|
60
|
+
title: string;
|
|
61
|
+
startsAt: number;
|
|
62
|
+
endsAt?: number;
|
|
63
|
+
sourceInstanceId: string;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FocusWatch, FocusWatchKind, FocusWatchStatus } from './types.js';
|
|
2
|
+
export declare function listFocusWatches(options?: {
|
|
3
|
+
threadId?: string;
|
|
4
|
+
status?: FocusWatchStatus;
|
|
5
|
+
}): FocusWatch[];
|
|
6
|
+
export declare function getFocusWatchByThreadAndKind(threadId: string, kind: FocusWatchKind): FocusWatch | null;
|
|
7
|
+
export declare function getFocusWatchByAutomationId(automationId: string): FocusWatch | null;
|
|
8
|
+
export declare function createFocusWatch(input: {
|
|
9
|
+
threadId: string;
|
|
10
|
+
goalId?: string;
|
|
11
|
+
automationId: string;
|
|
12
|
+
kind: FocusWatchKind;
|
|
13
|
+
config?: Record<string, unknown>;
|
|
14
|
+
trialEndsAt?: number;
|
|
15
|
+
nowMs?: number;
|
|
16
|
+
}): FocusWatch;
|
|
17
|
+
export declare function setFocusWatchStatus(id: string, status: FocusWatchStatus, nowMs?: number): FocusWatch | null;
|
|
18
|
+
export declare function restartFocusWatchTrial(id: string, trialEndsAt: number, nowMs?: number): FocusWatch | null;
|
|
19
|
+
export declare function recordFocusWatchRun(input: {
|
|
20
|
+
id: string;
|
|
21
|
+
runId: string;
|
|
22
|
+
outcome: 'meaningful' | 'empty' | 'failed';
|
|
23
|
+
nowMs?: number;
|
|
24
|
+
}): FocusWatch | null;
|
|
25
|
+
export declare function recordFocusWatchFeedback(id: string, useful: boolean, nowMs?: number): {
|
|
26
|
+
watch: FocusWatch;
|
|
27
|
+
consecutiveDismissed: number;
|
|
28
|
+
} | null;
|